3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Alfredo Braunstein
9 * \author Angus Leeming
10 * \author Asger Alstrup
12 * \author Dov Feldstern
13 * \author Jean-Marc Lasgouttes
15 * \author Jürgen Vigna
16 * \author Lars Gullik Bjønnes
17 * \author Stefan Schimanski
19 * Full author contact details are available in file CREDITS.
27 #include "BranchList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
32 #include "CompletionList.h"
34 #include "CursorSlice.h"
35 #include "CutAndPaste.h"
36 #include "DispatchResult.h"
38 #include "ErrorList.h"
40 #include "FloatList.h"
42 #include "FuncRequest.h"
43 #include "FuncStatus.h"
44 #include "InsetList.h"
50 #include "LyXAction.h"
53 #include "Paragraph.h"
54 #include "ParagraphParameters.h"
55 #include "SpellChecker.h"
56 #include "TextClass.h"
57 #include "TextMetrics.h"
58 #include "Thesaurus.h"
59 #include "WordLangTuple.h"
62 #include "frontends/alert.h"
63 #include "frontends/Application.h"
64 #include "frontends/Clipboard.h"
65 #include "frontends/Selection.h"
67 #include "mathed/InsetMathHull.h"
68 #include "mathed/InsetMathMacroTemplate.h"
70 #include "insets/Inset.h"
71 #include "insets/InsetArgument.h"
72 #include "insets/InsetCaption.h"
73 #include "insets/InsetCollapsible.h"
74 #include "insets/InsetCommand.h"
75 #include "insets/InsetExternal.h"
76 #include "insets/InsetFloat.h"
77 #include "insets/InsetFloatList.h"
78 #include "insets/InsetGraphics.h"
79 #include "insets/InsetGraphicsParams.h"
80 #include "insets/InsetIndexMacro.h"
81 #include "insets/InsetInfo.h"
82 #include "insets/InsetIPAMacro.h"
83 #include "insets/InsetNewline.h"
84 #include "insets/InsetQuotes.h"
85 #include "insets/InsetSpecialChar.h"
86 #include "insets/InsetTabular.h"
87 #include "insets/InsetText.h"
88 #include "insets/InsetWrap.h"
90 #include "support/convert.h"
91 #include "support/debug.h"
92 #include "support/docstream.h"
93 #include "support/docstring.h"
94 #include "support/docstring_list.h"
95 #include "support/filetools.h"
96 #include "support/gettext.h"
97 #include "support/lassert.h"
98 #include "support/limited_stack.h"
99 #include "support/lstrings.h"
100 #include "support/lyxtime.h"
101 #include "support/textutils.h"
102 #include "support/unique_ptr.h"
111 using namespace support;
113 using frontend::Clipboard;
118 bool moveItem(Paragraph & fromPar, pos_type fromPos,
119 Paragraph & toPar, pos_type toPos, BufferParams const & params)
121 // Note: moveItem() does not honour change tracking!
122 // Therefore, it should only be used for breaking and merging paragraphs
124 // We need a copy here because the character at fromPos is going to be erased.
125 Font const tmpFont = fromPar.getFontSettings(params, fromPos);
126 Change const tmpChange = fromPar.lookupChange(fromPos);
128 if (Inset * tmpInset = fromPar.getInset(fromPos)) {
129 fromPar.releaseInset(fromPos);
130 // The inset is not in fromPar any more.
131 if (!toPar.insertInset(toPos, tmpInset, tmpFont, tmpChange)) {
138 char_type const tmpChar = fromPar.getChar(fromPos);
139 fromPar.eraseChar(fromPos, false);
140 toPar.insertChar(toPos, tmpChar, tmpFont, tmpChange);
148 void breakParagraphConservative(BufferParams const & bparams,
149 ParagraphList & pars, pit_type pit, pos_type pos)
151 // create a new paragraph
152 Paragraph & tmp = *pars.insert(pars.iterator_at(pit + 1), Paragraph());
153 Paragraph & par = pars[pit];
155 tmp.setInsetOwner(&par.inInset());
156 tmp.makeSameLayout(par);
158 LASSERT(pos <= par.size(), return);
160 if (pos < par.size()) {
161 // move everything behind the break position to the new paragraph
162 pos_type pos_end = par.size() - 1;
164 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
165 if (moveItem(par, pos, tmp, j, bparams)) {
169 // Move over the end-of-par change information
170 tmp.setChange(tmp.size(), par.lookupChange(par.size()));
171 par.setChange(par.size(), Change(bparams.track_changes ?
172 Change::INSERTED : Change::UNCHANGED));
177 void mergeParagraph(BufferParams const & bparams,
178 ParagraphList & pars, pit_type par_offset)
180 Paragraph & next = pars[par_offset + 1];
181 Paragraph & par = pars[par_offset];
183 pos_type pos_end = next.size() - 1;
184 pos_type pos_insert = par.size();
186 // the imaginary end-of-paragraph character (at par.size()) has to be
187 // marked as unmodified. Otherwise, its change is adopted by the first
188 // character of the next paragraph.
189 if (par.isChanged(par.size())) {
190 LYXERR(Debug::CHANGES,
191 "merging par with inserted/deleted end-of-par character");
192 par.setChange(par.size(), Change(Change::UNCHANGED));
195 Change change = next.lookupChange(next.size());
197 // move the content of the second paragraph to the end of the first one
198 for (pos_type i = 0, j = pos_insert; i <= pos_end; ++i) {
199 if (moveItem(next, 0, par, j, bparams)) {
204 // move the change of the end-of-paragraph character
205 par.setChange(par.size(), change);
207 pars.erase(pars.iterator_at(par_offset + 1));
211 Text::Text(InsetText * owner, bool use_default_layout)
214 pars_.push_back(Paragraph());
215 Paragraph & par = pars_.back();
216 par.setInsetOwner(owner);
217 DocumentClass const & dc = owner->buffer().params().documentClass();
218 if (use_default_layout)
219 par.setDefaultLayout(dc);
221 par.setPlainLayout(dc);
225 Text::Text(InsetText * owner, Text const & text)
226 : owner_(owner), pars_(text.pars_)
228 for (auto & p : pars_)
229 p.setInsetOwner(owner);
233 pit_type Text::depthHook(pit_type pit, depth_type depth) const
235 pit_type newpit = pit;
240 while (newpit != 0 && pars_[newpit].getDepth() > depth)
243 if (pars_[newpit].getDepth() > depth)
250 pit_type Text::outerHook(pit_type par_offset) const
252 Paragraph const & par = pars_[par_offset];
254 if (par.getDepth() == 0)
256 return depthHook(par_offset, par.getDepth() - 1);
260 bool Text::isFirstInSequence(pit_type par_offset) const
262 Paragraph const & par = pars_[par_offset];
264 pit_type dhook_offset = depthHook(par_offset, par.getDepth());
266 if (dhook_offset == par_offset)
269 Paragraph const & dhook = pars_[dhook_offset];
271 return dhook.layout() != par.layout()
272 || dhook.getDepth() != par.getDepth();
276 pit_type Text::lastInSequence(pit_type pit) const
278 depth_type const depth = pars_[pit].getDepth();
279 pit_type newpit = pit;
281 while (size_t(newpit + 1) < pars_.size() &&
282 (pars_[newpit + 1].getDepth() > depth ||
283 (pars_[newpit + 1].getDepth() == depth &&
284 pars_[newpit + 1].layout() == pars_[pit].layout())))
291 int Text::getTocLevel(pit_type par_offset) const
293 Paragraph const & par = pars_[par_offset];
295 if (par.layout().isEnvironment() && !isFirstInSequence(par_offset))
296 return Layout::NOT_IN_TOC;
298 return par.layout().toclevel;
302 Font const Text::outerFont(pit_type par_offset) const
304 depth_type par_depth = pars_[par_offset].getDepth();
305 FontInfo tmpfont = inherit_font;
306 depth_type prev_par_depth = 0;
307 // Resolve against environment font information
308 while (par_offset != pit_type(pars_.size())
309 && par_depth != prev_par_depth
311 && !tmpfont.resolved()) {
312 prev_par_depth = par_depth;
313 par_offset = outerHook(par_offset);
314 if (par_offset != pit_type(pars_.size())) {
315 tmpfont.realize(pars_[par_offset].layout().font);
316 par_depth = pars_[par_offset].getDepth();
320 return Font(tmpfont);
324 int Text::getEndLabel(pit_type p) const
327 depth_type par_depth = pars_[p].getDepth();
328 while (pit != pit_type(pars_.size())) {
329 Layout const & layout = pars_[pit].layout();
330 int const endlabeltype = layout.endlabeltype;
332 if (endlabeltype != END_LABEL_NO_LABEL) {
333 if (p + 1 == pit_type(pars_.size()))
336 depth_type const next_depth =
337 pars_[p + 1].getDepth();
338 if (par_depth > next_depth ||
339 (par_depth == next_depth && layout != pars_[p + 1].layout()))
345 pit = outerHook(pit);
346 if (pit != pit_type(pars_.size()))
347 par_depth = pars_[pit].getDepth();
349 return END_LABEL_NO_LABEL;
353 static void acceptOrRejectChanges(ParagraphList & pars,
354 BufferParams const & bparams, Text::ChangeOp op)
356 pit_type pars_size = static_cast<pit_type>(pars.size());
358 // first, accept or reject changes within each individual
359 // paragraph (do not consider end-of-par)
360 for (pit_type pit = 0; pit < pars_size; ++pit) {
361 // prevent assertion failure
362 if (!pars[pit].empty()) {
363 if (op == Text::ACCEPT)
364 pars[pit].acceptChanges(0, pars[pit].size());
366 pars[pit].rejectChanges(0, pars[pit].size());
370 // next, accept or reject imaginary end-of-par characters
371 for (pit_type pit = 0; pit < pars_size; ++pit) {
372 pos_type pos = pars[pit].size();
373 if (pars[pit].isChanged(pos)) {
374 // keep the end-of-par char if it is inserted and accepted
375 // or when it is deleted and rejected.
376 if (pars[pit].isInserted(pos) == (op == Text::ACCEPT)) {
377 pars[pit].setChange(pos, Change(Change::UNCHANGED));
379 if (pit == pars_size - 1) {
380 // we cannot remove a par break at the end of the last
381 // paragraph; instead, we mark it unchanged
382 pars[pit].setChange(pos, Change(Change::UNCHANGED));
384 mergeParagraph(bparams, pars, pit);
394 void acceptChanges(ParagraphList & pars, BufferParams const & bparams)
396 acceptOrRejectChanges(pars, bparams, Text::ACCEPT);
400 void rejectChanges(ParagraphList & pars, BufferParams const & bparams)
402 acceptOrRejectChanges(pars, bparams, Text::REJECT);
406 InsetText const & Text::inset() const
413 void Text::readParToken(Paragraph & par, Lexer & lex,
414 string const & token, Font & font, Change & change, ErrorList & errorList)
416 Buffer * buf = &owner_->buffer();
417 BufferParams & bp = buf->params();
419 if (token[0] != '\\') {
420 docstring dstr = lex.getDocString();
421 par.appendString(dstr, font, change);
423 } else if (token == "\\begin_layout") {
425 docstring layoutname = lex.getDocString();
427 font = Font(inherit_font, bp.language);
428 change = Change(Change::UNCHANGED);
430 DocumentClass const & tclass = bp.documentClass();
432 if (layoutname.empty())
433 layoutname = tclass.defaultLayoutName();
435 if (owner_->forcePlainLayout()) {
436 // in this case only the empty layout is allowed
437 layoutname = tclass.plainLayoutName();
438 } else if (par.usePlainLayout()) {
439 // in this case, default layout maps to empty layout
440 if (layoutname == tclass.defaultLayoutName())
441 layoutname = tclass.plainLayoutName();
443 // otherwise, the empty layout maps to the default
444 if (layoutname == tclass.plainLayoutName())
445 layoutname = tclass.defaultLayoutName();
448 // When we apply an unknown layout to a document, we add this layout to the textclass
449 // of this document. For example, when you apply class article to a beamer document,
450 // all unknown layouts such as frame will be added to document class article so that
451 // these layouts can keep their original names.
452 bool const added_one = tclass.addLayoutIfNeeded(layoutname);
455 docstring const s = bformat(_("Layout `%1$s' was not found."), layoutname);
456 errorList.push_back(ErrorItem(_("Layout Not Found"), s,
457 {par.id(), 0}, {par.id(), -1}));
460 par.setLayout(bp.documentClass()[layoutname]);
462 // Test whether the layout is obsolete.
463 Layout const & layout = par.layout();
464 if (!layout.obsoleted_by().empty())
465 par.setLayout(bp.documentClass()[layout.obsoleted_by()]);
467 par.params().read(lex);
469 } else if (token == "\\end_layout") {
470 LYXERR0("Solitary \\end_layout in line " << lex.lineNumber() << "\n"
471 << "Missing \\begin_layout ?");
472 } else if (token == "\\end_inset") {
473 LYXERR0("Solitary \\end_inset in line " << lex.lineNumber() << "\n"
474 << "Missing \\begin_inset ?");
475 } else if (token == "\\begin_inset") {
476 Inset * inset = readInset(lex, buf);
478 par.insertInset(par.size(), inset, font, change);
481 docstring line = lex.getDocString();
482 errorList.push_back(ErrorItem(_("Unknown Inset"), line,
483 {par.id(), 0}, {par.id(), -1}));
485 } else if (token == "\\family") {
487 setLyXFamily(lex.getString(), font.fontInfo());
488 } else if (token == "\\series") {
490 setLyXSeries(lex.getString(), font.fontInfo());
491 } else if (token == "\\shape") {
493 setLyXShape(lex.getString(), font.fontInfo());
494 } else if (token == "\\size") {
496 setLyXSize(lex.getString(), font.fontInfo());
497 } else if (token == "\\lang") {
499 string const tok = lex.getString();
500 Language const * lang = languages.getLanguage(tok);
502 font.setLanguage(lang);
504 font.setLanguage(bp.language);
505 lex.printError("Unknown language `$$Token'");
507 } else if (token == "\\numeric") {
509 font.fontInfo().setNumber(setLyXMisc(lex.getString()));
510 } else if (token == "\\nospellcheck") {
512 font.fontInfo().setNoSpellcheck(setLyXMisc(lex.getString()));
513 } else if (token == "\\emph") {
515 font.fontInfo().setEmph(setLyXMisc(lex.getString()));
516 } else if (token == "\\bar") {
518 string const tok = lex.getString();
521 font.fontInfo().setUnderbar(FONT_ON);
522 else if (tok == "no")
523 font.fontInfo().setUnderbar(FONT_OFF);
524 else if (tok == "default")
525 font.fontInfo().setUnderbar(FONT_INHERIT);
527 lex.printError("Unknown bar font flag "
529 } else if (token == "\\strikeout") {
531 font.fontInfo().setStrikeout(setLyXMisc(lex.getString()));
532 } else if (token == "\\xout") {
534 font.fontInfo().setXout(setLyXMisc(lex.getString()));
535 } else if (token == "\\uuline") {
537 font.fontInfo().setUuline(setLyXMisc(lex.getString()));
538 } else if (token == "\\uwave") {
540 font.fontInfo().setUwave(setLyXMisc(lex.getString()));
541 } else if (token == "\\noun") {
543 font.fontInfo().setNoun(setLyXMisc(lex.getString()));
544 } else if (token == "\\color") {
546 setLyXColor(lex.getString(), font.fontInfo());
547 } else if (token == "\\SpecialChar" ||
548 (token == "\\SpecialCharNoPassThru" &&
549 !par.layout().pass_thru && !inset().isPassThru())) {
550 auto inset = make_unique<InsetSpecialChar>();
552 inset->setBuffer(*buf);
553 par.insertInset(par.size(), inset.release(), font, change);
554 } else if (token == "\\SpecialCharNoPassThru") {
556 docstring const s = ltrim(lex.getDocString(), "\\");
557 par.insert(par.size(), s, font, change);
558 } else if (token == "\\IPAChar") {
559 auto inset = make_unique<InsetIPAChar>();
561 inset->setBuffer(*buf);
562 par.insertInset(par.size(), inset.release(), font, change);
563 } else if (token == "\\twohyphens" || token == "\\threehyphens") {
564 // Ideally, this should be done by lyx2lyx, but lyx2lyx does not know the
565 // running font and does not know anything about layouts (and CopyStyle).
566 Layout const & layout(par.layout());
567 FontInfo info = font.fontInfo();
568 info.realize(layout.resfont);
569 if (layout.pass_thru || inset().isPassThru() ||
570 info.family() == TYPEWRITER_FAMILY) {
571 if (token == "\\twohyphens")
572 par.insert(par.size(), from_ascii("--"), font, change);
574 par.insert(par.size(), from_ascii("---"), font, change);
576 if (token == "\\twohyphens")
577 par.insertChar(par.size(), 0x2013, font, change);
579 par.insertChar(par.size(), 0x2014, font, change);
581 } else if (token == "\\backslash") {
582 par.appendChar('\\', font, change);
583 } else if (token == "\\LyXTable") {
584 auto inset = make_unique<InsetTabular>(buf);
586 par.insertInset(par.size(), inset.release(), font, change);
587 } else if (token == "\\change_unchanged") {
588 change = Change(Change::UNCHANGED);
589 } else if (token == "\\change_inserted" || token == "\\change_deleted") {
591 istringstream is(lex.getString());
595 BufferParams::AuthorMap const & am = bp.author_map_;
596 if (am.find(aid) == am.end()) {
597 errorList.push_back(ErrorItem(
598 _("Change tracking author index missing"),
599 bformat(_("A change tracking author information for index "
600 "%1$d is missing. This can happen after a wrong "
601 "merge by a version control system. In this case, "
602 "either fix the merge, or have this information "
603 "missing until the corresponding tracked changes "
604 "are merged or this user edits the file again.\n"),
606 {par.id(), par.size()}, {par.id(), par.size() + 1}));
607 bp.addAuthor(Author(aid));
609 if (token == "\\change_inserted")
610 change = Change(Change::INSERTED, am.find(aid)->second, ct);
612 change = Change(Change::DELETED, am.find(aid)->second, ct);
615 errorList.push_back(ErrorItem(_("Unknown token"),
616 bformat(_("Unknown token: %1$s %2$s\n"),
619 {par.id(), 0}, {par.id(), -1}));
624 void Text::readParagraph(Paragraph & par, Lexer & lex,
625 ErrorList & errorList)
628 string token = lex.getString();
630 Change change(Change::UNCHANGED);
633 readParToken(par, lex, token, font, change, errorList);
636 token = lex.getString();
641 if (token == "\\end_layout") {
642 //Ok, paragraph finished
646 LYXERR(Debug::PARSER, "Handling paragraph token: `" << token << '\'');
647 if (token == "\\begin_layout" || token == "\\end_document"
648 || token == "\\end_inset" || token == "\\begin_deeper"
649 || token == "\\end_deeper") {
650 lex.pushToken(token);
651 lyxerr << "Paragraph ended in line "
652 << lex.lineNumber() << "\n"
653 << "Missing \\end_layout.\n";
657 // Final change goes to paragraph break:
658 if (inset().allowMultiPar())
659 par.setChange(par.size(), change);
661 // Initialize begin_of_body_ on load; redoParagraph maintains
662 par.setBeginOfBody();
664 // mark paragraph for spell checking on load
665 // par.requestSpellCheck();
669 class TextCompletionList : public CompletionList
673 TextCompletionList(Cursor const & cur, WordList const & list)
674 : buffer_(cur.buffer()), list_(list)
677 virtual ~TextCompletionList() {}
680 bool sorted() const override { return true; }
682 size_t size() const override
687 docstring const & data(size_t idx) const override
689 return list_.word(idx);
694 Buffer const * buffer_;
696 WordList const & list_;
700 bool Text::empty() const
702 return pars_.empty() || (pars_.size() == 1 && pars_[0].empty()
703 // FIXME: Should we consider the labeled type as empty too?
704 && pars_[0].layout().labeltype == LABEL_NO_LABEL);
708 double Text::spacing(Paragraph const & par) const
710 if (par.params().spacing().isDefault())
711 return owner_->buffer().params().spacing().getValue();
712 return par.params().spacing().getValue();
717 * This breaks a paragraph at the specified position.
718 * The new paragraph will:
719 * - change layout to default layout when keep_layout == false
720 * - keep layout when keep_layout == true
722 static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
725 BufferParams const & bparams = text.inset().buffer().params();
726 ParagraphList & pars = text.paragraphs();
727 // create a new paragraph, and insert into the list
728 ParagraphList::iterator tmp =
729 pars.insert(pars.iterator_at(par_offset + 1), Paragraph());
731 Paragraph & par = pars[par_offset];
733 // remember to set the inset_owner
734 tmp->setInsetOwner(&par.inInset());
735 tmp->params().depth(par.params().depth());
738 tmp->setLayout(par.layout());
739 tmp->setLabelWidthString(par.params().labelWidthString());
741 tmp->setPlainOrDefaultLayout(bparams.documentClass());
743 bool const isempty = (par.allowEmpty() && par.empty());
745 if (!isempty && (par.size() > pos || par.empty())) {
746 tmp->setLayout(par.layout());
747 tmp->params().align(par.params().align());
748 tmp->setLabelWidthString(par.params().labelWidthString());
750 tmp->params().depth(par.params().depth());
751 tmp->params().noindent(par.params().noindent());
752 tmp->params().spacing(par.params().spacing());
754 // move everything behind the break position
755 // to the new paragraph
757 /* Note: if !keepempty, empty() == true, then we reach
758 * here with size() == 0. So pos_end becomes - 1. This
759 * doesn't cause problems because both loops below
760 * enforce pos <= pos_end and 0 <= pos
762 pos_type pos_end = par.size() - 1;
764 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
765 if (moveItem(par, pos, *tmp, j, bparams)) {
771 // Move over the end-of-par change information
772 tmp->setChange(tmp->size(), par.lookupChange(par.size()));
773 par.setChange(par.size(), Change(bparams.track_changes ?
774 Change::INSERTED : Change::UNCHANGED));
777 // Make sure that we keep the language when
778 // breaking paragraph.
780 Font changed = tmp->getFirstFontSettings(bparams);
781 Font const & old = par.getFontSettings(bparams, par.size());
782 changed.setLanguage(old.language());
783 tmp->setFont(0, changed);
790 bool const soa = par.params().startOfAppendix();
791 par.params().clear();
792 // do not lose start of appendix marker (bug 4212)
793 par.params().startOfAppendix(soa);
794 par.setPlainOrDefaultLayout(bparams.documentClass());
798 par.setLayout(tmp->layout());
799 par.setLabelWidthString(tmp->params().labelWidthString());
800 par.params().depth(tmp->params().depth());
805 void Text::breakParagraph(Cursor & cur, bool inverse_logic)
807 LBUFERR(this == cur.text());
809 Paragraph & cpar = cur.paragraph();
810 pit_type cpit = cur.pit();
812 DocumentClass const & tclass = cur.buffer()->params().documentClass();
813 Layout const & layout = cpar.layout();
815 if (cur.lastpos() == 0 && !cpar.allowEmpty()) {
816 if (changeDepthAllowed(cur, DEC_DEPTH)) {
817 changeDepth(cur, DEC_DEPTH);
818 pit_type const prev = depthHook(cpit, cpar.getDepth());
819 docstring const & lay = pars_[prev].layout().name();
820 if (lay != layout.name())
823 docstring const & lay = cur.paragraph().usePlainLayout()
824 ? tclass.plainLayoutName() : tclass.defaultLayoutName();
825 if (lay != layout.name())
833 // Always break behind a space
834 // It is better to erase the space (Dekel)
835 if (cur.pos() != cur.lastpos() && cpar.isLineSeparator(cur.pos()))
836 cpar.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
838 // What should the layout for the new paragraph be?
839 bool keep_layout = layout.isEnvironment()
840 || (layout.isParagraph() && layout.parbreak_is_newline);
842 keep_layout = !keep_layout;
844 // We need to remember this before we break the paragraph, because
845 // that invalidates the layout variable
846 bool sensitive = layout.labeltype == LABEL_SENSITIVE;
848 // we need to set this before we insert the paragraph.
849 bool const isempty = cpar.allowEmpty() && cpar.empty();
851 lyx::breakParagraph(*this, cpit, cur.pos(), keep_layout);
853 // After this, neither paragraph contains any rows!
856 pit_type next_par = cpit + 1;
858 // well this is the caption hack since one caption is really enough
861 // set to standard-layout
862 //FIXME Check if this should be plainLayout() in some cases
863 pars_[cpit].applyLayout(tclass.defaultLayout());
865 // set to standard-layout
866 //FIXME Check if this should be plainLayout() in some cases
867 pars_[next_par].applyLayout(tclass.defaultLayout());
870 while (!pars_[next_par].empty() && pars_[next_par].isNewline(0)) {
871 if (!pars_[next_par].eraseChar(0, cur.buffer()->params().track_changes))
872 break; // the character couldn't be deleted physically due to change tracking
875 // A singlePar update is not enough in this case.
876 cur.screenUpdateFlags(Update::Force);
877 cur.forceBufferUpdate();
879 // This check is necessary. Otherwise the new empty paragraph will
880 // be deleted automatically. And it is more friendly for the user!
881 if (cur.pos() != 0 || isempty)
882 setCursor(cur, cur.pit() + 1, 0);
884 setCursor(cur, cur.pit(), 0);
888 // needed to insert the selection
889 void Text::insertStringAsLines(Cursor & cur, docstring const & str,
892 BufferParams const & bparams = owner_->buffer().params();
893 pit_type pit = cur.pit();
894 pos_type pos = cur.pos();
896 // The special chars we handle
897 static map<wchar_t, InsetSpecialChar::Kind> specialchars = {
898 { 0x200c, InsetSpecialChar::LIGATURE_BREAK },
899 { 0x200b, InsetSpecialChar::ALLOWBREAK },
900 { 0x2026, InsetSpecialChar::LDOTS },
901 { 0x2011, InsetSpecialChar::NOBREAKDASH }
904 // insert the string, don't insert doublespace
905 bool space_inserted = true;
906 for (auto const & ch : str) {
907 Paragraph & par = pars_[pit];
909 if (inset().allowMultiPar() && (!par.empty() || par.allowEmpty())) {
910 lyx::breakParagraph(*this, pit, pos,
911 par.layout().isEnvironment());
914 space_inserted = true;
918 // do not insert consecutive spaces if !free_spacing
919 } else if ((ch == ' ' || ch == '\t') &&
920 space_inserted && !par.isFreeSpacing()) {
922 } else if (ch == '\t') {
923 if (!par.isFreeSpacing()) {
924 // tabs are like spaces here
925 par.insertChar(pos, ' ', font, bparams.track_changes);
927 space_inserted = true;
929 par.insertChar(pos, ch, font, bparams.track_changes);
931 space_inserted = true;
933 } else if (specialchars.find(ch) != specialchars.end()
934 && (par.insertInset(pos, new InsetSpecialChar(specialchars.find(ch)->second),
935 font, bparams.track_changes
936 ? Change(Change::INSERTED)
937 : Change(Change::UNCHANGED)))) {
939 space_inserted = false;
940 } else if (!isPrintable(ch)) {
941 // Ignore (other) unprintables
944 // just insert the character
945 par.insertChar(pos, ch, font, bparams.track_changes);
947 space_inserted = (ch == ' ');
950 setCursor(cur, pit, pos);
954 // turn double CR to single CR, others are converted into one
955 // blank. Then insertStringAsLines is called
956 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str,
959 docstring linestr = str;
960 bool newline_inserted = false;
962 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
963 if (linestr[i] == '\n') {
964 if (newline_inserted) {
965 // we know that \r will be ignored by
966 // insertStringAsLines. Of course, it is a dirty
967 // trick, but it works...
968 linestr[i - 1] = '\r';
972 newline_inserted = true;
974 } else if (isPrintable(linestr[i])) {
975 newline_inserted = false;
978 insertStringAsLines(cur, linestr, font);
984 bool canInsertChar(Cursor const & cur, char_type c)
986 Paragraph const & par = cur.paragraph();
987 // If not in free spacing mode, check if there will be two blanks together or a blank at
988 // the beginning of a paragraph.
989 if (!par.isFreeSpacing() && isLineSeparatorChar(c)) {
990 if (cur.pos() == 0) {
992 "You cannot insert a space at the "
993 "beginning of a paragraph. Please read the Tutorial."));
996 // If something is wrong, ignore this character.
997 LASSERT(cur.pos() > 0, return false);
998 if ((par.isLineSeparator(cur.pos() - 1) || par.isNewline(cur.pos() - 1))
999 && !par.isDeleted(cur.pos() - 1)) {
1001 "You cannot type two spaces this way. "
1002 "Please read the Tutorial."));
1007 // Prevent to insert uncodable characters in verbatim and ERT.
1008 // The encoding is inherited from the context here.
1009 if (par.isPassThru() && cur.getEncoding()) {
1010 Encoding const * e = cur.getEncoding();
1011 if (!e->encodable(c)) {
1012 cur.message(_("Character is uncodable in this verbatim context."));
1022 // insert a character, moves all the following breaks in the
1023 // same Paragraph one to the right and make a rebreak
1024 void Text::insertChar(Cursor & cur, char_type c)
1026 LBUFERR(this == cur.text());
1028 if (!canInsertChar(cur,c))
1031 cur.recordUndo(INSERT_UNDO);
1033 TextMetrics const & tm = cur.bv().textMetrics(this);
1034 Buffer const & buffer = *cur.buffer();
1035 Paragraph & par = cur.paragraph();
1036 // try to remove this
1037 pit_type const pit = cur.pit();
1039 if (lyxrc.auto_number) {
1040 static docstring const number_operators = from_ascii("+-/*");
1041 static docstring const number_unary_operators = from_ascii("+-");
1043 // Common Number Separators: comma, dot etc.
1044 // European Number Terminators: percent, permille, degree, euro etc.
1045 if (cur.current_font.fontInfo().number() == FONT_ON) {
1046 if (!isDigitASCII(c) && !contains(number_operators, c) &&
1047 !(isCommonNumberSeparator(c) &&
1049 cur.pos() != cur.lastpos() &&
1050 tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1051 tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON) &&
1052 !(isEuropeanNumberTerminator(c) &&
1054 tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1055 tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON)
1057 number(cur); // Set current_font.number to OFF
1058 } else if (isDigitASCII(c) &&
1059 cur.real_current_font.isVisibleRightToLeft()) {
1060 number(cur); // Set current_font.number to ON
1062 if (cur.pos() != 0) {
1063 char_type const ch = par.getChar(cur.pos() - 1);
1064 if (contains(number_unary_operators, ch) &&
1066 || par.isSeparator(cur.pos() - 2)
1067 || par.isEnvSeparator(cur.pos() - 2)
1068 || par.isNewline(cur.pos() - 2))
1070 setCharFont(pit, cur.pos() - 1, cur.current_font,
1072 } else if (isCommonNumberSeparator(ch)
1074 && tm.displayFont(pit, cur.pos() - 2).fontInfo().number() == FONT_ON) {
1075 setCharFont(pit, cur.pos() - 1, cur.current_font,
1082 // In Bidi text, we want spaces to be treated in a special way: spaces
1083 // which are between words in different languages should get the
1084 // paragraph's language; otherwise, spaces should keep the language
1085 // they were originally typed in. This is only in effect while typing;
1086 // after the text is already typed in, the user can always go back and
1087 // explicitly set the language of a space as desired. But 99.9% of the
1088 // time, what we're doing here is what the user actually meant.
1090 // The following cases are the ones in which the language of the space
1091 // should be changed to match that of the containing paragraph. In the
1092 // depictions, lowercase is LTR, uppercase is RTL, underscore (_)
1093 // represents a space, pipe (|) represents the cursor position (so the
1094 // character before it is the one just typed in). The different cases
1095 // are depicted logically (not visually), from left to right:
1100 // Theoretically, there are other situations that we should, perhaps, deal
1101 // with (e.g.: a|_A, A|_a). In practice, though, there really isn't any
1102 // point (to understand why, just try to create this situation...).
1104 if ((cur.pos() >= 2) && (par.isLineSeparator(cur.pos() - 1))) {
1105 // get font in front and behind the space in question. But do NOT
1106 // use getFont(cur.pos()) because the character c is not inserted yet
1107 Font const pre_space_font = tm.displayFont(cur.pit(), cur.pos() - 2);
1108 Font const & post_space_font = cur.real_current_font;
1109 bool pre_space_rtl = pre_space_font.isVisibleRightToLeft();
1110 bool post_space_rtl = post_space_font.isVisibleRightToLeft();
1112 if (pre_space_rtl != post_space_rtl) {
1113 // Set the space's language to match the language of the
1114 // adjacent character whose direction is the paragraph's
1115 // direction; don't touch other properties of the font
1116 Language const * lang =
1117 (pre_space_rtl == par.isRTL(buffer.params())) ?
1118 pre_space_font.language() : post_space_font.language();
1120 Font space_font = tm.displayFont(cur.pit(), cur.pos() - 1);
1121 space_font.setLanguage(lang);
1122 par.setFont(cur.pos() - 1, space_font);
1126 pos_type pos = cur.pos();
1127 if (!cur.paragraph().isPassThru() && owner_->lyxCode() != IPA_CODE &&
1128 cur.real_current_font.fontInfo().family() != TYPEWRITER_FAMILY &&
1129 c == '-' && pos > 0) {
1130 if (par.getChar(pos - 1) == '-') {
1131 // convert "--" to endash
1132 par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1135 } else if (par.getChar(pos - 1) == 0x2013) {
1136 // convert "---" to emdash
1137 par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1143 par.insertChar(pos, c, cur.current_font,
1144 cur.buffer()->params().track_changes);
1145 cur.checkBufferStructure();
1147 // cur.screenUpdateFlags(Update::Force);
1148 bool boundary = cur.boundary()
1149 || tm.isRTLBoundary(cur.pit(), pos + 1);
1150 setCursor(cur, cur.pit(), pos + 1, false, boundary);
1155 void Text::charInserted(Cursor & cur)
1157 Paragraph & par = cur.paragraph();
1159 // register word if a non-letter was entered
1161 && !par.isWordSeparator(cur.pos() - 2)
1162 && par.isWordSeparator(cur.pos() - 1)) {
1163 // get the word in front of cursor
1164 LBUFERR(this == cur.text());
1170 // the cursor set functions have a special mechanism. When they
1171 // realize, that you left an empty paragraph, they will delete it.
1173 bool Text::cursorForwardOneWord(Cursor & cur)
1175 LBUFERR(this == cur.text());
1177 if (lyxrc.mac_like_cursor_movement) {
1178 DocIterator dit(cur);
1179 DocIterator prv(cur);
1180 bool inword = false;
1181 bool intext = dit.inTexted();
1182 while (!dit.atEnd()) {
1183 if (dit.inTexted()) { // no paragraphs in mathed
1184 Paragraph const & par = dit.paragraph();
1185 pos_type const pos = dit.pos();
1187 if (!par.isDeleted(pos)) {
1188 bool wordsep = par.isWordSeparator(pos);
1189 if (inword && wordsep)
1190 break; // stop at word end
1191 else if (!inword && !wordsep)
1195 } else if (intext) {
1196 // move to end of math
1197 while (!dit.inTexted() && !dit.atEnd()) dit.forwardPos();
1201 dit.forwardPosIgnoreCollapsed();
1203 if (dit.atEnd()) dit = prv;
1204 if (dit == cur) return false; // we didn't move
1207 // see comment above
1208 cur.bv().checkDepm(cur, orig);
1211 pos_type const lastpos = cur.lastpos();
1212 pit_type pit = cur.pit();
1213 pos_type pos = cur.pos();
1214 Paragraph const & par = cur.paragraph();
1216 // Paragraph boundary is a word boundary
1217 if (pos == lastpos || (pos + 1 == lastpos && par.isEnvSeparator(pos))) {
1218 if (pit != cur.lastpit())
1219 return setCursor(cur, pit + 1, 0);
1224 LASSERT(pos < lastpos, return false); // see above
1225 if (!par.isWordSeparator(pos))
1226 while (pos != lastpos && !par.isWordSeparator(pos))
1228 else if (par.isChar(pos))
1229 while (pos != lastpos && par.isChar(pos))
1231 else if (!par.isSpace(pos)) // non-char inset
1234 // Skip over white space
1235 while (pos != lastpos && par.isSpace(pos))
1238 // Don't skip a separator inset at the end of a paragraph
1239 if (pos == lastpos && pos && par.isEnvSeparator(pos - 1))
1242 return setCursor(cur, pit, pos);
1247 bool Text::cursorBackwardOneWord(Cursor & cur)
1249 LBUFERR(this == cur.text());
1251 if (lyxrc.mac_like_cursor_movement) {
1252 DocIterator dit(cur);
1253 bool inword = false;
1254 bool intext = dit.inTexted();
1255 while (!dit.atBegin()) {
1256 DocIterator prv(dit);
1257 dit.backwardPosIgnoreCollapsed();
1258 if (dit.inTexted()) { // no paragraphs in mathed
1259 Paragraph const & par = dit.paragraph();
1260 pos_type pos = dit.pos();
1262 if (!par.isDeleted(pos)) {
1263 bool wordsep = par.isWordSeparator(pos);
1264 if (inword && wordsep) {
1266 break; // stop at word begin
1267 } else if (!inword && !wordsep)
1271 } else if (intext) {
1272 // move to begin of math
1273 while (!dit.inTexted() && !dit.atBegin()) dit.backwardPos();
1277 if (dit == cur) return false; // we didn't move
1280 // see comment above cursorForwardOneWord
1281 cur.bv().checkDepm(cur, orig);
1284 Paragraph const & par = cur.paragraph();
1285 pit_type const pit = cur.pit();
1286 pos_type pos = cur.pos();
1288 // Paragraph boundary is a word boundary
1289 if (pos == 0 && pit != 0) {
1290 Paragraph & prevpar = getPar(pit - 1);
1291 pos = prevpar.size();
1292 // Don't stop after an environment separator
1293 if (pos && prevpar.isEnvSeparator(pos - 1))
1295 return setCursor(cur, pit - 1, pos);
1297 // Skip over white space
1298 while (pos != 0 && par.isSpace(pos - 1))
1301 if (pos != 0 && !par.isWordSeparator(pos - 1))
1302 while (pos != 0 && !par.isWordSeparator(pos - 1))
1304 else if (pos != 0 && par.isChar(pos - 1))
1305 while (pos != 0 && par.isChar(pos - 1))
1307 else if (pos != 0 && !par.isSpace(pos - 1)) // non-char inset
1310 return setCursor(cur, pit, pos);
1315 bool Text::cursorVisLeftOneWord(Cursor & cur)
1317 LBUFERR(this == cur.text());
1319 pos_type left_pos, right_pos;
1321 Cursor temp_cur = cur;
1323 // always try to move at least once...
1324 while (temp_cur.posVisLeft(true /* skip_inset */)) {
1326 // collect some information about current cursor position
1327 temp_cur.getSurroundingPos(left_pos, right_pos);
1328 bool left_is_letter =
1329 (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1330 bool right_is_letter =
1331 (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1333 // if we're not at a letter/non-letter boundary, continue moving
1334 if (left_is_letter == right_is_letter)
1337 // we should stop when we have an LTR word on our right or an RTL word
1339 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1340 temp_cur.buffer()->params(), left_pos).isRightToLeft())
1341 || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1342 temp_cur.buffer()->params(), right_pos).isRightToLeft()))
1346 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1347 true, temp_cur.boundary());
1351 bool Text::cursorVisRightOneWord(Cursor & cur)
1353 LBUFERR(this == cur.text());
1355 pos_type left_pos, right_pos;
1357 Cursor temp_cur = cur;
1359 // always try to move at least once...
1360 while (temp_cur.posVisRight(true /* skip_inset */)) {
1362 // collect some information about current cursor position
1363 temp_cur.getSurroundingPos(left_pos, right_pos);
1364 bool left_is_letter =
1365 (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1366 bool right_is_letter =
1367 (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1369 // if we're not at a letter/non-letter boundary, continue moving
1370 if (left_is_letter == right_is_letter)
1373 // we should stop when we have an LTR word on our right or an RTL word
1375 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1376 temp_cur.buffer()->params(),
1377 left_pos).isRightToLeft())
1378 || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1379 temp_cur.buffer()->params(),
1380 right_pos).isRightToLeft()))
1384 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1385 true, temp_cur.boundary());
1389 void Text::selectWord(Cursor & cur, word_location loc)
1391 LBUFERR(this == cur.text());
1392 CursorSlice from = cur.top();
1394 getWord(from, to, loc);
1395 if (cur.top() != from)
1396 setCursor(cur, from.pit(), from.pos());
1399 if (!cur.selection())
1401 setCursor(cur, to.pit(), to.pos());
1403 cur.setWordSelection(true);
1407 void Text::expandWordSel(Cursor & cur)
1409 // get selection of word around cur
1412 c.text()->selectWord(c, WHOLE_WORD);
1413 // use the correct word boundary, depending on selection direction
1414 if (cur.top() > cur.normalAnchor())
1415 cur.pos() = c.selEnd().pos();
1417 cur.pos() = c.selBegin().pos();
1421 void Text::selectAll(Cursor & cur)
1423 LBUFERR(this == cur.text());
1424 if (cur.lastpos() == 0 && cur.lastpit() == 0)
1426 // If the cursor is at the beginning, make sure the cursor ends there
1427 if (cur.pit() == 0 && cur.pos() == 0) {
1428 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1430 setCursor(cur, 0, 0);
1432 setCursor(cur, 0, 0);
1434 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1440 // Select the word currently under the cursor when no
1441 // selection is currently set
1442 bool Text::selectWordWhenUnderCursor(Cursor & cur, word_location loc)
1444 LBUFERR(this == cur.text());
1445 if (cur.selection())
1447 selectWord(cur, loc);
1448 return cur.selection();
1452 void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
1454 LBUFERR(this == cur.text());
1456 if (!cur.selection()) {
1457 if (!selectChange(cur))
1461 cur.recordUndoSelection();
1463 pit_type begPit = cur.selectionBegin().pit();
1464 pit_type endPit = cur.selectionEnd().pit();
1466 pos_type begPos = cur.selectionBegin().pos();
1467 pos_type endPos = cur.selectionEnd().pos();
1469 // keep selection info, because endPos becomes invalid after the first loop
1470 bool const endsBeforeEndOfPar = (endPos < pars_[endPit].size());
1472 // first, accept/reject changes within each individual paragraph (do not consider end-of-par)
1473 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1474 pos_type parSize = pars_[pit].size();
1476 // ignore empty paragraphs; otherwise, an assertion will fail for
1477 // acceptChanges(bparams, 0, 0) or rejectChanges(bparams, 0, 0)
1481 // do not consider first paragraph if the cursor starts at pos size()
1482 if (pit == begPit && begPos == parSize)
1485 // do not consider last paragraph if the cursor ends at pos 0
1486 if (pit == endPit && endPos == 0)
1487 break; // last iteration anyway
1489 pos_type const left = (pit == begPit ? begPos : 0);
1490 pos_type const right = (pit == endPit ? endPos : parSize);
1493 // there is no change here
1497 pars_[pit].acceptChanges(left, right);
1499 pars_[pit].rejectChanges(left, right);
1503 // next, accept/reject imaginary end-of-par characters
1505 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1506 pos_type pos = pars_[pit].size();
1508 // skip if the selection ends before the end-of-par
1509 if (pit == endPit && endsBeforeEndOfPar)
1510 break; // last iteration anyway
1512 // skip if this is not the last paragraph of the document
1513 // note: the user should be able to accept/reject the par break of the last par!
1514 if (pit == endPit && pit + 1 != int(pars_.size()))
1515 break; // last iteration anway
1518 if (pars_[pit].isInserted(pos)) {
1519 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1520 } else if (pars_[pit].isDeleted(pos)) {
1521 if (pit + 1 == int(pars_.size())) {
1522 // we cannot remove a par break at the end of the last paragraph;
1523 // instead, we mark it unchanged
1524 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1526 mergeParagraph(cur.buffer()->params(), pars_, pit);
1532 if (pars_[pit].isDeleted(pos)) {
1533 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1534 } else if (pars_[pit].isInserted(pos)) {
1535 if (pit + 1 == int(pars_.size())) {
1536 // we mark the par break at the end of the last paragraph unchanged
1537 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1539 mergeParagraph(cur.buffer()->params(), pars_, pit);
1547 // finally, invoke the DEPM
1548 deleteEmptyParagraphMechanism(begPit, endPit, begPos, endPos,
1549 cur.buffer()->params().track_changes);
1552 cur.clearSelection();
1553 setCursorIntern(cur, begPit, begPos);
1554 cur.screenUpdateFlags(Update::Force);
1555 cur.forceBufferUpdate();
1559 void Text::acceptChanges()
1561 BufferParams const & bparams = owner_->buffer().params();
1562 lyx::acceptChanges(pars_, bparams);
1563 deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.track_changes);
1567 void Text::rejectChanges()
1569 BufferParams const & bparams = owner_->buffer().params();
1570 pit_type pars_size = static_cast<pit_type>(pars_.size());
1572 // first, reject changes within each individual paragraph
1573 // (do not consider end-of-par)
1574 for (pit_type pit = 0; pit < pars_size; ++pit) {
1575 if (!pars_[pit].empty()) // prevent assertion failure
1576 pars_[pit].rejectChanges(0, pars_[pit].size());
1579 // next, reject imaginary end-of-par characters
1580 for (pit_type pit = 0; pit < pars_size; ++pit) {
1581 pos_type pos = pars_[pit].size();
1583 if (pars_[pit].isDeleted(pos)) {
1584 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1585 } else if (pars_[pit].isInserted(pos)) {
1586 if (pit == pars_size - 1) {
1587 // we mark the par break at the end of the last
1588 // paragraph unchanged
1589 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1591 mergeParagraph(bparams, pars_, pit);
1598 // finally, invoke the DEPM
1599 deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.track_changes);
1603 void Text::deleteWordForward(Cursor & cur, bool const force)
1605 LBUFERR(this == cur.text());
1606 if (cur.lastpos() == 0)
1610 cur.selection(true);
1611 cursorForwardOneWord(cur);
1613 if (force || !cur.confirmDeletion()) {
1614 cutSelection(cur, false);
1615 cur.checkBufferStructure();
1621 void Text::deleteWordBackward(Cursor & cur, bool const force)
1623 LBUFERR(this == cur.text());
1624 if (cur.lastpos() == 0)
1625 cursorBackward(cur);
1628 cur.selection(true);
1629 cursorBackwardOneWord(cur);
1631 if (force || !cur.confirmDeletion()) {
1632 cutSelection(cur, false);
1633 cur.checkBufferStructure();
1639 // Kill to end of line.
1640 void Text::changeCase(Cursor & cur, TextCase action, bool partial)
1642 LBUFERR(this == cur.text());
1646 bool const gotsel = cur.selection();
1648 from = cur.selBegin();
1652 getWord(from, to, partial ? PARTIAL_WORD : WHOLE_WORD);
1653 cursorForwardOneWord(cur);
1656 cur.recordUndoSelection();
1658 pit_type begPit = from.pit();
1659 pit_type endPit = to.pit();
1661 pos_type begPos = from.pos();
1662 pos_type endPos = to.pos();
1664 pos_type right = 0; // needed after the for loop
1666 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1667 Paragraph & par = pars_[pit];
1668 pos_type const pos = (pit == begPit ? begPos : 0);
1669 right = (pit == endPit ? endPos : par.size());
1670 par.changeCase(cur.buffer()->params(), pos, right, action);
1673 // the selection may have changed due to logically-only deleted chars
1675 setCursor(cur, begPit, begPos);
1677 setCursor(cur, endPit, right);
1680 setCursor(cur, endPit, right);
1682 cur.checkBufferStructure();
1686 bool Text::handleBibitems(Cursor & cur)
1688 if (cur.paragraph().layout().labeltype != LABEL_BIBLIO)
1694 BufferParams const & bufparams = cur.buffer()->params();
1695 Paragraph const & par = cur.paragraph();
1696 Cursor prevcur = cur;
1697 if (cur.pit() > 0) {
1699 prevcur.pos() = prevcur.lastpos();
1701 Paragraph const & prevpar = prevcur.paragraph();
1703 // if a bibitem is deleted, merge with previous paragraph
1704 // if this is a bibliography item as well
1705 if (cur.pit() > 0 && par.layout() == prevpar.layout()) {
1706 cur.recordUndo(prevcur.pit());
1707 mergeParagraph(bufparams, cur.text()->paragraphs(),
1709 cur.forceBufferUpdate();
1710 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1711 cur.screenUpdateFlags(Update::Force);
1715 // otherwise reset to default
1716 cur.paragraph().setPlainOrDefaultLayout(bufparams.documentClass());
1721 bool Text::erase(Cursor & cur)
1723 LASSERT(this == cur.text(), return false);
1724 bool needsUpdate = false;
1725 Paragraph & par = cur.paragraph();
1727 if (cur.pos() != cur.lastpos()) {
1728 // this is the code for a normal delete, not pasting
1730 cur.recordUndo(DELETE_UNDO);
1731 bool const was_inset = cur.paragraph().isInset(cur.pos());
1732 if(!par.eraseChar(cur.pos(), cur.buffer()->params().track_changes))
1733 // the character has been logically deleted only => skip it
1734 cur.top().forwardPos();
1737 cur.forceBufferUpdate();
1739 cur.checkBufferStructure();
1742 if (cur.pit() == cur.lastpit())
1743 return dissolveInset(cur);
1745 if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1746 cur.recordUndo(DELETE_UNDO);
1747 par.setChange(cur.pos(), Change(Change::DELETED));
1751 setCursorIntern(cur, cur.pit() + 1, 0);
1752 needsUpdate = backspacePos0(cur);
1756 needsUpdate |= handleBibitems(cur);
1759 // Make sure the cursor is correct. Is this really needed?
1760 // No, not really... at least not here!
1761 cur.top().setPitPos(cur.pit(), cur.pos());
1762 cur.checkBufferStructure();
1769 bool Text::backspacePos0(Cursor & cur)
1771 LBUFERR(this == cur.text());
1775 BufferParams const & bufparams = cur.buffer()->params();
1776 ParagraphList & plist = cur.text()->paragraphs();
1777 Paragraph const & par = cur.paragraph();
1778 Cursor prevcur = cur;
1780 prevcur.pos() = prevcur.lastpos();
1781 Paragraph const & prevpar = prevcur.paragraph();
1783 // is it an empty paragraph?
1784 if (cur.lastpos() == 0
1785 || (cur.lastpos() == 1 && par.isSeparator(0))) {
1786 cur.recordUndo(prevcur.pit());
1787 plist.erase(plist.iterator_at(cur.pit()));
1789 // is previous par empty?
1790 else if (prevcur.lastpos() == 0
1791 || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) {
1792 cur.recordUndo(prevcur.pit());
1793 plist.erase(plist.iterator_at(prevcur.pit()));
1795 // FIXME: Do we really not want to allow this???
1796 // Pasting is not allowed, if the paragraphs have different
1797 // layouts. I think it is a real bug of all other
1798 // word processors to allow it. It confuses the user.
1799 // Correction: Pasting is always allowed with standard-layout
1800 // or the empty layout.
1802 cur.recordUndo(prevcur.pit());
1803 mergeParagraph(bufparams, plist, prevcur.pit());
1806 cur.forceBufferUpdate();
1807 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1813 bool Text::backspace(Cursor & cur)
1815 LBUFERR(this == cur.text());
1816 bool needsUpdate = false;
1817 if (cur.pos() == 0) {
1819 return dissolveInset(cur);
1821 Cursor prev_cur = cur;
1824 if (!cur.paragraph().empty()
1825 && !prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1826 cur.recordUndo(prev_cur.pit(), prev_cur.pit());
1827 prev_cur.paragraph().setChange(prev_cur.lastpos(), Change(Change::DELETED));
1828 setCursorIntern(cur, prev_cur.pit(), prev_cur.lastpos());
1831 // The cursor is at the beginning of a paragraph, so
1832 // the backspace will collapse two paragraphs into one.
1833 needsUpdate = backspacePos0(cur);
1836 // this is the code for a normal backspace, not pasting
1838 cur.recordUndo(DELETE_UNDO);
1839 // We used to do cursorBackwardIntern() here, but it is
1840 // not a good idea since it triggers the auto-delete
1841 // mechanism. So we do a cursorBackwardIntern()-lite,
1842 // without the dreaded mechanism. (JMarc)
1843 setCursorIntern(cur, cur.pit(), cur.pos() - 1,
1844 false, cur.boundary());
1845 bool const was_inset = cur.paragraph().isInset(cur.pos());
1846 cur.paragraph().eraseChar(cur.pos(), cur.buffer()->params().track_changes);
1848 cur.forceBufferUpdate();
1850 cur.checkBufferStructure();
1853 if (cur.pos() == cur.lastpos())
1854 cur.setCurrentFont();
1856 needsUpdate |= handleBibitems(cur);
1858 // A singlePar update is not enough in this case.
1859 // cur.screenUpdateFlags(Update::Force);
1860 cur.top().setPitPos(cur.pit(), cur.pos());
1866 bool Text::dissolveInset(Cursor & cur)
1868 LASSERT(this == cur.text(), return false);
1870 if (isMainText() || cur.inset().nargs() != 1)
1873 cur.recordUndoInset();
1875 cur.selHandle(false);
1876 // save position inside inset
1877 pos_type spos = cur.pos();
1878 pit_type spit = cur.pit();
1879 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1881 // update cursor offset
1885 // remember position outside inset to delete inset later
1886 // we do not do it now to avoid memory reuse issues (see #10667).
1887 DocIterator inset_it = cur;
1891 Buffer & b = *cur.buffer();
1892 // Is there anything in this text?
1893 if (inset_non_empty) {
1895 // we clear the cache so that we won't get conflicts with labels
1896 // that get pasted into the buffer. we should update this before
1897 // its being empty matters. if not (i.e., if we encounter bugs),
1898 // then this should instead be:
1899 // cur.buffer().updateBuffer();
1900 // but we'll try the cheaper solution here.
1901 cur.buffer()->clearReferenceCache();
1903 ParagraphList & plist = paragraphs();
1904 if (!lyxrc.ct_markup_copied)
1905 // Do not revive deleted text
1906 lyx::acceptChanges(plist, b.params());
1908 // ERT paragraphs have the Language latex_language.
1909 // This is invalid outside of ERT, so we need to
1910 // change it to the buffer language.
1911 for (auto & p : plist)
1912 p.changeLanguage(b.params(), latex_language, b.language());
1914 /* If the inset is the only thing in paragraph and the layout
1915 * is not plain, then the layout of the first paragraph of
1916 * inset should be remembered.
1917 * FIXME: this does not work as expected when change tracking
1918 * is on However, we do not really know what to do in this
1921 DocumentClass const & tclass = cur.buffer()->params().documentClass();
1922 if (inset_it.lastpos() == 1
1923 && !tclass.isPlainLayout(plist[0].layout())
1924 && !tclass.isDefaultLayout(plist[0].layout())) {
1925 // Copy all parameters except depth.
1926 Paragraph & par = cur.paragraph();
1927 par.setLayout(plist[0].layout());
1928 depth_type const dpth = par.getDepth();
1929 par.params() = plist[0].params();
1930 par.params().depth(dpth);
1933 pasteParagraphList(cur, plist, b.params().documentClassPtr(),
1934 b.params().authors(),
1935 b.errorList("Paste"));
1938 // delete the inset now
1939 inset_it.paragraph().eraseChar(inset_it.pos(), b.params().track_changes);
1942 cur.pit() = min(cur.lastpit(), spit);
1943 cur.pos() = min(cur.lastpos(), spos);
1944 // Ensure the current language is set correctly (bug 6292)
1945 cur.text()->setCursor(cur, cur.pit(), cur.pos());
1946 cur.clearSelection();
1948 cur.forceBufferUpdate();
1954 bool Text::splitInset(Cursor & cur)
1956 LASSERT(this == cur.text(), return false);
1958 if (isMainText() || cur.inset().nargs() != 1)
1962 if (cur.selection()) {
1963 // start from selection begin
1964 setCursor(cur, cur.selBegin().pit(), cur.selBegin().pos());
1965 cur.clearSelection();
1967 // save split position inside inset
1968 // (we need to copy the whole inset first)
1969 pos_type spos = cur.pos();
1970 pit_type spit = cur.pit();
1971 // some things only need to be done if the inset has content
1972 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1974 // move right before the inset
1977 // remember position outside inset
1978 pos_type ipos = cur.pos();
1979 pit_type ipit = cur.pit();
1984 cap::copySelectionToTemp(cur);
1985 cur.clearSelection();
1987 // paste copied inset
1988 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
1989 cur.forceBufferUpdate();
1991 // if the inset has text, cut after split position
1992 // and paste to new inset
1993 if (inset_non_empty) {
1994 // go back to first inset
1995 cur.text()->setCursor(cur, ipit, ipos);
1997 setCursor(cur, spit, spos);
1999 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
2001 // Remember whether there was something cut that has to be pasted below
2003 bool const hasCut = cur.selection();
2004 cap::cutSelectionToTemp(cur);
2006 cur.selHandle(false);
2008 bool atlastpos = false;
2009 if (cur.pos() == 0 && cur.pit() > 0) {
2010 // if we are at par start, remove this par
2011 cur.text()->backspace(cur);
2012 cur.forceBufferUpdate();
2013 } else if (cur.pos() == cur.lastpos())
2015 // Move out of and jump over inset
2023 cur.text()->selectAll(cur);
2024 cutSelection(cur, false);
2025 // If there was something cut paste it
2027 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
2028 cur.text()->setCursor(cur, 0, 0);
2029 if (atlastpos && cur.paragraph().isFreeSpacing() && cur.paragraph().empty()) {
2030 // We started from par end, remove extra empty par in free spacing insets
2031 cur.text()->erase(cur);
2032 cur.forceBufferUpdate();
2041 void Text::getWord(CursorSlice & from, CursorSlice & to,
2042 word_location const loc) const
2045 pars_[to.pit()].locateWord(from.pos(), to.pos(), loc);
2049 void Text::write(ostream & os) const
2051 Buffer const & buf = owner_->buffer();
2052 ParagraphList::const_iterator pit = paragraphs().begin();
2053 ParagraphList::const_iterator end = paragraphs().end();
2055 for (; pit != end; ++pit)
2056 pit->write(os, buf.params(), dth);
2058 // Close begin_deeper
2059 for(; dth > 0; --dth)
2060 os << "\n\\end_deeper";
2064 bool Text::read(Lexer & lex,
2065 ErrorList & errorList, InsetText * insetPtr)
2067 Buffer const & buf = owner_->buffer();
2068 depth_type depth = 0;
2071 while (lex.isOK()) {
2073 string const token = lex.getString();
2078 if (token == "\\end_inset")
2081 if (token == "\\end_body")
2084 if (token == "\\begin_body")
2087 if (token == "\\end_document") {
2092 if (token == "\\begin_layout") {
2093 lex.pushToken(token);
2096 par.setInsetOwner(insetPtr);
2097 par.params().depth(depth);
2098 par.setFont(0, Font(inherit_font, buf.params().language));
2099 pars_.push_back(par);
2100 readParagraph(pars_.back(), lex, errorList);
2102 // register the words in the global word list
2103 pars_.back().updateWords();
2104 } else if (token == "\\begin_deeper") {
2106 } else if (token == "\\end_deeper") {
2108 lex.printError("\\end_deeper: " "depth is already null");
2112 LYXERR0("Handling unknown body token: `" << token << '\'');
2116 // avoid a crash on weird documents (bug 4859)
2117 if (pars_.empty()) {
2119 par.setInsetOwner(insetPtr);
2120 par.params().depth(depth);
2121 par.setFont(0, Font(inherit_font,
2122 buf.params().language));
2123 par.setPlainOrDefaultLayout(buf.params().documentClass());
2124 pars_.push_back(par);
2131 // Returns the current state (font, depth etc.) as a message for status bar.
2132 docstring Text::currentState(CursorData const & cur, bool devel_mode) const
2134 LBUFERR(this == cur.text());
2135 Buffer & buf = *cur.buffer();
2136 Paragraph const & par = cur.paragraph();
2137 odocstringstream os;
2139 if (buf.params().track_changes)
2140 os << _("[Change Tracking] ");
2142 Change change = par.lookupChange(cur.pos());
2144 if (change.changed()) {
2145 docstring const author =
2146 buf.params().authors().get(change.author).nameAndEmail();
2147 docstring const date = formatted_datetime(change.changetime);
2148 os << bformat(_("Changed by %1$s[[author]] on %2$s[[date]]. "),
2152 // I think we should only show changes from the default
2154 // No, from the document font (MV)
2155 Font font = cur.real_current_font;
2156 font.fontInfo().reduce(buf.params().getFont().fontInfo());
2158 os << bformat(_("Font: %1$s"), font.stateText(&buf.params()));
2160 // The paragraph depth
2161 int depth = par.getDepth();
2163 os << bformat(_(", Depth: %1$d"), depth);
2165 // The paragraph spacing, but only if different from
2167 Spacing const & spacing = par.params().spacing();
2168 if (!spacing.isDefault()) {
2169 os << _(", Spacing: ");
2170 switch (spacing.getSpace()) {
2171 case Spacing::Single:
2174 case Spacing::Onehalf:
2177 case Spacing::Double:
2180 case Spacing::Other:
2181 os << _("Other (") << from_ascii(spacing.getValueAsString()) << ')';
2183 case Spacing::Default:
2184 // should never happen, do nothing
2189 // Custom text style
2190 InsetLayout const & layout = cur.inset().getLayout();
2191 if (layout.lyxtype() == InsetLyXType::CHARSTYLE)
2192 os << _(", Style: ") << translateIfPossible(layout.labelstring());
2195 os << _(", Inset: ") << &cur.inset();
2196 if (cur.lastidx() > 0)
2197 os << _(", Cell: ") << cur.idx();
2198 os << _(", Paragraph: ") << cur.pit();
2199 os << _(", Id: ") << par.id();
2200 os << _(", Position: ") << cur.pos();
2201 // FIXME: Why is the check for par.size() needed?
2202 // We are called with cur.pos() == par.size() quite often.
2203 if (!par.empty() && cur.pos() < par.size()) {
2204 // Force output of code point, not character
2205 size_t const c = par.getChar(cur.pos());
2206 if (c == META_INSET)
2207 os << ", Char: INSET";
2209 os << _(", Char: 0x") << hex << c;
2211 os << _(", Boundary: ") << cur.boundary();
2212 // Row & row = cur.textRow();
2213 // os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos());
2219 docstring Text::getPossibleLabel(DocIterator const & cur) const
2221 pit_type textpit = cur.pit();
2222 Layout const * layout = &(pars_[textpit].layout());
2224 // Will contain the label prefix.
2227 // For captions, we just take the caption type
2228 Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE);
2229 if (caption_inset) {
2230 string const & ftype = static_cast<InsetCaption *>(caption_inset)->floattype();
2231 FloatList const & fl = cur.buffer()->params().documentClass().floats();
2232 if (fl.typeExist(ftype)) {
2233 Floating const & flt = fl.getType(ftype);
2234 name = from_utf8(flt.refPrefix());
2237 name = from_utf8(ftype.substr(0,3));
2239 // For section, subsection, etc...
2240 if (layout->latextype == LATEX_PARAGRAPH && textpit != 0) {
2241 Layout const * layout2 = &(pars_[textpit - 1].layout());
2242 if (layout2->latextype != LATEX_PARAGRAPH) {
2247 if (layout->latextype != LATEX_PARAGRAPH)
2248 name = layout->refprefix;
2250 // If none of the above worked, see if the inset knows.
2252 InsetLayout const & il = cur.inset().getLayout();
2253 name = il.refprefix();
2258 docstring par_text = pars_[textpit].asString(AS_STR_SKIPDELETE);
2260 // The return string of math matrices might contain linebreaks
2261 par_text = subst(par_text, '\n', '-');
2262 int const numwords = 3;
2263 for (int i = 0; i < numwords; ++i) {
2264 if (par_text.empty())
2267 par_text = split(par_text, head, ' ');
2268 // Is it legal to use spaces in labels ?
2274 // Make sure it isn't too long
2275 unsigned int const max_label_length = 32;
2276 if (text.size() > max_label_length)
2277 text.resize(max_label_length);
2280 text = name + ':' + text;
2282 // We need a unique label
2283 docstring label = text;
2285 while (cur.buffer()->activeLabel(label)) {
2286 label = text + '-' + convert<docstring>(i);
2294 docstring Text::asString(int options) const
2296 return asString(0, pars_.size(), options);
2300 docstring Text::asString(pit_type beg, pit_type end, int options) const
2302 size_t i = size_t(beg);
2303 docstring str = pars_[i].asString(options);
2304 for (++i; i != size_t(end); ++i) {
2306 str += pars_[i].asString(options);
2312 void Text::shortenForOutliner(docstring & str, size_t const maxlen)
2314 support::truncateWithEllipsis(str, maxlen);
2315 for (char_type & c : str)
2316 if (c == L'\n' || c == L'\t')
2321 void Text::forOutliner(docstring & os, size_t const maxlen,
2322 bool const shorten) const
2324 pit_type end = pars_.size() - 1;
2325 if (0 <= end && !pars_[0].labelString().empty())
2326 os += pars_[0].labelString() + ' ';
2327 forOutliner(os, maxlen, 0, end, shorten);
2331 void Text::forOutliner(docstring & os, size_t const maxlen,
2332 pit_type pit_start, pit_type pit_end,
2333 bool const shorten) const
2335 size_t tmplen = shorten ? maxlen + 1 : maxlen;
2336 pit_type end = min(size_t(pit_end), pars_.size() - 1);
2338 for (pit_type i = pit_start; i <= end && os.length() < tmplen; ++i) {
2341 // This function lets the first label be treated separately
2342 pars_[i].forOutliner(os, tmplen, false, !first);
2346 shortenForOutliner(os, maxlen);
2350 void Text::charsTranspose(Cursor & cur)
2352 LBUFERR(this == cur.text());
2354 pos_type pos = cur.pos();
2356 // If cursor is at beginning or end of paragraph, do nothing.
2357 if (pos == cur.lastpos() || pos == 0)
2360 Paragraph & par = cur.paragraph();
2362 // Get the positions of the characters to be transposed.
2363 pos_type pos1 = pos - 1;
2364 pos_type pos2 = pos;
2366 // In change tracking mode, ignore deleted characters.
2367 while (pos2 < cur.lastpos() && par.isDeleted(pos2))
2369 if (pos2 == cur.lastpos())
2372 while (pos1 >= 0 && par.isDeleted(pos1))
2377 // Don't do anything if one of the "characters" is not regular text.
2378 if (par.isInset(pos1) || par.isInset(pos2))
2381 // Store the characters to be transposed (including font information).
2382 char_type const char1 = par.getChar(pos1);
2384 par.getFontSettings(cur.buffer()->params(), pos1);
2386 char_type const char2 = par.getChar(pos2);
2388 par.getFontSettings(cur.buffer()->params(), pos2);
2390 // And finally, we are ready to perform the transposition.
2391 // Track the changes if Change Tracking is enabled.
2392 bool const trackChanges = cur.buffer()->params().track_changes;
2396 par.eraseChar(pos2, trackChanges);
2397 par.eraseChar(pos1, trackChanges);
2398 par.insertChar(pos1, char2, font2, trackChanges);
2399 par.insertChar(pos2, char1, font1, trackChanges);
2401 cur.checkBufferStructure();
2403 // After the transposition, move cursor to after the transposition.
2404 setCursor(cur, cur.pit(), pos2);
2409 DocIterator Text::macrocontextPosition() const
2411 return macrocontext_position_;
2415 void Text::setMacrocontextPosition(DocIterator const & pos)
2417 macrocontext_position_ = pos;
2421 bool Text::completionSupported(Cursor const & cur) const
2423 Paragraph const & par = cur.paragraph();
2424 return !cur.buffer()->isReadonly()
2427 && (cur.pos() >= par.size() || par.isWordSeparator(cur.pos()))
2428 && !par.isWordSeparator(cur.pos() - 1);
2432 CompletionList const * Text::createCompletionList(Cursor const & cur) const
2434 WordList const & list = theWordList(cur.getFont().language()->lang());
2435 return new TextCompletionList(cur, list);
2439 bool Text::insertCompletion(Cursor & cur, docstring const & s)
2441 LBUFERR(cur.bv().cursor() == cur);
2442 if (cur.buffer()->isReadonly())
2446 cur.bv().cursor() = cur;
2447 if (!(cur.result().screenUpdate() & Update::Force))
2448 cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
2453 docstring Text::completionPrefix(Cursor const & cur) const
2455 CursorSlice from = cur.top();
2456 CursorSlice to = from;
2457 getWord(from, to, PREVIOUS_WORD);
2459 return cur.paragraph().asString(from.pos(), to.pos());
2462 bool Text::isMainText() const
2464 return &owner_->buffer().text() == this;
2468 // Note that this is supposed to return a fully realized font.
2469 FontInfo Text::layoutFont(pit_type const pit) const
2471 Layout const & layout = pars_[pit].layout();
2473 if (!pars_[pit].getDepth()) {
2474 FontInfo lf = layout.resfont;
2475 // In case the default family has been customized
2476 if (layout.font.family() == INHERIT_FAMILY)
2477 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
2478 FontInfo icf = (!isMainText())
2479 // inside insets, we call the getFont() method
2481 // outside, we access the layout font directly
2482 : owner_->getLayout().font();
2487 FontInfo font = layout.font;
2488 // Realize with the fonts of lesser depth.
2489 //font.realize(outerFont(pit));
2490 font.realize(owner_->buffer().params().getFont().fontInfo());
2496 // Note that this is supposed to return a fully realized font.
2497 FontInfo Text::labelFont(Paragraph const & par) const
2499 Buffer const & buffer = owner_->buffer();
2500 Layout const & layout = par.layout();
2502 if (!par.getDepth()) {
2503 FontInfo lf = layout.reslabelfont;
2504 // In case the default family has been customized
2505 if (layout.labelfont.family() == INHERIT_FAMILY)
2506 lf.setFamily(buffer.params().getFont().fontInfo().family());
2510 FontInfo font = layout.labelfont;
2511 // Realize with the fonts of lesser depth.
2512 font.realize(buffer.params().getFont().fontInfo());
2518 void Text::setCharFont(pit_type pit,
2519 pos_type pos, Font const & fnt, Font const & display_font)
2521 Buffer const & buffer = owner_->buffer();
2523 Layout const & layout = pars_[pit].layout();
2525 // Get concrete layout font to reduce against
2526 FontInfo layoutfont;
2528 if (pos < pars_[pit].beginOfBody())
2529 layoutfont = layout.labelfont;
2531 layoutfont = layout.font;
2533 // Realize against environment font information
2534 if (pars_[pit].getDepth()) {
2536 while (!layoutfont.resolved() &&
2537 tp != pit_type(paragraphs().size()) &&
2538 pars_[tp].getDepth()) {
2540 if (tp != pit_type(paragraphs().size()))
2541 layoutfont.realize(pars_[tp].layout().font);
2545 // Inside inset, apply the inset's font attributes if any
2548 layoutfont.realize(display_font.fontInfo());
2550 layoutfont.realize(buffer.params().getFont().fontInfo());
2552 // Now, reduce font against full layout font
2553 font.fontInfo().reduce(layoutfont);
2555 pars_[pit].setFont(pos, font);
2559 void Text::setInsetFont(BufferView const & bv, pit_type pit,
2560 pos_type pos, Font const & font)
2562 Inset * const inset = pars_[pit].getInset(pos);
2563 LASSERT(inset && inset->resetFontEdit(), return);
2565 idx_type endidx = inset->nargs();
2566 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
2567 Text * text = cs.text();
2569 // last position of the cell
2570 CursorSlice cellend = cs;
2571 cellend.pit() = cellend.lastpit();
2572 cellend.pos() = cellend.lastpos();
2573 text->setFont(bv, cs, cellend, font);
2579 void Text::setLayout(pit_type start, pit_type end,
2580 docstring const & layout)
2582 // FIXME: make this work in multicell selection case
2583 LASSERT(start != end, return);
2585 Buffer const & buffer = owner_->buffer();
2586 BufferParams const & bp = buffer.params();
2587 Layout const & lyxlayout = bp.documentClass()[layout];
2589 for (pit_type pit = start; pit != end; ++pit) {
2590 Paragraph & par = pars_[pit];
2591 // Is this a separating paragraph? If so,
2592 // this needs to be standard layout
2593 bool const is_separator = par.size() == 1
2594 && par.isEnvSeparator(0);
2595 par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
2596 if (lyxlayout.margintype == MARGIN_MANUAL)
2597 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
2600 deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
2604 // set layout over selection and make a total rebreak of those paragraphs
2605 void Text::setLayout(Cursor & cur, docstring const & layout)
2607 LBUFERR(this == cur.text());
2609 pit_type start = cur.selBegin().pit();
2610 pit_type end = cur.selEnd().pit() + 1;
2611 cur.recordUndoSelection();
2612 setLayout(start, end, layout);
2614 cur.setCurrentFont();
2615 cur.forceBufferUpdate();
2619 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
2620 Paragraph const & par, int max_depth)
2622 int const depth = par.params().depth();
2623 if (type == Text::INC_DEPTH && depth < max_depth)
2625 if (type == Text::DEC_DEPTH && depth > 0)
2631 bool Text::changeDepthAllowed(Cursor const & cur, DEPTH_CHANGE type) const
2633 LBUFERR(this == cur.text());
2634 // this happens when selecting several cells in tabular (bug 2630)
2635 if (cur.selBegin().idx() != cur.selEnd().idx())
2638 pit_type const beg = cur.selBegin().pit();
2639 pit_type const end = cur.selEnd().pit() + 1;
2640 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2642 for (pit_type pit = beg; pit != end; ++pit) {
2643 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
2645 max_depth = pars_[pit].getMaxDepthAfter();
2651 void Text::changeDepth(Cursor const & cur, DEPTH_CHANGE type)
2653 LBUFERR(this == cur.text());
2654 pit_type const beg = cur.selBegin().pit();
2655 pit_type const end = cur.selEnd().pit() + 1;
2656 cur.recordUndoSelection();
2657 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2659 for (pit_type pit = beg; pit != end; ++pit) {
2660 Paragraph & par = pars_[pit];
2661 if (lyx::changeDepthAllowed(type, par, max_depth)) {
2662 int const depth = par.params().depth();
2663 if (type == INC_DEPTH)
2664 par.params().depth(depth + 1);
2666 par.params().depth(depth - 1);
2668 max_depth = par.getMaxDepthAfter();
2670 // this handles the counter labels, and also fixes up
2671 // depth values for follow-on (child) paragraphs
2672 cur.forceBufferUpdate();
2676 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
2678 LASSERT(this == cur.text(), return);
2680 // If there is a selection, record undo before the cursor font is changed.
2681 if (cur.selection())
2682 cur.recordUndoSelection();
2684 // Set the current_font
2685 // Determine basis font
2686 FontInfo layoutfont;
2687 pit_type pit = cur.pit();
2688 if (cur.pos() < pars_[pit].beginOfBody())
2689 layoutfont = labelFont(pars_[pit]);
2691 layoutfont = layoutFont(pit);
2693 // Update current font
2694 cur.real_current_font.update(font,
2695 cur.buffer()->params().language,
2698 // Reduce to implicit settings
2699 cur.current_font = cur.real_current_font;
2700 cur.current_font.fontInfo().reduce(layoutfont);
2701 // And resolve it completely
2702 cur.real_current_font.fontInfo().realize(layoutfont);
2704 // if there is no selection that's all we need to do
2705 if (!cur.selection())
2708 // Ok, we have a selection.
2709 Font newfont = font;
2712 // Toggling behaves as follows: We check the first character of the
2713 // selection. If it's (say) got EMPH on, then we set to off; if off,
2714 // then to on. With families and the like, we set it to INHERIT, if
2715 // we already have it.
2716 CursorSlice const & sl = cur.selBegin();
2717 Text const & text = *sl.text();
2718 Paragraph const & par = text.getPar(sl.pit());
2720 // get font at the position
2721 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
2722 text.outerFont(sl.pit()));
2723 FontInfo const & oldfi = oldfont.fontInfo();
2725 FontInfo & newfi = newfont.fontInfo();
2727 FontFamily newfam = newfi.family();
2728 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
2729 newfam == oldfi.family())
2730 newfi.setFamily(INHERIT_FAMILY);
2732 FontSeries newser = newfi.series();
2733 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
2734 newfi.setSeries(INHERIT_SERIES);
2736 FontShape newshp = newfi.shape();
2737 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
2738 newshp == oldfi.shape())
2739 newfi.setShape(INHERIT_SHAPE);
2741 ColorCode newcol = newfi.color();
2742 if (newcol != Color_none && newcol != Color_inherit
2743 && newcol != Color_ignore && newcol == oldfi.color())
2744 newfi.setColor(Color_none);
2747 if (newfi.emph() == FONT_TOGGLE)
2748 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
2749 if (newfi.underbar() == FONT_TOGGLE)
2750 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
2751 if (newfi.strikeout() == FONT_TOGGLE)
2752 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
2753 if (newfi.xout() == FONT_TOGGLE)
2754 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
2755 if (newfi.uuline() == FONT_TOGGLE)
2756 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
2757 if (newfi.uwave() == FONT_TOGGLE)
2758 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
2759 if (newfi.noun() == FONT_TOGGLE)
2760 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
2761 if (newfi.number() == FONT_TOGGLE)
2762 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
2763 if (newfi.nospellcheck() == FONT_TOGGLE)
2764 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
2767 setFont(cur.bv(), cur.selectionBegin().top(),
2768 cur.selectionEnd().top(), newfont);
2772 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
2773 CursorSlice const & end, Font const & font)
2775 Buffer const & buffer = bv.buffer();
2777 // Don't use forwardChar here as ditend might have
2778 // pos() == lastpos() and forwardChar would miss it.
2779 // Can't use forwardPos either as this descends into
2781 Language const * language = buffer.params().language;
2782 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
2783 if (dit.pos() == dit.lastpos())
2785 pit_type const pit = dit.pit();
2786 pos_type const pos = dit.pos();
2787 Inset * inset = pars_[pit].getInset(pos);
2788 if (inset && inset->resetFontEdit()) {
2789 // We need to propagate the font change to all
2790 // text cells of the inset (bugs 1973, 6919).
2791 setInsetFont(bv, pit, pos, font);
2793 TextMetrics const & tm = bv.textMetrics(this);
2794 Font f = tm.displayFont(pit, pos);
2795 f.update(font, language);
2796 setCharFont(pit, pos, f, tm.font_);
2797 // font change may change language...
2798 // spell checker has to know that
2799 pars_[pit].requestSpellCheck(pos);
2804 bool Text::cursorTop(Cursor & cur)
2806 LBUFERR(this == cur.text());
2807 return setCursor(cur, 0, 0);
2811 bool Text::cursorBottom(Cursor & cur)
2813 LBUFERR(this == cur.text());
2814 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
2818 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
2820 LBUFERR(this == cur.text());
2821 // If the mask is completely neutral, tell user
2822 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
2823 // Could only happen with user style
2824 cur.message(_("No font change defined."));
2828 // Try implicit word selection
2829 // If there is a change in the language the implicit word selection
2831 CursorSlice const resetCursor = cur.top();
2832 bool const implicitSelection =
2833 font.language() == ignore_language
2834 && font.fontInfo().number() == FONT_IGNORE
2835 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
2838 setFont(cur, font, toggleall);
2840 // Implicit selections are cleared afterwards
2841 // and cursor is set to the original position.
2842 if (implicitSelection) {
2843 cur.clearSelection();
2844 cur.top() = resetCursor;
2848 // if there was no selection at all, the point was to change cursor font.
2849 // Otherwise, we want to reset it to local text font.
2850 if (cur.selection() || implicitSelection)
2851 cur.setCurrentFont();
2855 docstring Text::getStringForDialog(Cursor & cur)
2857 LBUFERR(this == cur.text());
2859 if (cur.selection())
2860 return cur.selectionAsString(false);
2862 // Try implicit word selection. If there is a change
2863 // in the language the implicit word selection is
2865 selectWordWhenUnderCursor(cur, WHOLE_WORD);
2866 docstring const & retval = cur.selectionAsString(false);
2867 cur.clearSelection();
2872 void Text::setLabelWidthStringToSequence(Cursor const & cur,
2873 docstring const & s)
2876 // Find first of same layout in sequence
2877 while (!isFirstInSequence(c.pit())) {
2878 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
2881 // now apply label width string to every par
2883 depth_type const depth = c.paragraph().getDepth();
2884 Layout const & layout = c.paragraph().layout();
2885 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
2886 while (c.paragraph().getDepth() > depth) {
2888 if (c.pit() > c.lastpit())
2891 if (c.paragraph().getDepth() < depth)
2893 if (c.paragraph().layout() != layout)
2896 c.paragraph().setLabelWidthString(s);
2901 void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
2903 LBUFERR(cur.text());
2906 string const argument = to_utf8(arg);
2907 depth_type priordepth = -1;
2910 c.setCursor(cur.selectionBegin());
2911 pit_type const last_pit = cur.selectionEnd().pit();
2912 for ( ; c.pit() <= last_pit ; ++c.pit()) {
2913 Paragraph & par = c.paragraph();
2914 ParagraphParameters params = par.params();
2915 params.read(argument, merge);
2916 // Changes to label width string apply to all paragraphs
2917 // with same layout in a sequence.
2918 // Do this only once for a selected range of paragraphs
2919 // of the same layout and depth.
2921 par.params().apply(params, par.layout());
2922 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2923 setLabelWidthStringToSequence(c, params.labelWidthString());
2924 priordepth = par.getDepth();
2925 priorlayout = par.layout();
2930 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
2932 LBUFERR(cur.text());
2934 depth_type priordepth = -1;
2937 c.setCursor(cur.selectionBegin());
2938 pit_type const last_pit = cur.selectionEnd().pit();
2939 for ( ; c.pit() <= last_pit ; ++c.pit()) {
2940 Paragraph & par = c.paragraph();
2941 // Changes to label width string apply to all paragraphs
2942 // with same layout in a sequence.
2943 // Do this only once for a selected range of paragraphs
2944 // of the same layout and depth.
2946 par.params().apply(p, par.layout());
2947 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2948 setLabelWidthStringToSequence(c,
2949 par.params().labelWidthString());
2950 priordepth = par.getDepth();
2951 priorlayout = par.layout();
2956 // this really should just insert the inset and not move the cursor.
2957 void Text::insertInset(Cursor & cur, Inset * inset)
2959 LBUFERR(this == cur.text());
2961 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
2962 Change(cur.buffer()->params().track_changes
2963 ? Change::INSERTED : Change::UNCHANGED));
2967 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
2968 bool setfont, bool boundary)
2970 TextMetrics const & tm = cur.bv().textMetrics(this);
2971 bool const update_needed = !tm.contains(pit);
2973 setCursorIntern(cur, pit, pos, setfont, boundary);
2974 return cur.bv().checkDepm(cur, old) || update_needed;
2978 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
2979 bool setfont, bool boundary)
2981 LBUFERR(this == cur.text());
2982 cur.boundary(boundary);
2983 cur.top().setPitPos(pit, pos);
2985 cur.setCurrentFont();
2989 bool Text::checkAndActivateInset(Cursor & cur, bool front)
2991 if (front && cur.pos() == cur.lastpos())
2993 if (!front && cur.pos() == 0)
2995 Inset * inset = front ? cur.nextInset() : cur.prevInset();
2996 if (!inset || !inset->editable())
2998 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3001 * Apparently, when entering an inset we are expected to be positioned
3002 * *before* it in the containing paragraph, regardless of the direction
3003 * from which we are entering. Otherwise, cursor placement goes awry,
3004 * and when we exit from the beginning, we'll be placed *after* the
3009 inset->edit(cur, front);
3010 cur.setCurrentFont();
3011 cur.boundary(false);
3016 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
3018 if (cur.pos() == -1)
3020 if (cur.pos() == cur.lastpos())
3022 Paragraph & par = cur.paragraph();
3023 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
3024 if (!inset || !inset->editable())
3026 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3028 inset->edit(cur, movingForward,
3029 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
3030 cur.setCurrentFont();
3031 cur.boundary(false);
3036 bool Text::cursorBackward(Cursor & cur)
3038 // Tell BufferView to test for FitCursor in any case!
3039 cur.screenUpdateFlags(Update::FitCursor);
3041 // not at paragraph start?
3042 if (cur.pos() > 0) {
3043 // if on right side of boundary (i.e. not at paragraph end, but line end)
3044 // -> skip it, i.e. set boundary to true, i.e. go only logically left
3045 // there are some exceptions to ignore this: lineseps, newlines, spaces
3047 // some effectless debug code to see the values in the debugger
3048 bool bound = cur.boundary();
3049 int rowpos = cur.textRow().pos();
3050 int pos = cur.pos();
3051 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
3052 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
3053 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
3055 if (!cur.boundary() &&
3056 cur.textRow().pos() == cur.pos() &&
3057 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
3058 !cur.paragraph().isNewline(cur.pos() - 1) &&
3059 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
3060 !cur.paragraph().isSeparator(cur.pos() - 1)) {
3061 return setCursor(cur, cur.pit(), cur.pos(), true, true);
3064 // go left and try to enter inset
3065 if (checkAndActivateInset(cur, false))
3068 // normal character left
3069 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
3072 // move to the previous paragraph or do nothing
3073 if (cur.pit() > 0) {
3074 Paragraph & par = getPar(cur.pit() - 1);
3075 pos_type lastpos = par.size();
3076 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
3077 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
3079 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
3085 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
3087 Cursor temp_cur = cur;
3088 temp_cur.posVisLeft(skip_inset);
3089 if (temp_cur.depth() > cur.depth()) {
3093 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3094 true, temp_cur.boundary());
3098 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
3100 Cursor temp_cur = cur;
3101 temp_cur.posVisRight(skip_inset);
3102 if (temp_cur.depth() > cur.depth()) {
3106 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3107 true, temp_cur.boundary());
3111 bool Text::cursorForward(Cursor & cur)
3113 // Tell BufferView to test for FitCursor in any case!
3114 cur.screenUpdateFlags(Update::FitCursor);
3116 // not at paragraph end?
3117 if (cur.pos() != cur.lastpos()) {
3118 // in front of editable inset, i.e. jump into it?
3119 if (checkAndActivateInset(cur, true))
3122 TextMetrics const & tm = cur.bv().textMetrics(this);
3123 // if left of boundary -> just jump to right side
3124 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
3125 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
3126 return setCursor(cur, cur.pit(), cur.pos(), true, false);
3128 // next position is left of boundary,
3129 // but go to next line for special cases like space, newline, linesep
3131 // some effectless debug code to see the values in the debugger
3132 int endpos = cur.textRow().endpos();
3133 int lastpos = cur.lastpos();
3134 int pos = cur.pos();
3135 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
3136 bool newline = cur.paragraph().isNewline(cur.pos());
3137 bool sep = cur.paragraph().isSeparator(cur.pos());
3138 if (cur.pos() != cur.lastpos()) {
3139 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
3140 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
3141 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
3144 if (cur.textRow().endpos() == cur.pos() + 1) {
3145 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
3146 cur.pos() + 1 == cur.lastpos() &&
3147 cur.pit() != cur.lastpit()) {
3148 // move to next paragraph
3149 return setCursor(cur, cur.pit() + 1, 0, true, false);
3150 } else if (cur.textRow().endpos() != cur.lastpos() &&
3151 !cur.paragraph().isNewline(cur.pos()) &&
3152 !cur.paragraph().isEnvSeparator(cur.pos()) &&
3153 !cur.paragraph().isLineSeparator(cur.pos()) &&
3154 !cur.paragraph().isSeparator(cur.pos())) {
3155 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3159 // in front of RTL boundary? Stay on this side of the boundary because:
3160 // ab|cDDEEFFghi -> abc|DDEEFFghi
3161 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
3162 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3165 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
3168 // move to next paragraph
3169 if (cur.pit() != cur.lastpit())
3170 return setCursor(cur, cur.pit() + 1, 0, true, false);
3175 bool Text::cursorUpParagraph(Cursor & cur)
3177 bool updated = false;
3179 updated = setCursor(cur, cur.pit(), 0);
3180 else if (cur.pit() != 0)
3181 updated = setCursor(cur, cur.pit() - 1, 0);
3186 bool Text::cursorDownParagraph(Cursor & cur)
3188 bool updated = false;
3189 if (cur.pit() != cur.lastpit())
3190 if (lyxrc.mac_like_cursor_movement)
3191 if (cur.pos() == cur.lastpos())
3192 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
3194 updated = setCursor(cur, cur.pit(), cur.lastpos());
3196 updated = setCursor(cur, cur.pit() + 1, 0);
3198 updated = setCursor(cur, cur.pit(), cur.lastpos());
3204 /** delete num_spaces characters between from and to. Return the
3205 * number of spaces that got physically deleted (not marked as
3207 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
3208 int num_spaces, bool const trackChanges)
3210 if (num_spaces <= 0)
3213 // First, delete spaces marked as inserted
3215 while (pos < to && num_spaces > 0) {
3216 Change const & change = par.lookupChange(pos);
3217 if (change.inserted() && change.currentAuthor()) {
3218 par.eraseChar(pos, trackChanges);
3225 // Then remove remaining spaces
3226 int const psize = par.size();
3227 par.eraseChars(from, from + num_spaces, trackChanges);
3228 return psize - par.size();
3234 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
3235 Cursor & old, bool & need_anchor_change)
3237 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
3239 Paragraph & oldpar = old.paragraph();
3240 bool const trackChanges = cur.buffer()->params().track_changes;
3241 bool result = false;
3243 // We do nothing if cursor did not move
3244 if (cur.top() == old.top())
3247 // We do not do anything on read-only documents
3248 if (cur.buffer()->isReadonly())
3251 // Whether a common inset is found and whether the cursor is still in
3252 // the same paragraph (possibly nested).
3253 int const depth = cur.find(&old.inset());
3254 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
3255 && old.pit() == cur[depth].pit();
3258 * (1) If the chars around the old cursor were spaces and the
3259 * paragraph is not in free spacing mode, delete some of them, but
3260 * only if the cursor has really moved.
3263 /* There are still some small problems that can lead to
3264 double spaces stored in the document file or space at
3265 the beginning of paragraphs(). This happens if you have
3266 the cursor between two spaces and then save. Or if you
3267 cut and paste and the selection has a space at the
3268 beginning and then save right after the paste. (Lgb)
3270 if (!oldpar.isFreeSpacing()) {
3271 // find range of spaces around cursors
3272 pos_type from = old.pos();
3274 && oldpar.isLineSeparator(from - 1)
3275 && !oldpar.isDeleted(from - 1))
3277 pos_type to = old.pos();
3278 while (to < old.lastpos()
3279 && oldpar.isLineSeparator(to)
3280 && !oldpar.isDeleted(to))
3283 int num_spaces = to - from;
3284 // If we are not at the start of the paragraph, keep one space
3285 if (from != to && from > 0)
3288 // If cursor is inside range, keep one additional space
3289 if (same_par && cur.pos() > from && cur.pos() < to)
3292 // Remove spaces and adapt cursor.
3293 if (num_spaces > 0) {
3296 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
3297 // correct cur position
3298 // FIXME: there can be other cursors pointing there, we should update them
3300 if (cur[depth].pos() >= to)
3301 cur[depth].pos() -= deleted;
3302 else if (cur[depth].pos() > from)
3303 cur[depth].pos() = min(from + 1, old.lastpos());
3304 need_anchor_change = true;
3311 * (2) If the paragraph where the cursor was is empty, delete it
3314 // only do our other magic if we changed paragraph
3318 // only do our magic if the paragraph is empty
3319 if (!oldpar.empty())
3322 // don't delete anything if this is the ONLY paragraph!
3323 if (old.lastpit() == 0)
3326 // Do not delete empty paragraphs with keepempty set.
3327 if (oldpar.allowEmpty())
3331 old.recordUndo(max(old.pit() - 1, pit_type(0)),
3332 min(old.pit() + 1, old.lastpit()));
3333 ParagraphList & plist = old.text()->paragraphs();
3334 bool const soa = oldpar.params().startOfAppendix();
3335 plist.erase(plist.iterator_at(old.pit()));
3336 // do not lose start of appendix marker (bug 4212)
3337 if (soa && old.pit() < pit_type(plist.size()))
3338 plist[old.pit()].params().startOfAppendix(true);
3340 // see #warning (FIXME?) above
3341 if (cur.depth() >= old.depth()) {
3342 CursorSlice & curslice = cur[old.depth() - 1];
3343 if (&curslice.inset() == &old.inset()
3344 && curslice.idx() == old.idx()
3345 && curslice.pit() > old.pit()) {
3347 // since a paragraph has been deleted, all the
3348 // insets after `old' have been copied and
3349 // their address has changed. Therefore we
3350 // need to `regenerate' cur. (JMarc)
3351 cur.updateInsets(&(cur.bottom().inset()));
3352 need_anchor_change = true;
3360 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
3362 pos_type last_pos = pars_[last].size() - 1;
3363 deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
3367 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
3368 pos_type first_pos, pos_type last_pos,
3371 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
3373 for (pit_type pit = first; pit <= last; ++pit) {
3374 Paragraph & par = pars_[pit];
3377 * (1) Delete consecutive spaces
3379 if (!par.isFreeSpacing()) {
3380 pos_type from = (pit == first) ? first_pos : 0;
3381 pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
3382 while (from < to_pos) {
3384 while (from < par.size()
3385 && (!par.isLineSeparator(from) || par.isDeleted(from)))
3387 // find string of spaces
3389 while (to < par.size()
3390 && par.isLineSeparator(to) && !par.isDeleted(to))
3392 // empty? We are done
3396 int num_spaces = to - from;
3398 // If we are not at the extremity of the paragraph, keep one space
3399 if (from != to && from > 0 && to < par.size())
3402 // Remove spaces if needed
3403 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
3404 from = to - deleted;
3409 * (2) Delete empty pragraphs
3412 // don't delete anything if this is the only remaining paragraph
3413 // within the given range. Note: Text::acceptOrRejectChanges()
3414 // sets the cursor to 'first' after calling DEPM
3418 // don't delete empty paragraphs with keepempty set
3419 if (par.allowEmpty())
3422 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
3423 pars_.erase(pars_.iterator_at(pit));
3435 typedef limited_stack<pair<docstring, Font>> FontStack;
3436 static FontStack freeFonts(15);
3437 static bool toggleall = false;
3439 void toggleAndShow(Cursor & cur, Text * text,
3440 Font const & font, bool togall = true)
3442 text->toggleFree(cur, font, togall);
3444 if (font.language() != ignore_language ||
3445 font.fontInfo().number() != FONT_IGNORE) {
3446 TextMetrics const & tm = cur.bv().textMetrics(text);
3447 if (cur.boundary() != tm.isRTLBoundary(cur.pit(), cur.pos(),
3448 cur.real_current_font))
3449 text->setCursor(cur, cur.pit(), cur.pos(),
3450 false, !cur.boundary());
3451 if (font.language() != ignore_language)
3452 // We need a buffer update if we change the language
3453 // (e.g., with info insets or if the selection contains
3455 cur.forceBufferUpdate();
3460 void moveCursor(Cursor & cur, bool selecting)
3462 if (selecting || cur.mark())
3467 void finishChange(Cursor & cur, bool selecting)
3470 moveCursor(cur, selecting);
3474 void mathDispatch(Cursor & cur, FuncRequest const & cmd)
3477 docstring sel = cur.selectionAsString(false);
3479 // It may happen that sel is empty but there is a selection
3480 replaceSelection(cur);
3482 // Is this a valid formula?
3486 #ifdef ENABLE_ASSERTIONS
3487 const int old_pos = cur.pos();
3489 cur.insert(new InsetMathHull(cur.buffer(), hullSimple));
3490 #ifdef ENABLE_ASSERTIONS
3491 LATTEST(old_pos == cur.pos());
3493 cur.nextInset()->edit(cur, true);
3494 if (cmd.action() != LFUN_MATH_MODE)
3495 // LFUN_MATH_MODE has a different meaning in math mode
3498 InsetMathHull * formula = new InsetMathHull(cur.buffer());
3499 string const selstr = to_utf8(sel);
3500 istringstream is(selstr);
3503 if (!formula->readQuiet(lex)) {
3504 // No valid formula, let's try with delims
3505 is.str("$" + selstr + "$");
3507 if (!formula->readQuiet(lex)) {
3508 // Still not valid, leave it as is
3515 cur.insert(formula);
3516 cur.nextInset()->edit(cur, true);
3517 LASSERT(cur.inMathed(), return);
3520 cur.selection(true);
3521 cur.pos() = cur.lastpos();
3522 if (cmd.action() != LFUN_MATH_MODE)
3523 // LFUN_MATH_MODE has a different meaning in math mode
3525 cur.clearSelection();
3526 cur.pos() = cur.lastpos();
3530 cur.message(from_utf8(N_("Math editor mode")));
3532 cur.message(from_utf8(N_("No valid math formula")));
3536 void regexpDispatch(Cursor & cur, FuncRequest const & cmd)
3538 LASSERT(cmd.action() == LFUN_REGEXP_MODE, return);
3539 if (cur.inRegexped()) {
3540 cur.message(_("Already in regular expression mode"));
3544 docstring sel = cur.selectionAsString(false);
3546 // It may happen that sel is empty but there is a selection
3547 replaceSelection(cur);
3549 cur.insert(new InsetMathHull(cur.buffer(), hullRegexp));
3550 cur.nextInset()->edit(cur, true);
3551 cur.niceInsert(sel);
3553 cur.message(_("Regexp editor mode"));
3557 void specialChar(Cursor & cur, InsetSpecialChar::Kind kind)
3560 cap::replaceSelection(cur);
3561 cur.insert(new InsetSpecialChar(kind));
3566 void ipaChar(Cursor & cur, InsetIPAChar::Kind kind)
3569 cap::replaceSelection(cur);
3570 cur.insert(new InsetIPAChar(kind));
3575 bool doInsertInset(Cursor & cur, Text * text,
3576 FuncRequest const & cmd, bool edit,
3577 bool pastesel, bool resetfont = false)
3579 Buffer & buffer = cur.bv().buffer();
3580 BufferParams const & bparams = buffer.params();
3581 Inset * inset = createInset(&buffer, cmd);
3585 if (InsetCollapsible * ci = inset->asInsetCollapsible())
3586 ci->setButtonLabel();
3589 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3590 bool cotextinsert = false;
3591 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3592 Layout const & lay = cur.paragraph().layout();
3593 Layout::LaTeXArgMap args = lay.args();
3594 Layout::LaTeXArgMap::const_iterator const lait = args.find(ia->name());
3595 if (lait != args.end())
3596 cotextinsert = (*lait).second.insertcotext;
3598 InsetLayout const & il = cur.inset().getLayout();
3600 Layout::LaTeXArgMap::const_iterator const ilait = args.find(ia->name());
3601 if (ilait != args.end())
3602 cotextinsert = (*ilait).second.insertcotext;
3604 // The argument requests to insert a copy of the co-text to the inset
3607 // If we have a selection within a paragraph, use this
3608 if (cur.selection() && cur.selBegin().pit() == cur.selEnd().pit())
3609 ds = cur.selectionAsString(false);
3610 // else use the whole paragraph
3612 ds = cur.paragraph().asString();
3613 text->insertInset(cur, inset);
3614 ia->init(cur.paragraph());
3616 inset->edit(cur, true);
3617 // Now put co-text into inset
3618 Font const f(inherit_font, cur.current_font.language());
3620 cur.text()->insertStringAsLines(cur, ds, f);
3621 cur.leaveInset(*inset);
3627 bool gotsel = false;
3628 bool move_layout = false;
3629 if (cur.selection()) {
3630 if (cmd.action() == LFUN_INDEX_INSERT)
3631 copySelectionToTemp(cur);
3633 cutSelectionToTemp(cur, pastesel);
3634 /* Move layout information inside the inset if the whole
3635 * paragraph and the inset allows setting layout
3636 * FIXME: this does not work as expected when change tracking is on
3637 * However, we do not really know what to do in this case.
3638 * FIXME: figure out a good test in the environment case (see #12251).
3640 if (cur.paragraph().layout().isCommand()
3641 && cur.paragraph().empty()
3642 && !inset->forcePlainLayout()) {
3643 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3647 cur.clearSelection();
3649 } else if (cmd.action() == LFUN_INDEX_INSERT) {
3650 gotsel = text->selectWordWhenUnderCursor(cur, WHOLE_WORD);
3651 copySelectionToTemp(cur);
3652 cur.clearSelection();
3654 text->insertInset(cur, inset);
3656 InsetText * inset_text = inset->asInsetText();
3658 Font const & font = inset->inheritFont()
3659 ? cur.bv().textMetrics(text).displayFont(cur.pit(), cur.pos())
3660 : bparams.getFont();
3661 inset_text->setOuterFont(cur.bv(), font.fontInfo());
3664 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3665 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3666 ia->init(cur.paragraph());
3670 inset->edit(cur, true);
3672 if (!gotsel || !pastesel)
3675 pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
3676 cur.buffer()->errors("Paste");
3677 cur.clearSelection(); // bug 393
3681 // Reset of font (not language) is requested.
3682 // Used by InsetIndex (#11961).
3683 Language const * lang = cur.getFont().language();
3684 Font font(bparams.getFont().fontInfo(), lang);
3685 cur.paragraph().resetFonts(font);
3687 inset_text->fixParagraphsFont();
3690 /* If the containing paragraph has kept its layout, reset the
3691 * layout of the first paragraph of the inset.
3694 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3695 // FIXME: what does this do?
3696 if (cmd.action() == LFUN_FLEX_INSERT)
3699 cur.leaveInset(*inset);
3700 if (cmd.action() == LFUN_PREVIEW_INSERT
3701 || cmd.action() == LFUN_IPA_INSERT)
3703 notifyCursorLeavesOrEnters(old, cur);
3705 cur.leaveInset(*inset);
3706 // reset surrounding par to default
3707 DocumentClass const & dc = bparams.documentClass();
3708 docstring const layoutname = inset->usePlainLayout()
3709 ? dc.plainLayoutName()
3710 : dc.defaultLayoutName();
3711 text->setLayout(cur, layoutname);
3717 /// the type of outline operation
3719 OutlineUp, // Move this header with text down
3720 OutlineDown, // Move this header with text up
3721 OutlineIn, // Make this header deeper
3722 OutlineOut // Make this header shallower
3726 void insertSeparator(Cursor const & cur, depth_type const depth)
3728 Buffer & buf = *cur.buffer();
3729 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
3730 DocumentClass const & tc = buf.params().documentClass();
3731 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
3732 + from_ascii("\" ignoreautonests")));
3733 // FIXME: Bibitem mess!
3734 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
3735 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
3736 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
3737 while (cur.paragraph().params().depth() > depth)
3738 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
3742 void outline(OutlineOp mode, Cursor & cur, bool local)
3744 Buffer & buf = *cur.buffer();
3745 Text & text = *cur.text();
3746 pit_type & pit = cur.pit();
3747 ParagraphList & pars = text.paragraphs();
3748 ParagraphList::iterator const bgn = pars.begin();
3749 // The first paragraph of the area to be copied:
3750 ParagraphList::iterator start = pars.iterator_at(pit);
3751 // The final paragraph of area to be copied:
3752 ParagraphList::iterator finish = start;
3753 ParagraphList::iterator const end = pars.end();
3754 depth_type const current_depth = cur.paragraph().params().depth();
3756 int const thistoclevel = text.getTocLevel(distance(bgn, start));
3759 // Move out (down) from this section header
3763 if (!local || (mode != OutlineIn && mode != OutlineOut)) {
3764 // Seek the one (on same level) below
3765 for (; finish != end; ++finish) {
3766 toclevel = text.getTocLevel(distance(bgn, finish));
3767 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
3774 if (start == pars.begin())
3777 ParagraphList::iterator dest = start;
3778 // Move out (up) from this header
3781 // Search previous same-level header above
3784 toclevel = text.getTocLevel(distance(bgn, dest));
3786 && (toclevel == Layout::NOT_IN_TOC
3787 || toclevel > thistoclevel));
3788 // Not found; do nothing
3789 if (toclevel == Layout::NOT_IN_TOC || toclevel > thistoclevel)
3791 pit_type newpit = distance(bgn, dest);
3792 pit_type const len = distance(start, finish);
3793 pit_type const deletepit = pit + len;
3794 buf.undo().recordUndo(cur, newpit, deletepit - 1);
3795 // If we move an environment upwards, make sure it is
3796 // separated from its new neighbour below:
3797 // If an environment of the same layout follows, and the moved
3798 // paragraph sequence does not end with a separator, insert one.
3799 ParagraphList::iterator lastmoved = finish;
3801 if (start->layout().isEnvironment()
3802 && dest->layout() == start->layout()
3803 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3804 cur.pit() = distance(bgn, lastmoved);
3805 cur.pos() = cur.lastpos();
3806 insertSeparator(cur, current_depth);
3809 // Likewise, if we moved an environment upwards, make sure it
3810 // is separated from its new neighbour above.
3811 // The paragraph before the target of movement
3813 ParagraphList::iterator before = dest;
3815 // Get the parent paragraph (outer in nested context)
3816 pit_type const parent =
3817 before->params().depth() > current_depth
3818 ? text.depthHook(distance(bgn, before), current_depth)
3819 : distance(bgn, before);
3820 // If a environment with same layout preceeds the moved one in the new
3821 // position, and there is no separator yet, insert one.
3822 if (start->layout().isEnvironment()
3823 && pars[parent].layout() == start->layout()
3824 && !before->isEnvSeparator(before->beginOfBody())) {
3825 cur.pit() = distance(bgn, before);
3826 cur.pos() = cur.lastpos();
3827 insertSeparator(cur, current_depth);
3831 newpit = distance(bgn, dest);
3832 pars.splice(dest, start, finish);
3840 // Go one down from *this* header:
3841 ParagraphList::iterator dest = next(finish, 1);
3842 // Go further down to find header to insert in front of:
3843 for (; dest != end; ++dest) {
3844 toclevel = text.getTocLevel(distance(bgn, dest));
3845 if (toclevel != Layout::NOT_IN_TOC
3846 && toclevel <= thistoclevel)
3849 // One such was found, so go on...
3850 // If we move an environment downwards, make sure it is
3851 // separated from its new neighbour above.
3852 pit_type newpit = distance(bgn, dest);
3853 buf.undo().recordUndo(cur, pit, newpit - 1);
3854 // The paragraph before the target of movement
3855 ParagraphList::iterator before = dest;
3857 // Get the parent paragraph (outer in nested context)
3858 pit_type const parent =
3859 before->params().depth() > current_depth
3860 ? text.depthHook(distance(bgn, before), current_depth)
3861 : distance(bgn, before);
3862 // If a environment with same layout preceeds the moved one in the new
3863 // position, and there is no separator yet, insert one.
3864 if (start->layout().isEnvironment()
3865 && pars[parent].layout() == start->layout()
3866 && !before->isEnvSeparator(before->beginOfBody())) {
3867 cur.pit() = distance(bgn, before);
3868 cur.pos() = cur.lastpos();
3869 insertSeparator(cur, current_depth);
3872 // Likewise, make sure moved environments are separated
3873 // from their new neighbour below:
3874 // If an environment of the same layout follows, and the moved
3875 // paragraph sequence does not end with a separator, insert one.
3876 ParagraphList::iterator lastmoved = finish;
3879 && start->layout().isEnvironment()
3880 && dest->layout() == start->layout()
3881 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3882 cur.pit() = distance(bgn, lastmoved);
3883 cur.pos() = cur.lastpos();
3884 insertSeparator(cur, current_depth);
3887 newpit = distance(bgn, dest);
3888 pit_type const len = distance(start, finish);
3889 pars.splice(dest, start, finish);
3890 cur.pit() = newpit - len;
3895 // We first iterate without actually doing something
3896 // in order to check whether the action flattens the structure.
3897 // If so, warn (#11178).
3898 ParagraphList::iterator cstart = start;
3899 bool strucchange = false;
3900 for (; cstart != finish; ++cstart) {
3901 toclevel = text.getTocLevel(distance(bgn, cstart));
3902 if (toclevel == Layout::NOT_IN_TOC)
3905 DocumentClass const & tc = buf.params().documentClass();
3906 int const newtoclevel =
3907 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3910 for (auto const & lay : tc) {
3911 if (lay.toclevel == newtoclevel
3912 && lay.isNumHeadingLabelType()
3913 && cstart->layout().isNumHeadingLabelType()) {
3924 && frontend::Alert::prompt(_("Action flattens document structure"),
3925 _("This action will cause some headings that have been "
3926 "on different level before to be on the same level "
3927 "since there is no more lower or higher heading level. "
3930 _("&Yes, continue nonetheless"),
3931 _("&No, quit operation")) == 1)
3934 pit_type const len = distance(start, finish);
3935 buf.undo().recordUndo(cur, pit, pit + len - 1);
3936 for (; start != finish; ++start) {
3937 toclevel = text.getTocLevel(distance(bgn, start));
3938 if (toclevel == Layout::NOT_IN_TOC)
3941 DocumentClass const & tc = buf.params().documentClass();
3942 int const newtoclevel =
3943 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3945 for (auto const & lay : tc) {
3946 if (lay.toclevel == newtoclevel
3947 && lay.isNumHeadingLabelType()
3948 && start->layout().isNumHeadingLabelType()) {
3949 start->setLayout(lay);
3963 void Text::number(Cursor & cur)
3965 FontInfo font = ignore_font;
3966 font.setNumber(FONT_TOGGLE);
3967 toggleAndShow(cur, this, Font(font, ignore_language));
3971 bool Text::isRTL(pit_type const pit) const
3973 Buffer const & buffer = owner_->buffer();
3974 return pars_[pit].isRTL(buffer.params());
3980 Language const * getLanguage(Cursor const & cur, string const & lang)
3982 return lang.empty() ? cur.getFont().language() : languages.getLanguage(lang);
3986 docstring resolveLayout(docstring layout, DocIterator const & dit)
3988 Paragraph const & par = dit.paragraph();
3989 DocumentClass const & tclass = dit.buffer()->params().documentClass();
3992 layout = tclass.defaultLayoutName();
3994 if (dit.inset().forcePlainLayout(dit.idx()))
3995 // in this case only the empty layout is allowed
3996 layout = tclass.plainLayoutName();
3997 else if (par.usePlainLayout()) {
3998 // in this case, default layout maps to empty layout
3999 if (layout == tclass.defaultLayoutName())
4000 layout = tclass.plainLayoutName();
4002 // otherwise, the empty layout maps to the default
4003 if (layout == tclass.plainLayoutName())
4004 layout = tclass.defaultLayoutName();
4007 // If the entry is obsolete, use the new one instead.
4008 if (tclass.hasLayout(layout)) {
4009 docstring const & obs = tclass[layout].obsoleted_by();
4013 if (!tclass.hasLayout(layout))
4019 bool isAlreadyLayout(docstring const & layout, CursorData const & cur)
4021 ParagraphList const & pars = cur.text()->paragraphs();
4023 pit_type pit = cur.selBegin().pit();
4024 pit_type const epit = cur.selEnd().pit() + 1;
4025 for ( ; pit != epit; ++pit)
4026 if (pars[pit].layout().name() != layout)
4036 void Text::dispatch(Cursor & cur, FuncRequest & cmd)
4038 LYXERR(Debug::ACTION, "Text::dispatch: cmd: " << cmd);
4040 // Dispatch if the cursor is inside the text. It is not the
4041 // case for context menus (bug 5797).
4042 if (cur.text() != this) {
4047 BufferView * bv = &cur.bv();
4048 TextMetrics * tm = &bv->textMetrics(this);
4049 if (!tm->contains(cur.pit())) {
4050 lyx::dispatch(FuncRequest(LFUN_SCREEN_SHOW_CURSOR));
4051 tm = &bv->textMetrics(this);
4054 // FIXME: We use the update flag to indicates wether a singlePar or a
4055 // full screen update is needed. We reset it here but shall we restore it
4057 cur.noScreenUpdate();
4059 LBUFERR(this == cur.text());
4061 // NOTE: This should NOT be a reference. See commit 94a5481a.
4062 CursorSlice const oldTopSlice = cur.top();
4063 bool const oldBoundary = cur.boundary();
4064 bool const oldSelection = cur.selection();
4065 // Signals that, even if needsUpdate == false, an update of the
4066 // cursor paragraph is required
4067 bool singleParUpdate = lyxaction.funcHasFlag(cmd.action(),
4068 LyXAction::SingleParUpdate);
4069 // Signals that a full-screen update is required
4070 bool needsUpdate = !(lyxaction.funcHasFlag(cmd.action(),
4071 LyXAction::NoUpdate) || singleParUpdate);
4072 bool const last_misspelled = lyxrc.spellcheck_continuously
4073 && cur.paragraph().isMisspelled(cur.pos(), true);
4075 FuncCode const act = cmd.action();
4078 case LFUN_PARAGRAPH_MOVE_DOWN: {
4079 pit_type const pit = cur.pit();
4080 cur.recordUndo(pit, pit + 1);
4081 pars_.swap(pit, pit + 1);
4083 cur.forceBufferUpdate();
4088 case LFUN_PARAGRAPH_MOVE_UP: {
4089 pit_type const pit = cur.pit();
4090 cur.recordUndo(pit - 1, pit);
4092 pars_.swap(pit, pit - 1);
4095 cur.forceBufferUpdate();
4099 case LFUN_APPENDIX: {
4100 Paragraph & par = cur.paragraph();
4101 bool start = !par.params().startOfAppendix();
4103 // FIXME: The code below only makes sense at top level.
4104 // Should LFUN_APPENDIX be restricted to top-level paragraphs?
4105 // ensure that we have only one start_of_appendix in this document
4106 // FIXME: this don't work for multipart document!
4107 for (pit_type tmp = 0, end = pars_.size(); tmp != end; ++tmp) {
4108 if (pars_[tmp].params().startOfAppendix()) {
4109 cur.recordUndo(tmp, tmp);
4110 pars_[tmp].params().startOfAppendix(false);
4116 par.params().startOfAppendix(start);
4118 // we can set the refreshing parameters now
4119 cur.forceBufferUpdate();
4123 case LFUN_WORD_DELETE_FORWARD:
4124 if (cur.selection())
4125 cutSelection(cur, false);
4127 deleteWordForward(cur, cmd.getArg(0) != "confirm");
4128 finishChange(cur, false);
4131 case LFUN_WORD_DELETE_BACKWARD:
4132 if (cur.selection())
4133 cutSelection(cur, false);
4135 deleteWordBackward(cur, cmd.getArg(0) != "confirm");
4136 finishChange(cur, false);
4139 case LFUN_LINE_DELETE_FORWARD:
4140 if (cur.selection())
4141 cutSelection(cur, false);
4143 tm->deleteLineForward(cur);
4144 finishChange(cur, false);
4147 case LFUN_BUFFER_BEGIN:
4148 case LFUN_BUFFER_BEGIN_SELECT:
4149 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_BEGIN_SELECT);
4150 if (cur.depth() == 1)
4151 needsUpdate |= cursorTop(cur);
4154 cur.screenUpdateFlags(Update::FitCursor);
4157 case LFUN_BUFFER_END:
4158 case LFUN_BUFFER_END_SELECT:
4159 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_END_SELECT);
4160 if (cur.depth() == 1)
4161 needsUpdate |= cursorBottom(cur);
4164 cur.screenUpdateFlags(Update::FitCursor);
4167 case LFUN_INSET_BEGIN:
4168 case LFUN_INSET_BEGIN_SELECT:
4169 needsUpdate |= cur.selHandle(act == LFUN_INSET_BEGIN_SELECT);
4170 if (cur.depth() == 1 || !cur.top().at_begin())
4171 needsUpdate |= cursorTop(cur);
4174 cur.screenUpdateFlags(Update::FitCursor);
4177 case LFUN_INSET_END:
4178 case LFUN_INSET_END_SELECT:
4179 needsUpdate |= cur.selHandle(act == LFUN_INSET_END_SELECT);
4180 if (cur.depth() == 1 || !cur.top().at_end())
4181 needsUpdate |= cursorBottom(cur);
4184 cur.screenUpdateFlags(Update::FitCursor);
4187 case LFUN_CHAR_FORWARD:
4188 case LFUN_CHAR_FORWARD_SELECT: {
4189 //LYXERR0(" LFUN_CHAR_FORWARD[SEL]:\n" << cur);
4190 needsUpdate |= cur.selHandle(act == LFUN_CHAR_FORWARD_SELECT);
4191 bool const cur_moved = cursorForward(cur);
4192 needsUpdate |= cur_moved;
4194 if (!cur_moved && cur.depth() > 1
4195 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4197 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4199 // we will be moving out the inset, so we should execute
4200 // the depm-mechanism.
4201 // The cursor hasn't changed yet. To give the DEPM the
4202 // possibility of doing something we must provide it with
4203 // two different cursors.
4205 dummy.pos() = dummy.pit() = 0;
4206 if (cur.bv().checkDepm(dummy, cur))
4207 cur.forceBufferUpdate();
4212 case LFUN_CHAR_BACKWARD:
4213 case LFUN_CHAR_BACKWARD_SELECT: {
4214 //lyxerr << "handle LFUN_CHAR_BACKWARD[_SELECT]:\n" << cur << endl;
4215 needsUpdate |= cur.selHandle(act == LFUN_CHAR_BACKWARD_SELECT);
4216 bool const cur_moved = cursorBackward(cur);
4217 needsUpdate |= cur_moved;
4219 if (!cur_moved && cur.depth() > 1
4220 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4222 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4224 // we will be moving out the inset, so we should execute
4225 // the depm-mechanism.
4226 // The cursor hasn't changed yet. To give the DEPM the
4227 // possibility of doing something we must provide it with
4228 // two different cursors.
4230 dummy.pos() = cur.lastpos();
4231 dummy.pit() = cur.lastpit();
4232 if (cur.bv().checkDepm(dummy, cur))
4233 cur.forceBufferUpdate();
4238 case LFUN_CHAR_LEFT:
4239 case LFUN_CHAR_LEFT_SELECT:
4240 if (lyxrc.visual_cursor) {
4241 needsUpdate |= cur.selHandle(act == LFUN_CHAR_LEFT_SELECT);
4242 bool const cur_moved = cursorVisLeft(cur);
4243 needsUpdate |= cur_moved;
4244 if (!cur_moved && cur.depth() > 1
4245 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4247 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4250 if (cur.reverseDirectionNeeded()) {
4251 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4252 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4254 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4255 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4262 case LFUN_CHAR_RIGHT:
4263 case LFUN_CHAR_RIGHT_SELECT:
4264 if (lyxrc.visual_cursor) {
4265 needsUpdate |= cur.selHandle(cmd.action() == LFUN_CHAR_RIGHT_SELECT);
4266 bool const cur_moved = cursorVisRight(cur);
4267 needsUpdate |= cur_moved;
4268 if (!cur_moved && cur.depth() > 1
4269 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4271 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4274 if (cur.reverseDirectionNeeded()) {
4275 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4276 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4278 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4279 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4287 case LFUN_UP_SELECT:
4288 case LFUN_DOWN_SELECT:
4291 // stop/start the selection
4292 bool select = cmd.action() == LFUN_DOWN_SELECT ||
4293 cmd.action() == LFUN_UP_SELECT;
4295 // move cursor up/down
4296 bool up = cmd.action() == LFUN_UP_SELECT || cmd.action() == LFUN_UP;
4297 bool const atFirstOrLastRow = cur.atFirstOrLastRow(up);
4299 if (!atFirstOrLastRow) {
4300 needsUpdate |= cur.selHandle(select);
4301 cur.upDownInText(up, needsUpdate);
4302 needsUpdate |= cur.beforeDispatchCursor().inMathed();
4304 pos_type newpos = up ? 0 : cur.lastpos();
4305 if (lyxrc.mac_like_cursor_movement && cur.pos() != newpos) {
4306 needsUpdate |= cur.selHandle(select);
4307 // we do not reset the targetx of the cursor
4309 needsUpdate |= bv->checkDepm(cur, bv->cursor());
4310 cur.updateTextTargetOffset();
4312 cur.forceBufferUpdate();
4316 // if the cursor cannot be moved up or down do not remove
4317 // the selection right now, but wait for the next dispatch.
4319 needsUpdate |= cur.selHandle(select);
4320 cur.upDownInText(up, needsUpdate);
4327 case LFUN_PARAGRAPH_SELECT:
4329 needsUpdate |= setCursor(cur, cur.pit(), 0);
4330 needsUpdate |= cur.selHandle(true);
4331 if (cur.pos() < cur.lastpos())
4332 needsUpdate |= setCursor(cur, cur.pit(), cur.lastpos());
4335 case LFUN_PARAGRAPH_UP:
4336 case LFUN_PARAGRAPH_UP_SELECT:
4337 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_UP_SELECT);
4338 needsUpdate |= cursorUpParagraph(cur);
4341 case LFUN_PARAGRAPH_DOWN:
4342 case LFUN_PARAGRAPH_DOWN_SELECT:
4343 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_DOWN_SELECT);
4344 needsUpdate |= cursorDownParagraph(cur);
4347 case LFUN_LINE_BEGIN:
4348 case LFUN_LINE_BEGIN_SELECT:
4349 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_BEGIN_SELECT);
4350 needsUpdate |= tm->cursorHome(cur);
4354 case LFUN_LINE_END_SELECT:
4355 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_END_SELECT);
4356 needsUpdate |= tm->cursorEnd(cur);
4359 case LFUN_SECTION_SELECT: {
4360 Buffer const & buf = *cur.buffer();
4361 pit_type const pit = cur.pit();
4362 ParagraphList & pars = buf.text().paragraphs();
4363 ParagraphList::iterator bgn = pars.begin();
4364 // The first paragraph of the area to be selected:
4365 ParagraphList::iterator start = pars.iterator_at(pit);
4366 // The final paragraph of area to be selected:
4367 ParagraphList::iterator finish = start;
4368 ParagraphList::iterator end = pars.end();
4370 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
4371 if (thistoclevel == Layout::NOT_IN_TOC)
4375 Cursor const old_cur = cur;
4376 needsUpdate |= cur.selHandle(true);
4378 // Move out (down) from this section header
4382 // Seek the one (on same level) below
4383 for (; finish != end; ++finish, ++cur.pit()) {
4384 int const toclevel = buf.text().getTocLevel(distance(bgn, finish));
4385 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
4388 cur.pos() = cur.lastpos();
4389 cur.boundary(false);
4390 cur.setCurrentFont();
4392 needsUpdate |= cur != old_cur;
4396 case LFUN_WORD_RIGHT:
4397 case LFUN_WORD_RIGHT_SELECT:
4398 if (lyxrc.visual_cursor) {
4399 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_RIGHT_SELECT);
4400 bool const cur_moved = cursorVisRightOneWord(cur);
4401 needsUpdate |= cur_moved;
4402 if (!cur_moved && cur.depth() > 1
4403 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4405 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4408 if (cur.reverseDirectionNeeded()) {
4409 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4410 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4412 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4413 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4420 case LFUN_WORD_FORWARD:
4421 case LFUN_WORD_FORWARD_SELECT: {
4422 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_FORWARD_SELECT);
4423 bool const cur_moved = cursorForwardOneWord(cur);
4424 needsUpdate |= cur_moved;
4426 if (!cur_moved && cur.depth() > 1
4427 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4429 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4431 // we will be moving out the inset, so we should execute
4432 // the depm-mechanism.
4433 // The cursor hasn't changed yet. To give the DEPM the
4434 // possibility of doing something we must provide it with
4435 // two different cursors.
4437 dummy.pos() = dummy.pit() = 0;
4438 if (cur.bv().checkDepm(dummy, cur))
4439 cur.forceBufferUpdate();
4444 case LFUN_WORD_LEFT:
4445 case LFUN_WORD_LEFT_SELECT:
4446 if (lyxrc.visual_cursor) {
4447 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_LEFT_SELECT);
4448 bool const cur_moved = cursorVisLeftOneWord(cur);
4449 needsUpdate |= cur_moved;
4450 if (!cur_moved && cur.depth() > 1
4451 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4453 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4456 if (cur.reverseDirectionNeeded()) {
4457 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4458 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4460 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4461 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4468 case LFUN_WORD_BACKWARD:
4469 case LFUN_WORD_BACKWARD_SELECT: {
4470 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_BACKWARD_SELECT);
4471 bool const cur_moved = cursorBackwardOneWord(cur);
4472 needsUpdate |= cur_moved;
4474 if (!cur_moved && cur.depth() > 1
4475 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4477 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4479 // we will be moving out the inset, so we should execute
4480 // the depm-mechanism.
4481 // The cursor hasn't changed yet. To give the DEPM the
4482 // possibility of doing something we must provide it with
4483 // two different cursors.
4485 dummy.pos() = cur.lastpos();
4486 dummy.pit() = cur.lastpit();
4487 if (cur.bv().checkDepm(dummy, cur))
4488 cur.forceBufferUpdate();
4493 case LFUN_WORD_SELECT: {
4494 selectWord(cur, WHOLE_WORD);
4495 finishChange(cur, true);
4499 case LFUN_NEWLINE_INSERT: {
4500 InsetNewlineParams inp;
4501 docstring const & arg = cmd.argument();
4502 if (arg == "linebreak")
4503 inp.kind = InsetNewlineParams::LINEBREAK;
4505 inp.kind = InsetNewlineParams::NEWLINE;
4506 cap::replaceSelection(cur);
4508 cur.insert(new InsetNewline(inp));
4510 moveCursor(cur, false);
4514 case LFUN_TAB_INSERT: {
4515 bool const multi_par_selection = cur.selection() &&
4516 cur.selBegin().pit() != cur.selEnd().pit();
4517 if (multi_par_selection) {
4518 // If there is a multi-paragraph selection, a tab is inserted
4519 // at the beginning of each paragraph.
4520 cur.recordUndoSelection();
4521 pit_type const pit_end = cur.selEnd().pit();
4522 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4523 pars_[pit].insertChar(0, '\t',
4524 bv->buffer().params().track_changes);
4525 // Update the selection pos to make sure the selection does not
4526 // change as the inserted tab will increase the logical pos.
4527 if (cur.realAnchor().pit() == pit)
4528 cur.realAnchor().forwardPos();
4529 if (cur.pit() == pit)
4534 // Maybe we shouldn't allow tabs within a line, because they
4535 // are not (yet) aligned as one might do expect.
4536 FuncRequest ncmd(LFUN_SELF_INSERT, from_ascii("\t"));
4537 dispatch(cur, ncmd);
4542 case LFUN_TAB_DELETE: {
4543 bool const tc = bv->buffer().params().track_changes;
4544 if (cur.selection()) {
4545 // If there is a selection, a tab (if present) is removed from
4546 // the beginning of each paragraph.
4547 cur.recordUndoSelection();
4548 pit_type const pit_end = cur.selEnd().pit();
4549 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4550 Paragraph & par = paragraphs()[pit];
4553 char_type const c = par.getChar(0);
4554 if (c == '\t' || c == ' ') {
4555 // remove either 1 tab or 4 spaces.
4556 int const n = (c == ' ' ? 4 : 1);
4557 for (int i = 0; i < n
4558 && !par.empty() && par.getChar(0) == c; ++i) {
4559 if (cur.pit() == pit)
4561 if (cur.realAnchor().pit() == pit
4562 && cur.realAnchor().pos() > 0 )
4563 cur.realAnchor().backwardPos();
4564 par.eraseChar(0, tc);
4570 // If there is no selection, try to remove a tab or some spaces
4571 // before the position of the cursor.
4572 Paragraph & par = paragraphs()[cur.pit()];
4573 pos_type const pos = cur.pos();
4578 char_type const c = par.getChar(pos - 1);
4582 par.eraseChar(cur.pos(), tc);
4584 for (int n_spaces = 0;
4586 && par.getChar(cur.pos() - 1) == ' '
4590 par.eraseChar(cur.pos(), tc);
4597 case LFUN_CHAR_DELETE_FORWARD:
4598 if (!cur.selection()) {
4599 if (cur.pos() == cur.paragraph().size())
4600 // Par boundary, force full-screen update
4601 singleParUpdate = false;
4602 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion()) {
4604 cur.selection(true);
4609 needsUpdate |= erase(cur);
4612 cutSelection(cur, false);
4613 cur.setCurrentFont();
4614 singleParUpdate = false;
4616 moveCursor(cur, false);
4619 case LFUN_CHAR_DELETE_BACKWARD:
4620 if (!cur.selection()) {
4621 if (bv->getIntl().getTransManager().backspace()) {
4622 bool par_boundary = cur.pos() == 0;
4623 bool first_par = cur.pit() == 0;
4624 // Par boundary, full-screen update
4626 singleParUpdate = false;
4627 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion(true)) {
4629 cur.selection(true);
4634 needsUpdate |= backspace(cur);
4636 if (par_boundary && !first_par && cur.pos() > 0
4637 && cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
4638 needsUpdate |= backspace(cur);
4643 DocIterator const dit = cur.selectionBegin();
4644 cutSelection(cur, false);
4645 if (cur.buffer()->params().track_changes)
4646 // since we're doing backwards deletion,
4647 // and the selection is not really cut,
4648 // move cursor before selection (#11630)
4650 cur.setCurrentFont();
4651 singleParUpdate = false;
4655 case LFUN_PARAGRAPH_BREAK: {
4656 cap::replaceSelection(cur);
4657 pit_type pit = cur.pit();
4658 Paragraph const & par = pars_[pit];
4659 bool lastpar = (pit == pit_type(pars_.size() - 1));
4660 Paragraph const & nextpar = lastpar ? par : pars_[pit + 1];
4661 pit_type prev = pit > 0 ? depthHook(pit, par.getDepth()) : pit;
4662 if (prev < pit && cur.pos() == par.beginOfBody()
4663 && par.empty() && !par.isEnvSeparator(cur.pos())
4664 && !par.layout().keepempty
4665 && !par.layout().isCommand()
4666 && pars_[prev].layout() != par.layout()
4667 && pars_[prev].layout().isEnvironment()
4668 && !nextpar.isEnvSeparator(nextpar.beginOfBody())) {
4669 if (par.layout().isEnvironment()
4670 && pars_[prev].getDepth() == par.getDepth()) {
4671 docstring const layout = par.layout().name();
4672 DocumentClass const & tc = bv->buffer().params().documentClass();
4673 lyx::dispatch(FuncRequest(LFUN_LAYOUT, tc.plainLayout().name()));
4674 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4675 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
4676 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
4678 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4679 breakParagraph(cur);
4681 Font const f(inherit_font, cur.current_font.language());
4682 pars_[cur.pit() - 1].resetFonts(f);
4684 if (par.isEnvSeparator(cur.pos()) && cmd.getArg(1) != "ignoresep")
4686 breakParagraph(cur, cmd.getArg(0) == "inverse");
4689 // If we have a list and autoinsert item insets,
4691 Layout::LaTeXArgMap args = par.layout().args();
4692 for (auto const & thearg : args) {
4693 Layout::latexarg arg = thearg.second;
4694 if (arg.autoinsert && prefixIs(thearg.first, "item:")) {
4695 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, thearg.first);
4696 lyx::dispatch(cmd2);
4702 case LFUN_INSET_INSERT: {
4705 // We have to avoid triggering InstantPreview loading
4706 // before inserting into the document. See bug #5626.
4707 bool loaded = bv->buffer().isFullyLoaded();
4708 bv->buffer().setFullyLoaded(false);
4709 Inset * inset = createInset(&bv->buffer(), cmd);
4710 bv->buffer().setFullyLoaded(loaded);
4713 // FIXME (Abdel 01/02/2006):
4714 // What follows would be a partial fix for bug 2154:
4715 // http://www.lyx.org/trac/ticket/2154
4716 // This automatically put the label inset _after_ a
4717 // numbered section. It should be possible to extend the mechanism
4718 // to any kind of LateX environement.
4719 // The correct way to fix that bug would be at LateX generation.
4720 // I'll let the code here for reference as it could be used for some
4721 // other feature like "automatic labelling".
4723 Paragraph & par = pars_[cur.pit()];
4724 if (inset->lyxCode() == LABEL_CODE
4725 && !par.layout().counter.empty()) {
4726 // Go to the end of the paragraph
4727 // Warning: Because of Change-Tracking, the last
4728 // position is 'size()' and not 'size()-1':
4729 cur.pos() = par.size();
4730 // Insert a new paragraph
4731 FuncRequest fr(LFUN_PARAGRAPH_BREAK);
4735 if (cur.selection())
4736 cutSelection(cur, false);
4738 cur.forceBufferUpdate();
4739 if (inset->editable() && inset->asInsetText())
4740 inset->edit(cur, true);
4744 // trigger InstantPreview now
4745 if (inset->lyxCode() == EXTERNAL_CODE) {
4746 InsetExternal & ins =
4747 static_cast<InsetExternal &>(*inset);
4748 ins.updatePreview();
4755 case LFUN_INSET_DISSOLVE: {
4756 if (dissolveInset(cur)) {
4758 cur.forceBufferUpdate();
4763 case LFUN_INSET_SPLIT: {
4764 if (splitInset(cur)) {
4766 cur.forceBufferUpdate();
4771 case LFUN_GRAPHICS_SET_GROUP: {
4772 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
4778 string id = to_utf8(cmd.argument());
4779 string grp = graphics::getGroupParams(bv->buffer(), id);
4780 InsetGraphicsParams tmp, inspar = ins->getParams();
4783 inspar.groupId = to_utf8(cmd.argument());
4785 InsetGraphics::string2params(grp, bv->buffer(), tmp);
4786 tmp.filename = inspar.filename;
4790 ins->setParams(inspar);
4794 case LFUN_SPACE_INSERT:
4795 if (cur.paragraph().layout().free_spacing)
4796 insertChar(cur, ' ');
4798 doInsertInset(cur, this, cmd, false, false);
4801 moveCursor(cur, false);
4804 case LFUN_SPECIALCHAR_INSERT: {
4805 string const name = to_utf8(cmd.argument());
4806 if (name == "hyphenation")
4807 specialChar(cur, InsetSpecialChar::HYPHENATION);
4808 else if (name == "allowbreak")
4809 specialChar(cur, InsetSpecialChar::ALLOWBREAK);
4810 else if (name == "ligature-break")
4811 specialChar(cur, InsetSpecialChar::LIGATURE_BREAK);
4812 else if (name == "slash")
4813 specialChar(cur, InsetSpecialChar::SLASH);
4814 else if (name == "nobreakdash")
4815 specialChar(cur, InsetSpecialChar::NOBREAKDASH);
4816 else if (name == "dots")
4817 specialChar(cur, InsetSpecialChar::LDOTS);
4818 else if (name == "end-of-sentence")
4819 specialChar(cur, InsetSpecialChar::END_OF_SENTENCE);
4820 else if (name == "menu-separator")
4821 specialChar(cur, InsetSpecialChar::MENU_SEPARATOR);
4822 else if (name == "lyx")
4823 specialChar(cur, InsetSpecialChar::PHRASE_LYX);
4824 else if (name == "tex")
4825 specialChar(cur, InsetSpecialChar::PHRASE_TEX);
4826 else if (name == "latex")
4827 specialChar(cur, InsetSpecialChar::PHRASE_LATEX);
4828 else if (name == "latex2e")
4829 specialChar(cur, InsetSpecialChar::PHRASE_LATEX2E);
4830 else if (name.empty())
4831 lyxerr << "LyX function 'specialchar-insert' needs an argument." << endl;
4833 lyxerr << "Wrong argument for LyX function 'specialchar-insert'." << endl;
4837 case LFUN_IPAMACRO_INSERT: {
4838 string const arg = cmd.getArg(0);
4839 if (arg == "deco") {
4840 // Open the inset, and move the current selection
4842 doInsertInset(cur, this, cmd, true, true);
4844 // Some insets are numbered, others are shown in the outline pane so
4845 // let's update the labels and the toc backend.
4846 cur.forceBufferUpdate();
4849 if (arg == "tone-falling")
4850 ipaChar(cur, InsetIPAChar::TONE_FALLING);
4851 else if (arg == "tone-rising")
4852 ipaChar(cur, InsetIPAChar::TONE_RISING);
4853 else if (arg == "tone-high-rising")
4854 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING);
4855 else if (arg == "tone-low-rising")
4856 ipaChar(cur, InsetIPAChar::TONE_LOW_RISING);
4857 else if (arg == "tone-high-rising-falling")
4858 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING_FALLING);
4859 else if (arg.empty())
4860 lyxerr << "LyX function 'ipamacro-insert' needs an argument." << endl;
4862 lyxerr << "Wrong argument for LyX function 'ipamacro-insert'." << endl;
4866 case LFUN_WORD_UPCASE:
4867 changeCase(cur, text_uppercase, cmd.getArg(0) == "partial");
4870 case LFUN_WORD_LOWCASE:
4871 changeCase(cur, text_lowercase, cmd.getArg(0) == "partial");
4874 case LFUN_WORD_CAPITALIZE:
4875 changeCase(cur, text_capitalization, cmd.getArg(0) == "partial");
4878 case LFUN_CHARS_TRANSPOSE:
4879 charsTranspose(cur);
4883 cur.message(_("Paste"));
4884 LASSERT(cur.selBegin().idx() == cur.selEnd().idx(), break);
4885 cap::replaceSelection(cur);
4887 // without argument?
4888 string const arg = to_utf8(cmd.argument());
4890 bool tryGraphics = true;
4891 if (theClipboard().isInternal())
4892 pasteFromStack(cur, bv->buffer().errorList("Paste"), 0);
4893 else if (theClipboard().hasTextContents()) {
4894 if (pasteClipboardText(cur, bv->buffer().errorList("Paste"),
4895 !cur.paragraph().parbreakIsNewline(),
4896 Clipboard::AnyTextType))
4897 tryGraphics = false;
4899 if (tryGraphics && theClipboard().hasGraphicsContents())
4900 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"));
4901 } else if (isStrUnsignedInt(arg)) {
4902 // we have a numerical argument
4903 pasteFromStack(cur, bv->buffer().errorList("Paste"),
4904 convert<unsigned int>(arg));
4905 } else if (arg == "html" || arg == "latex") {
4906 Clipboard::TextType type = (arg == "html") ?
4907 Clipboard::HtmlTextType : Clipboard::LaTeXTextType;
4908 pasteClipboardText(cur, bv->buffer().errorList("Paste"), true, type);
4910 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
4912 type = Clipboard::PdfGraphicsType;
4913 else if (arg == "png")
4914 type = Clipboard::PngGraphicsType;
4915 else if (arg == "jpeg")
4916 type = Clipboard::JpegGraphicsType;
4917 else if (arg == "linkback")
4918 type = Clipboard::LinkBackGraphicsType;
4919 else if (arg == "emf")
4920 type = Clipboard::EmfGraphicsType;
4921 else if (arg == "wmf")
4922 type = Clipboard::WmfGraphicsType;
4924 // we also check in getStatus()
4925 LYXERR0("Unrecognized graphics type: " << arg);
4927 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"), type);
4930 bv->buffer().errors("Paste");
4931 bv->buffer().updatePreviews(); // bug 11619
4932 cur.clearSelection(); // bug 393
4938 cutSelection(cur, true);
4939 cur.message(_("Cut"));
4942 case LFUN_SERVER_GET_XY:
4943 cur.message(from_utf8(
4944 convert<string>(tm->cursorX(cur.top(), cur.boundary()))
4945 + ' ' + convert<string>(tm->cursorY(cur.top(), cur.boundary()))));
4948 case LFUN_SERVER_SET_XY: {
4951 istringstream is(to_utf8(cmd.argument()));
4954 lyxerr << "SETXY: Could not parse coordinates in '"
4955 << to_utf8(cmd.argument()) << endl;
4957 tm->setCursorFromCoordinates(cur, x, y);
4961 case LFUN_SERVER_GET_LAYOUT:
4962 cur.message(cur.paragraph().layout().name());
4966 case LFUN_LAYOUT_TOGGLE: {
4967 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
4968 docstring req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
4969 LYXERR(Debug::INFO, "LFUN_LAYOUT: (arg) " << to_utf8(req_layout));
4971 docstring layout = resolveLayout(req_layout, cur);
4972 if (layout.empty()) {
4973 cur.errorMessage(from_utf8(N_("Layout ")) + req_layout +
4974 from_utf8(N_(" not known")));
4978 docstring const old_layout = cur.paragraph().layout().name();
4979 bool change_layout = !isAlreadyLayout(layout, cur);
4981 if (cmd.action() == LFUN_LAYOUT_TOGGLE && !change_layout) {
4982 change_layout = true;
4983 layout = resolveLayout(docstring(), cur);
4986 if (change_layout) {
4987 setLayout(cur, layout);
4988 if (cur.pit() > 0 && !ignoreautonests) {
4989 pit_type prev_pit = cur.pit() - 1;
4990 depth_type const cur_depth = pars_[cur.pit()].getDepth();
4991 // Scan for the previous par on same nesting level
4992 while (prev_pit > 0 && pars_[prev_pit].getDepth() > cur_depth)
4994 set<docstring> const & autonests =
4995 pars_[prev_pit].layout().autonests();
4996 set<docstring> const & autonested =
4997 pars_[cur.pit()].layout().isAutonestedBy();
4998 if (autonests.find(layout) != autonests.end()
4999 || autonested.find(old_layout) != autonested.end())
5000 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5004 DocumentClass const & tclass = bv->buffer().params().documentClass();
5005 bool inautoarg = false;
5006 for (auto const & la_pair : tclass[layout].args()) {
5007 Layout::latexarg const & arg = la_pair.second;
5008 if (arg.autoinsert) {
5009 // If we had already inserted an arg automatically,
5010 // leave this now in order to insert the next one.
5012 cur.leaveInset(cur.inset());
5015 FuncRequest const cmd2(LFUN_ARGUMENT_INSERT, la_pair.first);
5016 lyx::dispatch(cmd2);
5024 case LFUN_ENVIRONMENT_SPLIT: {
5025 bool const outer = cmd.argument() == "outer";
5026 bool const previous = cmd.argument() == "previous";
5027 bool const before = cmd.argument() == "before";
5028 bool const normal = cmd.argument().empty();
5029 Paragraph const & para = cur.paragraph();
5031 if (para.layout().isEnvironment())
5032 layout = para.layout().name();
5033 depth_type split_depth = cur.paragraph().params().depth();
5034 vector<depth_type> nextpars_depth;
5035 if (outer || previous) {
5036 // check if we have an environment in our scope
5037 pit_type pit = cur.pit();
5038 Paragraph cpar = pars_[pit];
5044 if (layout.empty() && previous
5045 && cpar.layout().isEnvironment()
5046 && cpar.params().depth() <= split_depth)
5047 layout = cpar.layout().name();
5048 if (cpar.params().depth() < split_depth
5049 && cpar.layout().isEnvironment()) {
5051 layout = cpar.layout().name();
5052 split_depth = cpar.params().depth();
5054 if (cpar.params().depth() == 0)
5058 if ((outer || normal) && cur.pit() < cur.lastpit()) {
5059 // save nesting of following paragraphs if they are deeper
5061 pit_type offset = 1;
5062 depth_type cur_depth = pars_[cur.pit()].params().depth();
5063 while (cur.pit() + offset <= cur.lastpit()) {
5064 Paragraph cpar = pars_[cur.pit() + offset];
5065 depth_type nextpar_depth = cpar.params().depth();
5066 if (cur_depth <= nextpar_depth && nextpar_depth > 0) {
5067 nextpars_depth.push_back(nextpar_depth);
5068 cur_depth = nextpar_depth;
5075 cur.top().setPitPos(cur.pit(), 0);
5076 if (before || cur.pos() > 0)
5077 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
5078 else if (previous && cur.nextInset() && cur.nextInset()->lyxCode() == SEPARATOR_CODE)
5079 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5081 while (cur.paragraph().params().depth() > split_depth)
5082 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
5084 DocumentClass const & tc = bv->buffer().params().documentClass();
5085 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
5086 + from_ascii("\" ignoreautonests")));
5087 // FIXME: Bibitem mess!
5088 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
5089 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
5090 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
5093 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5094 while (cur.paragraph().params().depth() < split_depth)
5095 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5098 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
5099 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
5100 if ((outer || normal) && !nextpars_depth.empty()) {
5101 // restore nesting of following paragraphs
5102 DocIterator scur = cur;
5103 depth_type max_depth = cur.paragraph().params().depth() + 1;
5104 for (auto nextpar_depth : nextpars_depth) {
5106 while (cur.paragraph().params().depth() < min(nextpar_depth, max_depth)) {
5107 depth_type const olddepth = cur.paragraph().params().depth();
5108 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5109 if (olddepth == cur.paragraph().params().depth())
5110 // leave loop if no incrementation happens
5113 max_depth = cur.paragraph().params().depth() + 1;
5115 cur.setCursor(scur);
5121 case LFUN_CLIPBOARD_PASTE:
5122 cap::replaceSelection(cur);
5123 pasteClipboardText(cur, bv->buffer().errorList("Paste"),
5124 cmd.argument() == "paragraph");
5125 bv->buffer().errors("Paste");
5128 case LFUN_CLIPBOARD_PASTE_SIMPLE:
5129 cap::replaceSelection(cur);
5130 pasteSimpleText(cur, cmd.argument() == "paragraph");
5133 case LFUN_PRIMARY_SELECTION_PASTE:
5134 cap::replaceSelection(cur);
5135 pasteString(cur, theSelection().get(),
5136 cmd.argument() == "paragraph");
5139 case LFUN_SELECTION_PASTE:
5140 // Copy the selection buffer to the clipboard stack,
5141 // because we want it to appear in the "Edit->Paste
5143 cap::replaceSelection(cur);
5144 cap::copySelectionToStack();
5145 cap::pasteSelection(bv->cursor(), bv->buffer().errorList("Paste"));
5146 bv->buffer().errors("Paste");
5149 case LFUN_QUOTE_INSERT: {
5150 cap::replaceSelection(cur);
5153 Paragraph const & par = cur.paragraph();
5154 pos_type pos = cur.pos();
5155 // Ignore deleted text before cursor
5156 while (pos > 0 && par.isDeleted(pos - 1))
5159 bool const inner = (cmd.getArg(0) == "single" || cmd.getArg(0) == "inner");
5161 // Guess quote side.
5162 // A space triggers an opening quote. This is passed if the preceding
5163 // char/inset is a space or at paragraph start.
5165 if (pos > 0 && !par.isSpace(pos - 1)) {
5166 if (cur.prevInset() && cur.prevInset()->lyxCode() == QUOTE_CODE) {
5167 // If an opening double quotation mark precedes, and this
5168 // is a single quote, make it opening as well
5170 static_cast<InsetQuotes &>(*cur.prevInset());
5171 string const type = ins.getType();
5172 if (!suffixIs(type, "ld") || !inner)
5173 c = par.getChar(pos - 1);
5175 else if (!cur.prevInset()
5176 || (cur.prevInset() && cur.prevInset()->isChar()))
5177 // If a char precedes, pass that and let InsetQuote decide
5178 c = par.getChar(pos - 1);
5181 if (par.getInset(pos - 1)
5182 && !par.getInset(pos - 1)->isPartOfTextSequence()) {
5183 // skip "invisible" insets
5187 c = par.getChar(pos - 1);
5192 QuoteLevel const quote_level = inner
5193 ? QuoteLevel::Secondary : QuoteLevel::Primary;
5194 cur.insert(new InsetQuotes(cur.buffer(), c, quote_level, cmd.getArg(1), cmd.getArg(2)));
5195 cur.buffer()->updateBuffer();
5200 case LFUN_MOUSE_TRIPLE:
5201 if (cmd.button() == mouse_button::button1) {
5203 setCursor(cur, cur.pit(), 0);
5206 if (cur.pos() < cur.lastpos())
5207 setCursor(cur, cur.pit(), cur.lastpos());
5213 case LFUN_MOUSE_DOUBLE:
5214 if (cmd.button() == mouse_button::button1) {
5215 selectWord(cur, WHOLE_WORD);
5220 // Single-click on work area
5221 case LFUN_MOUSE_PRESS: {
5222 // We are not marking a selection with the keyboard in any case.
5223 Cursor & bvcur = cur.bv().cursor();
5224 bvcur.setMark(false);
5225 switch (cmd.button()) {
5226 case mouse_button::button1:
5227 if (!bvcur.selection())
5229 bvcur.resetAnchor();
5230 if (!bv->mouseSetCursor(cur, cmd.modifier() == ShiftModifier))
5231 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5232 // FIXME: move this to mouseSetCursor?
5233 if (bvcur.wordSelection() && bvcur.inTexted())
5234 expandWordSel(bvcur);
5237 case mouse_button::button2:
5238 if (lyxrc.mouse_middlebutton_paste) {
5239 // Middle mouse pasting.
5240 bv->mouseSetCursor(cur);
5242 FuncRequest(LFUN_COMMAND_ALTERNATIVES,
5243 "selection-paste ; primary-selection-paste paragraph"));
5245 cur.noScreenUpdate();
5248 case mouse_button::button3: {
5249 // Don't do anything if we right-click a
5250 // selection, a context menu will popup.
5251 if (bvcur.selection() && cur >= bvcur.selectionBegin()
5252 && cur <= bvcur.selectionEnd()) {
5253 cur.noScreenUpdate();
5256 if (!bv->mouseSetCursor(cur, false))
5257 cur.screenUpdateFlags(Update::FitCursor);
5263 } // switch (cmd.button())
5266 case LFUN_MOUSE_MOTION: {
5267 // Mouse motion with right or middle mouse do nothing for now.
5268 if (cmd.button() != mouse_button::button1) {
5269 cur.noScreenUpdate();
5272 // ignore motions deeper nested than the real anchor
5273 Cursor & bvcur = cur.bv().cursor();
5274 if (!bvcur.realAnchor().hasPart(cur)) {
5278 CursorSlice old = bvcur.top();
5280 int const wh = bv->workHeight();
5281 int const y = max(0, min(wh - 1, cmd.y()));
5283 tm->setCursorFromCoordinates(cur, cmd.x(), y);
5284 cur.setTargetX(cmd.x());
5285 // Don't allow selecting a separator inset
5286 if (cur.pos() && cur.paragraph().isEnvSeparator(cur.pos() - 1))
5289 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5290 else if (cmd.y() < 0)
5291 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5292 // This is to allow jumping over large insets
5293 if (cur.top() == old) {
5295 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5296 else if (cmd.y() < 0)
5297 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5299 // We continue with our existing selection or start a new one, so don't
5300 // reset the anchor.
5301 bvcur.setCursor(cur);
5302 if (bvcur.wordSelection() && bvcur.inTexted())
5303 expandWordSel(bvcur);
5304 bvcur.selection(true);
5305 bvcur.setCurrentFont();
5306 if (cur.top() == old) {
5307 // We didn't move one iota, so no need to update the screen.
5308 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5309 //cur.noScreenUpdate();
5315 case LFUN_MOUSE_RELEASE:
5316 switch (cmd.button()) {
5317 case mouse_button::button1:
5318 // Cursor was set at LFUN_MOUSE_PRESS or LFUN_MOUSE_MOTION time.
5319 // If there is a new selection, update persistent selection;
5320 // otherwise, single click does not clear persistent selection
5322 if (cur.selection()) {
5323 // Finish selection. If double click,
5324 // cur is moved to the end of word by
5325 // selectWord but bvcur is current
5327 cur.bv().cursor().setSelection();
5328 // We might have removed an empty but drawn selection
5329 // (probably a margin)
5330 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5332 cur.noScreenUpdate();
5333 // FIXME: We could try to handle drag and drop of selection here.
5336 case mouse_button::button2:
5337 // Middle mouse pasting is handled at mouse press time,
5338 // see LFUN_MOUSE_PRESS.
5339 cur.noScreenUpdate();
5342 case mouse_button::button3:
5343 // Cursor was set at LFUN_MOUSE_PRESS time.
5344 // FIXME: If there is a selection we could try to handle a special
5345 // drag & drop context menu.
5346 cur.noScreenUpdate();
5349 case mouse_button::none:
5350 case mouse_button::button4:
5351 case mouse_button::button5:
5353 } // switch (cmd.button())
5357 case LFUN_SELF_INSERT: {
5358 if (cmd.argument().empty())
5361 // Automatically delete the currently selected
5362 // text and replace it with what is being
5363 // typed in now. Depends on lyxrc settings
5364 // "auto_region_delete", which defaults to
5367 if (lyxrc.auto_region_delete && cur.selection()) {
5368 cutSelection(cur, false);
5369 cur.setCurrentFont();
5371 cur.clearSelection();
5373 for (char_type c : cmd.argument())
5374 bv->translateAndInsert(c, this, cur);
5377 moveCursor(cur, false);
5378 cur.markNewWordPosition();
5379 bv->bookmarkEditPosition();
5383 case LFUN_HREF_INSERT: {
5384 docstring content = cmd.argument();
5385 if (content.empty() && cur.selection())
5386 content = cur.selectionAsString(false);
5388 InsetCommandParams p(HYPERLINK_CODE);
5389 if (!content.empty()){
5390 // if it looks like a link, we'll put it as target,
5391 // otherwise as name (bug #8792).
5394 // regex_match(to_utf8(content), matches, link_re)
5395 // because smatch stores pointers to the substrings rather
5396 // than making copies of them. And those pointers become
5397 // invalid after regex_match returns, since it is then
5398 // being given a temporary object. (Thanks to Georg for
5399 // figuring that out.)
5400 regex const link_re("^(([a-z]+):|www\\.).*");
5402 string const c = to_utf8(lowercase(content));
5404 if (c.substr(0,7) == "mailto:") {
5405 p["target"] = content;
5406 p["type"] = from_ascii("mailto:");
5407 } else if (regex_match(c, matches, link_re)) {
5408 p["target"] = content;
5409 string protocol = matches.str(1);
5410 if (protocol == "file")
5411 p["type"] = from_ascii("file:");
5413 p["name"] = content;
5415 string const data = InsetCommand::params2string(p);
5417 // we need to have a target. if we already have one, then
5418 // that gets used at the default for the name, too, which
5419 // is probably what is wanted.
5420 if (p["target"].empty()) {
5421 bv->showDialog("href", data);
5423 FuncRequest fr(LFUN_INSET_INSERT, data);
5429 case LFUN_LABEL_INSERT: {
5430 InsetCommandParams p(LABEL_CODE);
5431 // Try to generate a valid label
5432 p["name"] = (cmd.argument().empty()) ?
5433 cur.getPossibleLabel() :
5435 string const data = InsetCommand::params2string(p);
5437 if (cmd.argument().empty()) {
5438 bv->showDialog("label", data);
5440 FuncRequest fr(LFUN_INSET_INSERT, data);
5446 case LFUN_INFO_INSERT: {
5447 if (cmd.argument().empty()) {
5448 bv->showDialog("info", cur.current_font.language()->lang());
5451 inset = createInset(cur.buffer(), cmd);
5455 insertInset(cur, inset);
5456 cur.forceBufferUpdate();
5461 case LFUN_CAPTION_INSERT:
5462 case LFUN_FOOTNOTE_INSERT:
5463 case LFUN_NOTE_INSERT:
5464 case LFUN_BOX_INSERT:
5465 case LFUN_BRANCH_INSERT:
5466 case LFUN_PHANTOM_INSERT:
5467 case LFUN_ERT_INSERT:
5468 case LFUN_INDEXMACRO_INSERT:
5469 case LFUN_LISTING_INSERT:
5470 case LFUN_MARGINALNOTE_INSERT:
5471 case LFUN_ARGUMENT_INSERT:
5472 case LFUN_INDEX_INSERT:
5473 case LFUN_PREVIEW_INSERT:
5474 case LFUN_SCRIPT_INSERT:
5475 case LFUN_IPA_INSERT: {
5476 // Indexes reset font formatting (#11961)
5477 bool const resetfont = cmd.action() == LFUN_INDEX_INSERT;
5478 // Open the inset, and move the current selection
5480 doInsertInset(cur, this, cmd, true, true, resetfont);
5482 cur.setCurrentFont();
5483 // Some insets are numbered, others are shown in the outline pane so
5484 // let's update the labels and the toc backend.
5485 cur.forceBufferUpdate();
5489 case LFUN_FLEX_INSERT: {
5490 // Open the inset, and move the current selection
5492 bool const sel = cur.selection();
5493 doInsertInset(cur, this, cmd, true, true);
5494 // Insert auto-insert arguments
5495 bool autoargs = false, inautoarg = false;
5496 Layout::LaTeXArgMap args = cur.inset().getLayout().args();
5497 for (auto const & argt : args) {
5498 Layout::latexarg arg = argt.second;
5499 if (!inautoarg && arg.insertonnewline && cur.pos() > 0) {
5500 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5501 lyx::dispatch(cmd2);
5503 if (arg.autoinsert) {
5504 // The cursor might have been invalidated by the replaceSelection.
5505 cur.buffer()->changed(true);
5506 // If we had already inserted an arg automatically,
5507 // leave this now in order to insert the next one.
5509 cur.leaveInset(cur.inset());
5510 cur.setCurrentFont();
5512 if (arg.insertonnewline && cur.pos() > 0) {
5513 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5514 lyx::dispatch(cmd2);
5517 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, argt.first);
5518 lyx::dispatch(cmd2);
5525 cur.leaveInset(cur.inset());
5528 // Some insets are numbered, others are shown in the outline pane so
5529 // let's update the labels and the toc backend.
5530 cur.forceBufferUpdate();
5534 case LFUN_TABULAR_INSERT: {
5535 // if there were no arguments, just open the dialog
5536 if (cmd.argument().empty()) {
5537 bv->showDialog("tabularcreate");
5539 } else if (cur.buffer()->masterParams().tablestyle != "default"
5540 || bv->buffer().params().documentClass().tablestyle() != "default") {
5541 string tabstyle = cur.buffer()->masterParams().tablestyle;
5542 if (tabstyle == "default")
5543 tabstyle = bv->buffer().params().documentClass().tablestyle();
5544 if (!libFileSearch("tabletemplates", tabstyle + ".lyx").empty()) {
5545 FuncRequest fr(LFUN_TABULAR_STYLE_INSERT,
5546 tabstyle + " " + to_ascii(cmd.argument()));
5550 // Unknown style. Report and fall back to default.
5551 cur.errorMessage(from_utf8(N_("Table Style ")) + from_utf8(tabstyle) +
5552 from_utf8(N_(" not known")));
5554 if (doInsertInset(cur, this, cmd, false, true))
5559 case LFUN_TABULAR_STYLE_INSERT: {
5560 string const style = cmd.getArg(0);
5561 string const rows = cmd.getArg(1);
5562 string const cols = cmd.getArg(2);
5563 if (cols.empty() || !isStrInt(cols)
5564 || rows.empty() || !isStrInt(rows))
5566 int const r = convert<int>(rows);
5567 int const c = convert<int>(cols);
5574 FileName const tabstyle = libFileSearch("tabletemplates",
5575 style + suffix + ".lyx", "lyx");
5576 if (tabstyle.empty())
5578 UndoGroupHelper ugh(cur.buffer());
5580 FuncRequest cmd2(LFUN_FILE_INSERT, tabstyle.absFileName() + " ignorelang");
5581 lyx::dispatch(cmd2);
5585 // move one cell up to middle cell
5587 // add the missing rows
5588 int const addrows = r - 3;
5589 for (int i = 0 ; i < addrows ; ++i) {
5590 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-row");
5594 // add the missing columns
5595 int const addcols = c - 1;
5596 for (int i = 0 ; i < addcols ; ++i) {
5597 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-column");
5606 case LFUN_FLOAT_INSERT:
5607 case LFUN_FLOAT_WIDE_INSERT:
5608 case LFUN_WRAP_INSERT: {
5609 // will some content be moved into the inset?
5610 bool const content = cur.selection();
5611 // does the content consist of multiple paragraphs?
5612 bool const singlepar = (cur.selBegin().pit() == cur.selEnd().pit());
5614 doInsertInset(cur, this, cmd, true, true);
5617 // If some single-par content is moved into the inset,
5618 // doInsertInset puts the cursor outside the inset.
5619 // To insert the caption we put it back into the inset.
5620 // FIXME cleanup doInsertInset to avoid such dances!
5621 if (content && singlepar)
5624 ParagraphList & pars = cur.text()->paragraphs();
5626 DocumentClass const & tclass = bv->buffer().params().documentClass();
5628 // add a separate paragraph for the caption inset
5629 pars.push_back(Paragraph());
5630 pars.back().setInsetOwner(&cur.text()->inset());
5631 pars.back().setPlainOrDefaultLayout(tclass);
5632 int cap_pit = pars.size() - 1;
5634 // if an empty inset was created, we create an additional empty
5635 // paragraph at the bottom so that the user can choose where to put
5636 // the graphics (or table).
5638 pars.push_back(Paragraph());
5639 pars.back().setInsetOwner(&cur.text()->inset());
5640 pars.back().setPlainOrDefaultLayout(tclass);
5643 // reposition the cursor to the caption
5644 cur.pit() = cap_pit;
5646 // FIXME: This Text/Cursor dispatch handling is a mess!
5647 // We cannot use Cursor::dispatch here it needs access to up to
5649 FuncRequest cmd_caption(LFUN_CAPTION_INSERT);
5650 doInsertInset(cur, cur.text(), cmd_caption, true, false);
5651 cur.forceBufferUpdate();
5652 cur.screenUpdateFlags(Update::Force);
5653 // FIXME: When leaving the Float (or Wrap) inset we should
5654 // delete any empty paragraph left above or below the
5659 case LFUN_NOMENCL_INSERT: {
5660 InsetCommandParams p(NOMENCL_CODE);
5661 if (cmd.argument().empty()) {
5663 bv->cursor().innerText()->getStringForDialog(bv->cursor());
5664 cur.clearSelection();
5666 p["symbol"] = cmd.argument();
5667 string const data = InsetCommand::params2string(p);
5668 bv->showDialog("nomenclature", data);
5672 case LFUN_INDEX_PRINT: {
5673 InsetCommandParams p(INDEX_PRINT_CODE);
5674 if (cmd.argument().empty())
5675 p["type"] = from_ascii("idx");
5677 p["type"] = cmd.argument();
5678 string const data = InsetCommand::params2string(p);
5679 FuncRequest fr(LFUN_INSET_INSERT, data);
5684 case LFUN_NOMENCL_PRINT:
5685 case LFUN_NEWPAGE_INSERT:
5687 doInsertInset(cur, this, cmd, false, false);
5691 case LFUN_SEPARATOR_INSERT: {
5692 doInsertInset(cur, this, cmd, false, false);
5694 // remove a following space
5695 Paragraph & par = cur.paragraph();
5696 if (cur.pos() != cur.lastpos() && par.isLineSeparator(cur.pos()))
5697 par.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
5701 case LFUN_DEPTH_DECREMENT:
5702 changeDepth(cur, DEC_DEPTH);
5705 case LFUN_DEPTH_INCREMENT:
5706 changeDepth(cur, INC_DEPTH);
5709 case LFUN_REGEXP_MODE:
5710 regexpDispatch(cur, cmd);
5713 case LFUN_MATH_MODE: {
5714 if (cmd.argument() == "on" || cmd.argument() == "") {
5715 // don't pass "on" as argument
5716 // (it would appear literally in the first cell)
5717 docstring sel = cur.selectionAsString(false);
5718 InsetMathMacroTemplate * macro = new InsetMathMacroTemplate(cur.buffer());
5719 // create a macro template if we see "\\newcommand" somewhere, and
5720 // an ordinary formula otherwise
5722 && (sel.find(from_ascii("\\newcommand")) != string::npos
5723 || sel.find(from_ascii("\\newlyxcommand")) != string::npos
5724 || sel.find(from_ascii("\\def")) != string::npos)
5725 && macro->fromString(sel)) {
5727 replaceSelection(cur);
5730 // no meaningful macro template was found
5732 mathDispatch(cur,FuncRequest(LFUN_MATH_MODE));
5735 // The argument is meaningful
5736 // We replace cmd with LFUN_MATH_INSERT because LFUN_MATH_MODE
5737 // has a different meaning in math mode
5738 mathDispatch(cur, FuncRequest(LFUN_MATH_INSERT,cmd.argument()));
5742 case LFUN_MATH_MACRO:
5743 if (cmd.argument().empty())
5744 cur.errorMessage(from_utf8(N_("Missing argument")));
5747 string s = to_utf8(cmd.argument());
5748 string const s1 = token(s, ' ', 1);
5749 int const nargs = s1.empty() ? 0 : convert<int>(s1);
5750 string const s2 = token(s, ' ', 2);
5751 MacroType type = MacroTypeNewcommand;
5753 type = MacroTypeDef;
5754 InsetMathMacroTemplate * inset = new InsetMathMacroTemplate(cur.buffer(),
5755 from_utf8(token(s, ' ', 0)), nargs, false, type);
5756 inset->setBuffer(bv->buffer());
5757 insertInset(cur, inset);
5759 // enter macro inset and select the name
5761 cur.top().pos() = cur.top().lastpos();
5763 cur.selection(true);
5764 cur.top().pos() = 0;
5768 case LFUN_MATH_DISPLAY:
5769 case LFUN_MATH_SUBSCRIPT:
5770 case LFUN_MATH_SUPERSCRIPT:
5771 case LFUN_MATH_INSERT:
5772 case LFUN_MATH_AMS_MATRIX:
5773 case LFUN_MATH_MATRIX:
5774 case LFUN_MATH_DELIM:
5775 case LFUN_MATH_BIGDELIM:
5776 mathDispatch(cur, cmd);
5779 case LFUN_FONT_EMPH: {
5780 Font font(ignore_font, ignore_language);
5781 font.fontInfo().setEmph(FONT_TOGGLE);
5782 toggleAndShow(cur, this, font);
5786 case LFUN_FONT_ITAL: {
5787 Font font(ignore_font, ignore_language);
5788 font.fontInfo().setShape(ITALIC_SHAPE);
5789 toggleAndShow(cur, this, font);
5793 case LFUN_FONT_BOLD:
5794 case LFUN_FONT_BOLDSYMBOL: {
5795 Font font(ignore_font, ignore_language);
5796 font.fontInfo().setSeries(BOLD_SERIES);
5797 toggleAndShow(cur, this, font);
5801 case LFUN_FONT_NOUN: {
5802 Font font(ignore_font, ignore_language);
5803 font.fontInfo().setNoun(FONT_TOGGLE);
5804 toggleAndShow(cur, this, font);
5808 case LFUN_FONT_TYPEWRITER: {
5809 Font font(ignore_font, ignore_language);
5810 font.fontInfo().setFamily(TYPEWRITER_FAMILY); // no good
5811 toggleAndShow(cur, this, font);
5815 case LFUN_FONT_SANS: {
5816 Font font(ignore_font, ignore_language);
5817 font.fontInfo().setFamily(SANS_FAMILY);
5818 toggleAndShow(cur, this, font);
5822 case LFUN_FONT_ROMAN: {
5823 Font font(ignore_font, ignore_language);
5824 font.fontInfo().setFamily(ROMAN_FAMILY);
5825 toggleAndShow(cur, this, font);
5829 case LFUN_FONT_DEFAULT: {
5830 Font font(inherit_font, ignore_language);
5831 toggleAndShow(cur, this, font);
5835 case LFUN_FONT_STRIKEOUT: {
5836 Font font(ignore_font, ignore_language);
5837 font.fontInfo().setStrikeout(FONT_TOGGLE);
5838 toggleAndShow(cur, this, font);
5842 case LFUN_FONT_CROSSOUT: {
5843 Font font(ignore_font, ignore_language);
5844 font.fontInfo().setXout(FONT_TOGGLE);
5845 toggleAndShow(cur, this, font);
5849 case LFUN_FONT_UNDERUNDERLINE: {
5850 Font font(ignore_font, ignore_language);
5851 font.fontInfo().setUuline(FONT_TOGGLE);
5852 toggleAndShow(cur, this, font);
5856 case LFUN_FONT_UNDERWAVE: {
5857 Font font(ignore_font, ignore_language);
5858 font.fontInfo().setUwave(FONT_TOGGLE);
5859 toggleAndShow(cur, this, font);
5863 case LFUN_FONT_UNDERLINE: {
5864 Font font(ignore_font, ignore_language);
5865 font.fontInfo().setUnderbar(FONT_TOGGLE);
5866 toggleAndShow(cur, this, font);
5870 case LFUN_FONT_NO_SPELLCHECK: {
5871 Font font(ignore_font, ignore_language);
5872 font.fontInfo().setNoSpellcheck(FONT_TOGGLE);
5873 toggleAndShow(cur, this, font);
5877 case LFUN_FONT_SIZE: {
5878 Font font(ignore_font, ignore_language);
5879 setLyXSize(to_utf8(cmd.argument()), font.fontInfo());
5880 toggleAndShow(cur, this, font);
5884 case LFUN_LANGUAGE: {
5885 string const lang_arg = cmd.getArg(0);
5886 bool const reset = (lang_arg.empty() || lang_arg == "reset");
5887 Language const * lang =
5888 reset ? cur.bv().buffer().params().language
5889 : languages.getLanguage(lang_arg);
5890 // we allow reset_language, which is 0, but only if it
5891 // was requested via empty or "reset" arg.
5892 if (!lang && !reset)
5894 bool const toggle = (cmd.getArg(1) != "set");
5895 selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
5896 Font font(ignore_font, lang);
5897 toggleAndShow(cur, this, font, toggle);
5901 case LFUN_TEXTSTYLE_APPLY: {
5902 unsigned int num = 0;
5903 string const arg = to_utf8(cmd.argument());
5906 if (isStrUnsignedInt(arg)) {
5907 num = convert<uint>(arg);
5908 if (num >= freeFonts.size()) {
5909 cur.message(_("Invalid argument (number exceeds stack size)!"));
5913 cur.message(_("Invalid argument (must be a non-negative number)!"));
5917 toggleAndShow(cur, this, freeFonts[num].second, toggleall);
5918 cur.message(bformat(_("Text properties applied: %1$s"), freeFonts[num].first));
5922 // Set the freefont using the contents of \param data dispatched from
5923 // the frontends and apply it at the current cursor location.
5924 case LFUN_TEXTSTYLE_UPDATE: {
5925 Font font(ignore_font, ignore_language);
5927 if (font.fromString(to_utf8(cmd.argument()), toggle)) {
5928 docstring const props = font.stateText(&bv->buffer().params(), true);
5929 freeFonts.push(make_pair(props, font));
5931 toggleAndShow(cur, this, font, toggleall);
5932 cur.message(bformat(_("Text properties applied: %1$s"), props));
5934 LYXERR0("Invalid argument of textstyle-update");
5938 case LFUN_FINISHED_LEFT:
5939 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_LEFT:\n" << cur);
5940 // We're leaving an inset, going left. If the inset is LTR, we're
5941 // leaving from the front, so we should not move (remain at --- but
5942 // not in --- the inset). If the inset is RTL, move left, without
5943 // entering the inset itself; i.e., move to after the inset.
5944 if (cur.paragraph().getFontSettings(
5945 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5946 cursorVisLeft(cur, true);
5949 case LFUN_FINISHED_RIGHT:
5950 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_RIGHT:\n" << cur);
5951 // We're leaving an inset, going right. If the inset is RTL, we're
5952 // leaving from the front, so we should not move (remain at --- but
5953 // not in --- the inset). If the inset is LTR, move right, without
5954 // entering the inset itself; i.e., move to after the inset.
5955 if (!cur.paragraph().getFontSettings(
5956 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5957 cursorVisRight(cur, true);
5960 case LFUN_FINISHED_BACKWARD:
5961 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_BACKWARD:\n" << cur);
5962 cur.setCurrentFont();
5965 case LFUN_FINISHED_FORWARD:
5966 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_FORWARD:\n" << cur);
5968 cur.setCurrentFont();
5971 case LFUN_LAYOUT_PARAGRAPH: {
5973 params2string(cur.paragraph(), data);
5974 data = "show\n" + data;
5975 bv->showDialog("paragraph", data);
5979 case LFUN_PARAGRAPH_UPDATE: {
5981 params2string(cur.paragraph(), data);
5983 // Will the paragraph accept changes from the dialog?
5985 cur.inset().allowParagraphCustomization(cur.idx());
5987 data = "update " + convert<string>(accept) + '\n' + data;
5988 bv->updateDialog("paragraph", data);
5992 case LFUN_ACCENT_UMLAUT:
5993 case LFUN_ACCENT_CIRCUMFLEX:
5994 case LFUN_ACCENT_GRAVE:
5995 case LFUN_ACCENT_ACUTE:
5996 case LFUN_ACCENT_TILDE:
5997 case LFUN_ACCENT_PERISPOMENI:
5998 case LFUN_ACCENT_CEDILLA:
5999 case LFUN_ACCENT_MACRON:
6000 case LFUN_ACCENT_DOT:
6001 case LFUN_ACCENT_UNDERDOT:
6002 case LFUN_ACCENT_UNDERBAR:
6003 case LFUN_ACCENT_CARON:
6004 case LFUN_ACCENT_BREVE:
6005 case LFUN_ACCENT_TIE:
6006 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
6007 case LFUN_ACCENT_CIRCLE:
6008 case LFUN_ACCENT_OGONEK:
6009 theApp()->handleKeyFunc(cmd.action());
6010 if (!cmd.argument().empty())
6011 // FIXME: Are all these characters encoded in one byte in utf8?
6012 bv->translateAndInsert(cmd.argument()[0], this, cur);
6013 cur.screenUpdateFlags(Update::FitCursor);
6016 case LFUN_FLOAT_LIST_INSERT: {
6017 DocumentClass const & tclass = bv->buffer().params().documentClass();
6018 if (tclass.floats().typeExist(to_utf8(cmd.argument()))) {
6020 if (cur.selection())
6021 cutSelection(cur, false);
6022 breakParagraph(cur);
6024 if (cur.lastpos() != 0) {
6025 cursorBackward(cur);
6026 breakParagraph(cur);
6029 docstring const laystr = cur.inset().usePlainLayout() ?
6030 tclass.plainLayoutName() :
6031 tclass.defaultLayoutName();
6032 setLayout(cur, laystr);
6033 ParagraphParameters p;
6034 // FIXME If this call were replaced with one to clearParagraphParams(),
6035 // then we could get rid of this method altogether.
6036 setParagraphs(cur, p);
6037 // FIXME This should be simplified when InsetFloatList takes a
6038 // Buffer in its constructor.
6039 InsetFloatList * ifl = new InsetFloatList(cur.buffer(), to_utf8(cmd.argument()));
6040 ifl->setBuffer(bv->buffer());
6041 insertInset(cur, ifl);
6044 lyxerr << "Non-existent float type: "
6045 << to_utf8(cmd.argument()) << endl;
6050 case LFUN_CHANGE_ACCEPT: {
6051 acceptOrRejectChanges(cur, ACCEPT);
6055 case LFUN_CHANGE_REJECT: {
6056 acceptOrRejectChanges(cur, REJECT);
6060 case LFUN_THESAURUS_ENTRY: {
6061 Language const * language = cur.getFont().language();
6062 docstring arg = cmd.argument();
6064 arg = cur.selectionAsString(false);
6065 // Too large. We unselect if needed and try to get
6066 // the first word in selection or under cursor
6067 if (arg.size() > 100 || arg.empty()) {
6068 if (cur.selection()) {
6069 DocIterator selbeg = cur.selectionBegin();
6070 cur.clearSelection();
6071 setCursorIntern(cur, selbeg.pit(), selbeg.pos());
6072 cur.screenUpdateFlags(Update::Force);
6074 // Get word or selection
6075 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6076 arg = cur.selectionAsString(false);
6077 arg += " lang=" + from_ascii(language->lang());
6080 string lang = cmd.getArg(1);
6081 // This duplicates the code in GuiThesaurus::initialiseParams
6082 if (prefixIs(lang, "lang=")) {
6083 language = languages.getLanguage(lang.substr(5));
6085 language = cur.getFont().language();
6088 string lang = language->code();
6089 if (lyxrc.thesaurusdir_path.empty() && !thesaurus.thesaurusInstalled(from_ascii(lang))) {
6090 LYXERR(Debug::ACTION, "Command " << cmd << ". Thesaurus not found for language " << lang);
6091 frontend::Alert::warning(_("Path to thesaurus directory not set!"),
6092 _("The path to the thesaurus directory has not been specified.\n"
6093 "The thesaurus is not functional.\n"
6094 "Please refer to sec. 6.15.1 of the User's Guide for setup\n"
6097 bv->showDialog("thesaurus", to_utf8(arg));
6101 case LFUN_SPELLING_ADD: {
6102 Language const * language = getLanguage(cur, cmd.getArg(1));
6103 docstring word = from_utf8(cmd.getArg(0));
6105 word = cur.selectionAsString(false);
6107 if (word.size() > 100 || word.empty()) {
6108 // Get word or selection
6109 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6110 word = cur.selectionAsString(false);
6113 WordLangTuple wl(word, language);
6114 theSpellChecker()->insert(wl);
6118 case LFUN_SPELLING_ADD_LOCAL: {
6119 Language const * language = getLanguage(cur, cmd.getArg(1));
6120 docstring word = from_utf8(cmd.getArg(0));
6122 word = cur.selectionAsString(false);
6123 if (word.size() > 100)
6126 // Get word or selection
6127 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6128 word = cur.selectionAsString(false);
6131 WordLangTuple wl(word, language);
6132 if (!bv->buffer().params().spellignored(wl)) {
6133 cur.recordUndoBufferParams();
6134 bv->buffer().params().spellignore().push_back(wl);
6136 // trigger re-check of whole buffer
6137 bv->buffer().requestSpellcheck();
6142 case LFUN_SPELLING_REMOVE_LOCAL: {
6143 Language const * language = getLanguage(cur, cmd.getArg(1));
6144 docstring word = from_utf8(cmd.getArg(0));
6146 word = cur.selectionAsString(false);
6147 if (word.size() > 100)
6150 // Get word or selection
6151 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6152 word = cur.selectionAsString(false);
6155 WordLangTuple wl(word, language);
6156 bool has_item = false;
6157 vector<WordLangTuple>::const_iterator it = bv->buffer().params().spellignore().begin();
6158 for (; it != bv->buffer().params().spellignore().end(); ++it) {
6159 if (it->lang()->code() != wl.lang()->code())
6161 if (it->word() == wl.word()) {
6167 cur.recordUndoBufferParams();
6168 bv->buffer().params().spellignore().erase(it);
6170 // trigger re-check of whole buffer
6171 bv->buffer().requestSpellcheck();
6177 case LFUN_SPELLING_IGNORE: {
6178 Language const * language = getLanguage(cur, cmd.getArg(1));
6179 docstring word = from_utf8(cmd.getArg(0));
6181 word = cur.selectionAsString(false);
6183 if (word.size() > 100 || word.empty()) {
6184 // Get word or selection
6185 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6186 word = cur.selectionAsString(false);
6189 WordLangTuple wl(word, language);
6190 theSpellChecker()->accept(wl);
6194 case LFUN_SPELLING_REMOVE: {
6195 Language const * language = getLanguage(cur, cmd.getArg(1));
6196 docstring word = from_utf8(cmd.getArg(0));
6198 word = cur.selectionAsString(false);
6200 if (word.size() > 100 || word.empty()) {
6201 // Get word or selection
6202 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6203 word = cur.selectionAsString(false);
6206 WordLangTuple wl(word, language);
6207 theSpellChecker()->remove(wl);
6211 case LFUN_PARAGRAPH_PARAMS_APPLY: {
6212 // Given data, an encoding of the ParagraphParameters
6213 // generated in the Paragraph dialog, this function sets
6214 // the current paragraph, or currently selected paragraphs,
6216 // NOTE: This function overrides all existing settings.
6217 setParagraphs(cur, cmd.argument());
6218 cur.message(_("Paragraph layout set"));
6222 case LFUN_PARAGRAPH_PARAMS: {
6223 // Given data, an encoding of the ParagraphParameters as we'd
6224 // find them in a LyX file, this function modifies the current paragraph,
6225 // or currently selected paragraphs.
6226 // NOTE: This function only modifies, and does not override, existing
6228 setParagraphs(cur, cmd.argument(), true);
6229 cur.message(_("Paragraph layout set"));
6234 if (cur.selection()) {
6235 cur.selection(false);
6238 // This used to be LFUN_FINISHED_RIGHT, I think FORWARD is more
6239 // correct, but I'm not 100% sure -- dov, 071019
6240 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
6244 case LFUN_OUTLINE_UP: {
6245 pos_type const opos = cur.pos();
6246 outline(OutlineUp, cur, false);
6247 setCursor(cur, cur.pit(), opos);
6248 cur.forceBufferUpdate();
6253 case LFUN_OUTLINE_DOWN: {
6254 pos_type const opos = cur.pos();
6255 outline(OutlineDown, cur, false);
6256 setCursor(cur, cur.pit(), opos);
6257 cur.forceBufferUpdate();
6262 case LFUN_OUTLINE_IN:
6263 outline(OutlineIn, cur, cmd.getArg(0) == "local");
6264 cur.forceBufferUpdate();
6268 case LFUN_OUTLINE_OUT:
6269 outline(OutlineOut, cur, cmd.getArg(0) == "local");
6270 cur.forceBufferUpdate();
6274 case LFUN_SERVER_GET_STATISTICS: {
6275 DocIterator from, to;
6276 if (cur.selection()) {
6277 from = cur.selectionBegin();
6278 to = cur.selectionEnd();
6280 from = doc_iterator_begin(cur.buffer());
6281 to = doc_iterator_end(cur.buffer());
6284 cur.buffer()->updateStatistics(from, to);
6285 string const arg0 = cmd.getArg(0);
6286 if (arg0 == "words") {
6287 cur.message(convert<docstring>(cur.buffer()->wordCount()));
6288 } else if (arg0 == "chars") {
6289 cur.message(convert<docstring>(cur.buffer()->charCount(false)));
6290 } else if (arg0 == "chars-space") {
6291 cur.message(convert<docstring>(cur.buffer()->charCount(true)));
6293 cur.message(convert<docstring>(cur.buffer()->wordCount()) + " "
6294 + convert<docstring>(cur.buffer()->charCount(false)) + " "
6295 + convert<docstring>(cur.buffer()->charCount(true)));
6301 LYXERR(Debug::ACTION, "Command " << cmd << " not DISPATCHED by Text");
6306 needsUpdate |= (cur.pos() != cur.lastpos()) && cur.selection();
6308 if (lyxrc.spellcheck_continuously && !needsUpdate) {
6309 // Check for misspelled text
6310 // The redraw is useful because of the painting of
6311 // misspelled markers depends on the cursor position.
6312 // Trigger a redraw for cursor moves inside misspelled text.
6313 if (!cur.inTexted()) {
6314 // move from regular text to math
6315 needsUpdate = last_misspelled;
6316 } else if (oldTopSlice != cur.top() || oldBoundary != cur.boundary()) {
6317 // move inside regular text
6318 needsUpdate = last_misspelled
6319 || cur.paragraph().isMisspelled(cur.pos(), true);
6323 // FIXME: The cursor flag is reset two lines below
6324 // so we need to check here if some of the LFUN did touch that.
6325 // for now only Text::erase() and Text::backspace() do that.
6326 // The plan is to verify all the LFUNs and then to remove this
6327 // singleParUpdate boolean altogether.
6328 if (cur.result().screenUpdate() & Update::Force) {
6329 singleParUpdate = false;
6333 // FIXME: the following code should go in favor of fine grained
6334 // update flag treatment.
6335 if (singleParUpdate) {
6336 // Inserting characters does not change par height in general. So, try
6337 // to update _only_ this paragraph. BufferView will detect if a full
6338 // metrics update is needed anyway.
6339 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
6343 && &oldTopSlice.inset() == &cur.inset()
6344 && oldTopSlice.idx() == cur.idx()
6345 && !oldSelection // oldSelection is a backup of cur.selection() at the beginning of the function.
6346 && !cur.selection())
6347 // FIXME: it would be better if we could just do this
6349 //if (cur.result().update() != Update::FitCursor)
6350 // cur.noScreenUpdate();
6352 // But some LFUNs do not set Update::FitCursor when needed, so we
6353 // do it for all. This is not very harmfull as FitCursor will provoke
6354 // a full redraw only if needed but still, a proper review of all LFUN
6355 // should be done and this needsUpdate boolean can then be removed.
6356 cur.screenUpdateFlags(Update::FitCursor);
6358 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
6362 bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
6363 FuncStatus & status) const
6365 LBUFERR(this == cur.text());
6367 FontInfo const & fontinfo = cur.real_current_font.fontInfo();
6369 bool allow_in_passthru = false;
6370 InsetCode code = NO_CODE;
6372 switch (cmd.action()) {
6374 case LFUN_DEPTH_DECREMENT:
6375 enable = changeDepthAllowed(cur, DEC_DEPTH);
6378 case LFUN_DEPTH_INCREMENT:
6379 enable = changeDepthAllowed(cur, INC_DEPTH);
6383 // FIXME We really should not allow this to be put, e.g.,
6384 // in a footnote, or in ERT. But it would make sense in a
6385 // branch, so I'm not sure what to do.
6386 status.setOnOff(cur.paragraph().params().startOfAppendix());
6389 case LFUN_DIALOG_SHOW_NEW_INSET:
6390 if (cmd.argument() == "bibitem")
6391 code = BIBITEM_CODE;
6392 else if (cmd.argument() == "bibtex") {
6394 // not allowed in description items
6395 enable = !inDescriptionItem(cur);
6397 else if (cmd.argument() == "box")
6399 else if (cmd.argument() == "branch")
6401 else if (cmd.argument() == "citation")
6403 else if (cmd.argument() == "counter")
6404 code = COUNTER_CODE;
6405 else if (cmd.argument() == "ert")
6407 else if (cmd.argument() == "external")
6408 code = EXTERNAL_CODE;
6409 else if (cmd.argument() == "float")
6411 else if (cmd.argument() == "graphics")
6412 code = GRAPHICS_CODE;
6413 else if (cmd.argument() == "href")
6414 code = HYPERLINK_CODE;
6415 else if (cmd.argument() == "include")
6416 code = INCLUDE_CODE;
6417 else if (cmd.argument() == "index")
6419 else if (cmd.argument() == "index_print")
6420 code = INDEX_PRINT_CODE;
6421 else if (cmd.argument() == "listings")
6422 code = LISTINGS_CODE;
6423 else if (cmd.argument() == "mathspace")
6424 code = MATH_HULL_CODE;
6425 else if (cmd.argument() == "nomenclature")
6426 code = NOMENCL_CODE;
6427 else if (cmd.argument() == "nomencl_print")
6428 code = NOMENCL_PRINT_CODE;
6429 else if (cmd.argument() == "label")
6431 else if (cmd.argument() == "line")
6433 else if (cmd.argument() == "note")
6435 else if (cmd.argument() == "phantom")
6436 code = PHANTOM_CODE;
6437 else if (cmd.argument() == "ref")
6439 else if (cmd.argument() == "space")
6441 else if (cmd.argument() == "toc")
6443 else if (cmd.argument() == "vspace")
6445 else if (cmd.argument() == "wrap")
6449 case LFUN_ERT_INSERT:
6452 case LFUN_LISTING_INSERT:
6453 code = LISTINGS_CODE;
6454 // not allowed in description items
6455 enable = !inDescriptionItem(cur);
6457 case LFUN_FOOTNOTE_INSERT:
6460 case LFUN_TABULAR_INSERT:
6461 code = TABULAR_CODE;
6463 case LFUN_TABULAR_STYLE_INSERT:
6464 code = TABULAR_CODE;
6466 case LFUN_MARGINALNOTE_INSERT:
6469 case LFUN_FLOAT_INSERT:
6470 case LFUN_FLOAT_WIDE_INSERT:
6471 // FIXME: If there is a selection, we should check whether there
6472 // are floats in the selection, but this has performance issues, see
6473 // LFUN_CHANGE_ACCEPT/REJECT.
6475 if (inDescriptionItem(cur))
6476 // not allowed in description items
6479 InsetCode const inset_code = cur.inset().lyxCode();
6481 // algorithm floats cannot be put in another float
6482 if (to_utf8(cmd.argument()) == "algorithm") {
6483 enable = inset_code != WRAP_CODE && inset_code != FLOAT_CODE;
6487 // for figures and tables: only allow in another
6488 // float or wrap if it is of the same type and
6489 // not a subfloat already
6490 if(cur.inset().lyxCode() == code) {
6491 InsetFloat const & ins =
6492 static_cast<InsetFloat const &>(cur.inset());
6493 enable = ins.params().type == to_utf8(cmd.argument())
6494 && !ins.params().subfloat;
6495 } else if(cur.inset().lyxCode() == WRAP_CODE) {
6496 InsetWrap const & ins =
6497 static_cast<InsetWrap const &>(cur.inset());
6498 enable = ins.params().type == to_utf8(cmd.argument());
6502 case LFUN_WRAP_INSERT:
6504 // not allowed in description items
6505 enable = !inDescriptionItem(cur);
6507 case LFUN_FLOAT_LIST_INSERT: {
6508 code = FLOAT_LIST_CODE;
6509 // not allowed in description items
6510 enable = !inDescriptionItem(cur);
6512 FloatList const & floats = cur.buffer()->params().documentClass().floats();
6513 FloatList::const_iterator cit = floats[to_ascii(cmd.argument())];
6514 // make sure we know about such floats
6515 if (cit == floats.end() ||
6516 // and that we know how to generate a list of them
6517 (!cit->second.usesFloatPkg() && cit->second.listCommand().empty())) {
6518 status.setUnknown(true);
6519 // probably not necessary, but...
6525 case LFUN_CAPTION_INSERT: {
6526 code = CAPTION_CODE;
6527 string arg = cmd.getArg(0);
6528 bool varia = arg != "Unnumbered"
6529 && cur.inset().allowsCaptionVariation(arg);
6530 // not allowed in description items,
6531 // and in specific insets
6532 enable = !inDescriptionItem(cur)
6533 && (varia || arg.empty() || arg == "Standard");
6536 case LFUN_NOTE_INSERT:
6539 case LFUN_FLEX_INSERT: {
6541 docstring s = from_utf8(cmd.getArg(0));
6542 // Prepend "Flex:" prefix if not there
6543 if (!prefixIs(s, from_ascii("Flex:")))
6544 s = from_ascii("Flex:") + s;
6545 if (!cur.buffer()->params().documentClass().hasInsetLayout(s))
6547 else if (!cur.paragraph().allowedInContext(cur, cur.buffer()->params().documentClass().insetLayout(s)))
6551 cur.buffer()->params().documentClass().insetLayout(s).lyxtype();
6552 if (ilt != InsetLyXType::CHARSTYLE
6553 && ilt != InsetLyXType::CUSTOM
6554 && ilt != InsetLyXType::STANDARD)
6559 case LFUN_BOX_INSERT:
6562 case LFUN_BRANCH_INSERT:
6564 if (cur.buffer()->masterBuffer()->params().branchlist().empty()
6565 && cur.buffer()->params().branchlist().empty())
6568 case LFUN_IPA_INSERT:
6571 case LFUN_PHANTOM_INSERT:
6572 code = PHANTOM_CODE;
6574 case LFUN_LABEL_INSERT:
6577 case LFUN_INFO_INSERT:
6579 enable = cmd.argument().empty()
6580 || infoparams.validateArgument(cur.buffer(), cmd.argument(), true);
6582 case LFUN_ARGUMENT_INSERT: {
6584 allow_in_passthru = true;
6585 string const arg = cmd.getArg(0);
6590 Layout const & lay = cur.paragraph().layout();
6591 Layout::LaTeXArgMap args = lay.args();
6592 Layout::LaTeXArgMap::const_iterator const lait =
6594 if (lait != args.end()) {
6596 pit_type pit = cur.pit();
6597 pit_type lastpit = cur.pit();
6598 if (lay.isEnvironment() && !prefixIs(arg, "item:")) {
6599 // In a sequence of "merged" environment layouts, we only allow
6600 // non-item arguments once.
6601 lastpit = cur.lastpit();
6602 // get the first paragraph in sequence with this layout
6603 depth_type const current_depth = cur.paragraph().params().depth();
6607 Paragraph cpar = pars_[pit - 1];
6608 if (cpar.layout() == lay && cpar.params().depth() == current_depth)
6614 for (; pit <= lastpit; ++pit) {
6615 if (pars_[pit].layout() != lay)
6617 for (auto const & table : pars_[pit].insetList())
6618 if (InsetArgument const * ins = table.inset->asInsetArgument())
6619 if (ins->name() == arg) {
6620 // we have this already
6629 case LFUN_INDEX_INSERT:
6632 case LFUN_INDEX_PRINT:
6633 code = INDEX_PRINT_CODE;
6634 // not allowed in description items
6635 enable = !inDescriptionItem(cur);
6637 case LFUN_NOMENCL_INSERT:
6638 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6642 code = NOMENCL_CODE;
6644 case LFUN_NOMENCL_PRINT:
6645 code = NOMENCL_PRINT_CODE;
6646 // not allowed in description items
6647 enable = !inDescriptionItem(cur);
6649 case LFUN_HREF_INSERT:
6650 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6654 code = HYPERLINK_CODE;
6656 case LFUN_INDEXMACRO_INSERT: {
6657 string const arg = cmd.getArg(0);
6658 if (arg == "sortkey")
6659 code = INDEXMACRO_SORTKEY_CODE;
6661 code = INDEXMACRO_CODE;
6664 case LFUN_IPAMACRO_INSERT: {
6665 string const arg = cmd.getArg(0);
6667 code = IPADECO_CODE;
6669 code = IPACHAR_CODE;
6672 case LFUN_QUOTE_INSERT:
6673 // always allow this, since we will inset a raw quote
6674 // if an inset is not allowed.
6675 allow_in_passthru = true;
6677 case LFUN_SPECIALCHAR_INSERT:
6678 code = SPECIALCHAR_CODE;
6680 case LFUN_SPACE_INSERT:
6681 // slight hack: we know this is allowed in math mode
6685 case LFUN_PREVIEW_INSERT:
6686 code = PREVIEW_CODE;
6688 case LFUN_SCRIPT_INSERT:
6692 case LFUN_MATH_INSERT:
6693 case LFUN_MATH_AMS_MATRIX:
6694 case LFUN_MATH_MATRIX:
6695 case LFUN_MATH_DELIM:
6696 case LFUN_MATH_BIGDELIM:
6697 case LFUN_MATH_DISPLAY:
6698 case LFUN_MATH_MODE:
6699 case LFUN_MATH_MACRO:
6700 case LFUN_MATH_SUBSCRIPT:
6701 case LFUN_MATH_SUPERSCRIPT:
6702 code = MATH_HULL_CODE;
6705 case LFUN_REGEXP_MODE:
6706 code = MATH_HULL_CODE;
6707 enable = cur.buffer()->isInternal() && !cur.inRegexped();
6710 case LFUN_INSET_MODIFY:
6711 // We need to disable this, because we may get called for a
6713 // InsetTabular::getStatus() -> InsetText::getStatus()
6714 // and we don't handle LFUN_INSET_MODIFY.
6718 case LFUN_FONT_EMPH:
6719 status.setOnOff(fontinfo.emph() == FONT_ON);
6720 enable = !cur.paragraph().isPassThru();
6723 case LFUN_FONT_ITAL:
6724 status.setOnOff(fontinfo.shape() == ITALIC_SHAPE);
6725 enable = !cur.paragraph().isPassThru();
6728 case LFUN_FONT_NOUN:
6729 status.setOnOff(fontinfo.noun() == FONT_ON);
6730 enable = !cur.paragraph().isPassThru();
6733 case LFUN_FONT_BOLD:
6734 case LFUN_FONT_BOLDSYMBOL:
6735 status.setOnOff(fontinfo.series() == BOLD_SERIES);
6736 enable = !cur.paragraph().isPassThru();
6739 case LFUN_FONT_SANS:
6740 status.setOnOff(fontinfo.family() == SANS_FAMILY);
6741 enable = !cur.paragraph().isPassThru();
6744 case LFUN_FONT_ROMAN:
6745 status.setOnOff(fontinfo.family() == ROMAN_FAMILY);
6746 enable = !cur.paragraph().isPassThru();
6749 case LFUN_FONT_TYPEWRITER:
6750 status.setOnOff(fontinfo.family() == TYPEWRITER_FAMILY);
6751 enable = !cur.paragraph().isPassThru();
6755 enable = cur.selection();
6759 if (cmd.argument().empty()) {
6760 if (theClipboard().isInternal())
6761 enable = cap::numberOfSelections() > 0;
6763 enable = !theClipboard().empty();
6767 // we have an argument
6768 string const arg = to_utf8(cmd.argument());
6769 if (isStrUnsignedInt(arg)) {
6770 // it's a number and therefore means the internal stack
6771 unsigned int n = convert<unsigned int>(arg);
6772 enable = cap::numberOfSelections() > n;
6776 // explicit text type?
6777 if (arg == "html") {
6778 // Do not enable for PlainTextType, since some tidying in the
6779 // frontend is needed for HTML, which is too unsafe for plain text.
6780 enable = theClipboard().hasTextContents(Clipboard::HtmlTextType);
6782 } else if (arg == "latex") {
6783 // LaTeX is usually not available on the clipboard with
6784 // the correct MIME type, but in plain text.
6785 enable = theClipboard().hasTextContents(Clipboard::PlainTextType) ||
6786 theClipboard().hasTextContents(Clipboard::LaTeXTextType);
6790 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
6792 type = Clipboard::PdfGraphicsType;
6793 else if (arg == "png")
6794 type = Clipboard::PngGraphicsType;
6795 else if (arg == "jpeg")
6796 type = Clipboard::JpegGraphicsType;
6797 else if (arg == "linkback")
6798 type = Clipboard::LinkBackGraphicsType;
6799 else if (arg == "emf")
6800 type = Clipboard::EmfGraphicsType;
6801 else if (arg == "wmf")
6802 type = Clipboard::WmfGraphicsType;
6805 LYXERR0("Unrecognized graphics type: " << arg);
6806 // we don't want to assert if the user just mistyped the LFUN
6807 LATTEST(cmd.origin() != FuncRequest::INTERNAL);
6811 enable = theClipboard().hasGraphicsContents(type);
6815 case LFUN_CLIPBOARD_PASTE:
6816 case LFUN_CLIPBOARD_PASTE_SIMPLE:
6817 enable = !theClipboard().empty();
6820 case LFUN_PRIMARY_SELECTION_PASTE:
6821 status.setUnknown(!theSelection().supported());
6822 enable = cur.selection() || !theSelection().empty();
6825 case LFUN_SELECTION_PASTE:
6826 enable = cap::selection();
6829 case LFUN_PARAGRAPH_MOVE_UP:
6830 enable = cur.pit() > 0 && !cur.selection();
6833 case LFUN_PARAGRAPH_MOVE_DOWN:
6834 enable = cur.pit() < cur.lastpit() && !cur.selection();
6837 case LFUN_CHANGE_ACCEPT:
6838 case LFUN_CHANGE_REJECT:
6839 if (!cur.selection())
6840 enable = cur.paragraph().isChanged(cur.pos());
6842 // will enable if there is a change in the selection
6845 // cheap improvement for efficiency: using cached
6846 // buffer variable, if there is no change in the
6847 // document, no need to check further.
6848 if (!cur.buffer()->areChangesPresent())
6851 for (DocIterator it = cur.selectionBegin(); ; it.forwardPar()) {
6852 pos_type const beg = it.pos();
6854 bool const in_last_par = (it.pit() == cur.selectionEnd().pit() &&
6855 it.idx() == cur.selectionEnd().idx());
6857 end = cur.selectionEnd().pos();
6859 // the +1 is needed for cases, e.g., where there is a
6860 // paragraph break. See #11629.
6861 end = it.lastpos() + 1;
6862 if (beg != end && it.paragraph().isChanged(beg, end)) {
6866 if (beg != end && it.paragraph().hasChangedInsets(beg, end)) {
6876 case LFUN_OUTLINE_UP:
6877 case LFUN_OUTLINE_DOWN:
6878 case LFUN_OUTLINE_IN:
6879 case LFUN_OUTLINE_OUT:
6880 enable = cur.text()->getTocLevel(cur.pit()) != Layout::NOT_IN_TOC;
6883 case LFUN_NEWLINE_INSERT:
6884 // LaTeX restrictions (labels or empty par)
6885 enable = !cur.paragraph().isPassThru()
6886 && cur.pos() > cur.paragraph().beginOfBody();
6889 case LFUN_SEPARATOR_INSERT:
6890 // Always enabled for now
6894 case LFUN_TAB_INSERT:
6895 case LFUN_TAB_DELETE:
6896 enable = cur.paragraph().isPassThru();
6899 case LFUN_GRAPHICS_SET_GROUP: {
6900 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
6904 status.setOnOff(to_utf8(cmd.argument()) == ins->getParams().groupId);
6908 case LFUN_NEWPAGE_INSERT:
6909 // not allowed in description items
6910 code = NEWPAGE_CODE;
6911 enable = !inDescriptionItem(cur);
6915 enable = !cur.paragraph().isPassThru();
6916 status.setOnOff(cmd.getArg(0) == cur.real_current_font.language()->lang());
6919 case LFUN_PARAGRAPH_BREAK:
6920 enable = inset().allowMultiPar();
6923 case LFUN_SPELLING_ADD:
6924 case LFUN_SPELLING_ADD_LOCAL:
6925 case LFUN_SPELLING_REMOVE_LOCAL:
6926 case LFUN_SPELLING_IGNORE:
6927 case LFUN_SPELLING_REMOVE:
6928 enable = theSpellChecker() != nullptr;
6929 if (enable && !cmd.getArg(1).empty()) {
6930 // validate explicitly given language
6931 Language const * const lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
6932 enable &= lang != nullptr;
6937 case LFUN_LAYOUT_TOGGLE: {
6938 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
6939 docstring const req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
6940 docstring const layout = resolveLayout(req_layout, cur);
6942 // FIXME: make this work in multicell selection case
6943 enable = !owner_->forcePlainLayout() && !layout.empty() && !cur.selIsMultiCell();
6944 status.setOnOff(!owner_->forcePlainLayout() && !cur.selIsMultiCell()
6945 && isAlreadyLayout(layout, cur));
6949 case LFUN_ENVIRONMENT_SPLIT: {
6950 if (cmd.argument() == "outer") {
6951 // check if we have an environment in our nesting hierarchy
6953 depth_type const current_depth = cur.paragraph().params().depth();
6954 pit_type pit = cur.pit();
6955 Paragraph cpar = pars_[pit];
6957 if (pit == 0 || cpar.params().depth() == 0)
6961 if (cpar.params().depth() < current_depth)
6962 res = cpar.layout().isEnvironment();
6967 else if (cmd.argument() == "previous") {
6968 // look if we have an environment in the previous par
6969 pit_type pit = cur.pit();
6970 Paragraph cpar = pars_[pit];
6974 enable = cpar.layout().isEnvironment();
6980 else if (cur.paragraph().layout().isEnvironment()) {
6981 enable = cmd.argument() == "before"
6982 || cur.pos() > 0 || !isFirstInSequence(cur.pit());
6989 case LFUN_LAYOUT_PARAGRAPH:
6990 case LFUN_PARAGRAPH_PARAMS:
6991 case LFUN_PARAGRAPH_PARAMS_APPLY:
6992 case LFUN_PARAGRAPH_UPDATE:
6993 enable = owner_->allowParagraphCustomization();
6996 // FIXME: why are accent lfuns forbidden with pass_thru layouts?
6997 // Because they insert COMBINING DIACRITICAL Unicode characters,
6998 // that cannot be handled by LaTeX but must be converted according
6999 // to the definition in lib/unicodesymbols?
7000 case LFUN_ACCENT_ACUTE:
7001 case LFUN_ACCENT_BREVE:
7002 case LFUN_ACCENT_CARON:
7003 case LFUN_ACCENT_CEDILLA:
7004 case LFUN_ACCENT_CIRCLE:
7005 case LFUN_ACCENT_CIRCUMFLEX:
7006 case LFUN_ACCENT_DOT:
7007 case LFUN_ACCENT_GRAVE:
7008 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
7009 case LFUN_ACCENT_MACRON:
7010 case LFUN_ACCENT_OGONEK:
7011 case LFUN_ACCENT_TIE:
7012 case LFUN_ACCENT_TILDE:
7013 case LFUN_ACCENT_PERISPOMENI:
7014 case LFUN_ACCENT_UMLAUT:
7015 case LFUN_ACCENT_UNDERBAR:
7016 case LFUN_ACCENT_UNDERDOT:
7017 case LFUN_FONT_FRAK:
7018 case LFUN_FONT_SIZE:
7019 case LFUN_FONT_STATE:
7020 case LFUN_FONT_UNDERLINE:
7021 case LFUN_FONT_STRIKEOUT:
7022 case LFUN_FONT_CROSSOUT:
7023 case LFUN_FONT_UNDERUNDERLINE:
7024 case LFUN_FONT_UNDERWAVE:
7025 case LFUN_FONT_NO_SPELLCHECK:
7026 case LFUN_TEXTSTYLE_UPDATE:
7027 enable = !cur.paragraph().isPassThru();
7030 case LFUN_FONT_DEFAULT: {
7031 Font font(inherit_font, ignore_language);
7032 BufferParams const & bp = cur.buffer()->masterParams();
7033 if (cur.selection()) {
7035 // Check if we have a non-default font attribute
7036 // in the selection range.
7037 DocIterator const from = cur.selectionBegin();
7038 DocIterator const to = cur.selectionEnd();
7039 for (DocIterator dit = from ; dit != to && !dit.atEnd(); ) {
7040 if (!dit.inTexted()) {
7044 Paragraph const & par = dit.paragraph();
7045 pos_type const pos = dit.pos();
7046 Font tmp = par.getFontSettings(bp, pos);
7047 if (tmp.fontInfo() != font.fontInfo()
7048 || tmp.language() != bp.language) {
7056 // Disable if all is default already.
7057 enable = (cur.current_font.fontInfo() != font.fontInfo()
7058 || cur.current_font.language() != bp.language);
7062 case LFUN_TEXTSTYLE_APPLY:
7063 enable = !freeFonts.empty();
7066 case LFUN_WORD_DELETE_FORWARD:
7067 case LFUN_WORD_DELETE_BACKWARD:
7068 case LFUN_LINE_DELETE_FORWARD:
7069 case LFUN_WORD_FORWARD:
7070 case LFUN_WORD_BACKWARD:
7071 case LFUN_WORD_RIGHT:
7072 case LFUN_WORD_LEFT:
7073 case LFUN_CHAR_FORWARD:
7074 case LFUN_CHAR_FORWARD_SELECT:
7075 case LFUN_CHAR_BACKWARD:
7076 case LFUN_CHAR_BACKWARD_SELECT:
7077 case LFUN_CHAR_LEFT:
7078 case LFUN_CHAR_LEFT_SELECT:
7079 case LFUN_CHAR_RIGHT:
7080 case LFUN_CHAR_RIGHT_SELECT:
7082 case LFUN_UP_SELECT:
7084 case LFUN_DOWN_SELECT:
7085 case LFUN_PARAGRAPH_SELECT:
7086 case LFUN_PARAGRAPH_UP_SELECT:
7087 case LFUN_PARAGRAPH_DOWN_SELECT:
7088 case LFUN_LINE_BEGIN_SELECT:
7089 case LFUN_LINE_END_SELECT:
7090 case LFUN_WORD_FORWARD_SELECT:
7091 case LFUN_WORD_BACKWARD_SELECT:
7092 case LFUN_WORD_RIGHT_SELECT:
7093 case LFUN_WORD_LEFT_SELECT:
7094 case LFUN_WORD_SELECT:
7095 case LFUN_SECTION_SELECT:
7096 case LFUN_BUFFER_BEGIN:
7097 case LFUN_BUFFER_END:
7098 case LFUN_BUFFER_BEGIN_SELECT:
7099 case LFUN_BUFFER_END_SELECT:
7100 case LFUN_INSET_BEGIN:
7101 case LFUN_INSET_END:
7102 case LFUN_INSET_BEGIN_SELECT:
7103 case LFUN_INSET_END_SELECT:
7104 case LFUN_PARAGRAPH_UP:
7105 case LFUN_PARAGRAPH_DOWN:
7106 case LFUN_LINE_BEGIN:
7108 case LFUN_CHAR_DELETE_FORWARD:
7109 case LFUN_CHAR_DELETE_BACKWARD:
7110 case LFUN_WORD_UPCASE:
7111 case LFUN_WORD_LOWCASE:
7112 case LFUN_WORD_CAPITALIZE:
7113 case LFUN_CHARS_TRANSPOSE:
7114 case LFUN_SERVER_GET_XY:
7115 case LFUN_SERVER_SET_XY:
7116 case LFUN_SERVER_GET_LAYOUT:
7117 case LFUN_SELF_INSERT:
7118 case LFUN_UNICODE_INSERT:
7119 case LFUN_THESAURUS_ENTRY:
7121 case LFUN_SERVER_GET_STATISTICS:
7122 // these are handled in our dispatch()
7126 case LFUN_INSET_INSERT: {
7127 string const type = cmd.getArg(0);
7128 if (type == "toc") {
7130 // not allowed in description items
7131 //FIXME: couldn't this be merged in Inset::insetAllowed()?
7132 enable = !inDescriptionItem(cur);
7139 case LFUN_SEARCH_IGNORE: {
7140 bool const value = cmd.getArg(1) == "true";
7141 setIgnoreFormat(cmd.getArg(0), value);
7151 || !cur.inset().insetAllowed(code)
7152 || (cur.paragraph().layout().pass_thru && !allow_in_passthru)))
7155 status.setEnabled(enable);
7160 void Text::pasteString(Cursor & cur, docstring const & clip,
7163 if (!clip.empty()) {
7166 insertStringAsParagraphs(cur, clip, cur.current_font);
7168 insertStringAsLines(cur, clip, cur.current_font);
7173 // FIXME: an item inset would make things much easier.
7174 bool Text::inDescriptionItem(Cursor const & cur) const
7176 Paragraph const & par = cur.paragraph();
7177 pos_type const pos = cur.pos();
7178 pos_type const body_pos = par.beginOfBody();
7180 if (par.layout().latextype != LATEX_LIST_ENVIRONMENT
7181 && (par.layout().latextype != LATEX_ITEM_ENVIRONMENT
7182 || par.layout().margintype != MARGIN_FIRST_DYNAMIC))
7185 return (pos < body_pos
7187 && (pos == 0 || par.getChar(pos - 1) != ' ')));
7191 std::vector<docstring> Text::getFreeFonts() const
7193 vector<docstring> ffList;
7195 for (auto const & f : freeFonts)
7196 ffList.push_back(f.first);