3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Alfredo Braunstein
9 * \author Angus Leeming
10 * \author Asger Alstrup
12 * \author Dov Feldstern
13 * \author Jean-Marc Lasgouttes
15 * \author Jürgen Vigna
16 * \author Lars Gullik Bjønnes
17 * \author Stefan Schimanski
19 * Full author contact details are available in file CREDITS.
27 #include "BranchList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
32 #include "CompletionList.h"
34 #include "CursorSlice.h"
35 #include "CutAndPaste.h"
36 #include "DispatchResult.h"
38 #include "ErrorList.h"
40 #include "FloatList.h"
42 #include "FuncRequest.h"
43 #include "FuncStatus.h"
44 #include "InsetList.h"
50 #include "LyXAction.h"
53 #include "Paragraph.h"
54 #include "ParagraphParameters.h"
55 #include "SpellChecker.h"
56 #include "TextClass.h"
57 #include "TextMetrics.h"
58 #include "Thesaurus.h"
59 #include "WordLangTuple.h"
62 #include "frontends/alert.h"
63 #include "frontends/Application.h"
64 #include "frontends/Clipboard.h"
65 #include "frontends/Selection.h"
67 #include "mathed/InsetMathHull.h"
68 #include "mathed/InsetMathMacroTemplate.h"
70 #include "insets/Inset.h"
71 #include "insets/InsetArgument.h"
72 #include "insets/InsetCaption.h"
73 #include "insets/InsetCollapsible.h"
74 #include "insets/InsetCommand.h"
75 #include "insets/InsetExternal.h"
76 #include "insets/InsetFloat.h"
77 #include "insets/InsetFloatList.h"
78 #include "insets/InsetGraphics.h"
79 #include "insets/InsetGraphicsParams.h"
80 #include "insets/InsetIndexMacro.h"
81 #include "insets/InsetInfo.h"
82 #include "insets/InsetIPAMacro.h"
83 #include "insets/InsetNewline.h"
84 #include "insets/InsetQuotes.h"
85 #include "insets/InsetSpecialChar.h"
86 #include "insets/InsetTabular.h"
87 #include "insets/InsetText.h"
88 #include "insets/InsetWrap.h"
90 #include "support/convert.h"
91 #include "support/debug.h"
92 #include "support/docstream.h"
93 #include "support/docstring.h"
94 #include "support/docstring_list.h"
95 #include "support/filetools.h"
96 #include "support/gettext.h"
97 #include "support/lassert.h"
98 #include "support/limited_stack.h"
99 #include "support/lstrings.h"
100 #include "support/lyxtime.h"
101 #include "support/textutils.h"
102 #include "support/unique_ptr.h"
111 using namespace support;
113 using frontend::Clipboard;
118 bool moveItem(Paragraph & fromPar, pos_type fromPos,
119 Paragraph & toPar, pos_type toPos, BufferParams const & params)
121 // Note: moveItem() does not honour change tracking!
122 // Therefore, it should only be used for breaking and merging paragraphs
124 // We need a copy here because the character at fromPos is going to be erased.
125 Font const tmpFont = fromPar.getFontSettings(params, fromPos);
126 Change const tmpChange = fromPar.lookupChange(fromPos);
128 if (Inset * tmpInset = fromPar.getInset(fromPos)) {
129 fromPar.releaseInset(fromPos);
130 // The inset is not in fromPar any more.
131 if (!toPar.insertInset(toPos, tmpInset, tmpFont, tmpChange)) {
138 char_type const tmpChar = fromPar.getChar(fromPos);
139 fromPar.eraseChar(fromPos, false);
140 toPar.insertChar(toPos, tmpChar, tmpFont, tmpChange);
148 void breakParagraphConservative(BufferParams const & bparams,
149 ParagraphList & pars, pit_type pit, pos_type pos)
151 // create a new paragraph
152 Paragraph & tmp = *pars.insert(pars.iterator_at(pit + 1), Paragraph());
153 Paragraph & par = pars[pit];
155 tmp.setInsetOwner(&par.inInset());
156 tmp.makeSameLayout(par);
158 LASSERT(pos <= par.size(), return);
160 if (pos < par.size()) {
161 // move everything behind the break position to the new paragraph
162 pos_type pos_end = par.size() - 1;
164 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
165 if (moveItem(par, pos, tmp, j, bparams)) {
169 // Move over the end-of-par change information
170 tmp.setChange(tmp.size(), par.lookupChange(par.size()));
171 par.setChange(par.size(), Change(bparams.track_changes ?
172 Change::INSERTED : Change::UNCHANGED));
177 void mergeParagraph(BufferParams const & bparams,
178 ParagraphList & pars, pit_type par_offset)
180 Paragraph & next = pars[par_offset + 1];
181 Paragraph & par = pars[par_offset];
183 pos_type pos_end = next.size() - 1;
184 pos_type pos_insert = par.size();
186 // the imaginary end-of-paragraph character (at par.size()) has to be
187 // marked as unmodified. Otherwise, its change is adopted by the first
188 // character of the next paragraph.
189 if (par.isChanged(par.size())) {
190 LYXERR(Debug::CHANGES,
191 "merging par with inserted/deleted end-of-par character");
192 par.setChange(par.size(), Change(Change::UNCHANGED));
195 Change change = next.lookupChange(next.size());
197 // move the content of the second paragraph to the end of the first one
198 for (pos_type i = 0, j = pos_insert; i <= pos_end; ++i) {
199 if (moveItem(next, 0, par, j, bparams)) {
204 // move the change of the end-of-paragraph character
205 par.setChange(par.size(), change);
207 pars.erase(pars.iterator_at(par_offset + 1));
211 Text::Text(InsetText * owner, bool use_default_layout)
214 pars_.push_back(Paragraph());
215 Paragraph & par = pars_.back();
216 par.setInsetOwner(owner);
217 DocumentClass const & dc = owner->buffer().params().documentClass();
218 if (use_default_layout)
219 par.setDefaultLayout(dc);
221 par.setPlainLayout(dc);
225 Text::Text(InsetText * owner, Text const & text)
226 : owner_(owner), pars_(text.pars_)
228 for (auto & p : pars_)
229 p.setInsetOwner(owner);
233 pit_type Text::depthHook(pit_type pit, depth_type depth) const
235 pit_type newpit = pit;
240 while (newpit != 0 && pars_[newpit].getDepth() > depth)
243 if (pars_[newpit].getDepth() > depth)
250 pit_type Text::outerHook(pit_type par_offset) const
252 Paragraph const & par = pars_[par_offset];
254 if (par.getDepth() == 0)
256 return depthHook(par_offset, par.getDepth() - 1);
260 bool Text::isFirstInSequence(pit_type par_offset) const
262 Paragraph const & par = pars_[par_offset];
264 pit_type dhook_offset = depthHook(par_offset, par.getDepth());
266 if (dhook_offset == par_offset)
269 Paragraph const & dhook = pars_[dhook_offset];
271 return dhook.layout() != par.layout()
272 || dhook.getDepth() != par.getDepth();
276 pit_type Text::lastInSequence(pit_type pit) const
278 depth_type const depth = pars_[pit].getDepth();
279 pit_type newpit = pit;
281 while (size_t(newpit + 1) < pars_.size() &&
282 (pars_[newpit + 1].getDepth() > depth ||
283 (pars_[newpit + 1].getDepth() == depth &&
284 pars_[newpit + 1].layout() == pars_[pit].layout())))
291 int Text::getTocLevel(pit_type par_offset) const
293 Paragraph const & par = pars_[par_offset];
295 if (par.layout().isEnvironment() && !isFirstInSequence(par_offset))
296 return Layout::NOT_IN_TOC;
298 return par.layout().toclevel;
302 Font const Text::outerFont(pit_type par_offset) const
304 depth_type par_depth = pars_[par_offset].getDepth();
305 FontInfo tmpfont = inherit_font;
306 depth_type prev_par_depth = 0;
307 // Resolve against environment font information
308 while (par_offset != pit_type(pars_.size())
309 && par_depth != prev_par_depth
311 && !tmpfont.resolved()) {
312 prev_par_depth = par_depth;
313 par_offset = outerHook(par_offset);
314 if (par_offset != pit_type(pars_.size())) {
315 tmpfont.realize(pars_[par_offset].layout().font);
316 par_depth = pars_[par_offset].getDepth();
320 return Font(tmpfont);
324 int Text::getEndLabel(pit_type p) const
327 depth_type par_depth = pars_[p].getDepth();
328 while (pit != pit_type(pars_.size())) {
329 Layout const & layout = pars_[pit].layout();
330 int const endlabeltype = layout.endlabeltype;
332 if (endlabeltype != END_LABEL_NO_LABEL) {
333 if (p + 1 == pit_type(pars_.size()))
336 depth_type const next_depth =
337 pars_[p + 1].getDepth();
338 if (par_depth > next_depth ||
339 (par_depth == next_depth && layout != pars_[p + 1].layout()))
345 pit = outerHook(pit);
346 if (pit != pit_type(pars_.size()))
347 par_depth = pars_[pit].getDepth();
349 return END_LABEL_NO_LABEL;
353 static void acceptOrRejectChanges(ParagraphList & pars,
354 BufferParams const & bparams, Text::ChangeOp op)
356 pit_type pars_size = static_cast<pit_type>(pars.size());
358 // first, accept or reject changes within each individual
359 // paragraph (do not consider end-of-par)
360 for (pit_type pit = 0; pit < pars_size; ++pit) {
361 // prevent assertion failure
362 if (!pars[pit].empty()) {
363 if (op == Text::ACCEPT)
364 pars[pit].acceptChanges(0, pars[pit].size());
366 pars[pit].rejectChanges(0, pars[pit].size());
370 // next, accept or reject imaginary end-of-par characters
371 for (pit_type pit = 0; pit < pars_size; ++pit) {
372 pos_type pos = pars[pit].size();
373 if (pars[pit].isChanged(pos)) {
374 // keep the end-of-par char if it is inserted and accepted
375 // or when it is deleted and rejected.
376 if (pars[pit].isInserted(pos) == (op == Text::ACCEPT)) {
377 pars[pit].setChange(pos, Change(Change::UNCHANGED));
379 if (pit == pars_size - 1) {
380 // we cannot remove a par break at the end of the last
381 // paragraph; instead, we mark it unchanged
382 pars[pit].setChange(pos, Change(Change::UNCHANGED));
384 mergeParagraph(bparams, pars, pit);
394 void acceptChanges(ParagraphList & pars, BufferParams const & bparams)
396 acceptOrRejectChanges(pars, bparams, Text::ACCEPT);
400 void rejectChanges(ParagraphList & pars, BufferParams const & bparams)
402 acceptOrRejectChanges(pars, bparams, Text::REJECT);
406 InsetText const & Text::inset() const
413 void Text::readParToken(Paragraph & par, Lexer & lex,
414 string const & token, Font & font, Change & change, ErrorList & errorList)
416 Buffer * buf = &owner_->buffer();
417 BufferParams & bp = buf->params();
419 if (token[0] != '\\') {
420 docstring dstr = lex.getDocString();
421 par.appendString(dstr, font, change);
423 } else if (token == "\\begin_layout") {
425 docstring layoutname = lex.getDocString();
427 font = Font(inherit_font, bp.language);
428 change = Change(Change::UNCHANGED);
430 DocumentClass const & tclass = bp.documentClass();
432 if (layoutname.empty())
433 layoutname = tclass.defaultLayoutName();
435 if (owner_->forcePlainLayout()) {
436 // in this case only the empty layout is allowed
437 layoutname = tclass.plainLayoutName();
438 } else if (par.usePlainLayout()) {
439 // in this case, default layout maps to empty layout
440 if (layoutname == tclass.defaultLayoutName())
441 layoutname = tclass.plainLayoutName();
443 // otherwise, the empty layout maps to the default
444 if (layoutname == tclass.plainLayoutName())
445 layoutname = tclass.defaultLayoutName();
448 // When we apply an unknown layout to a document, we add this layout to the textclass
449 // of this document. For example, when you apply class article to a beamer document,
450 // all unknown layouts such as frame will be added to document class article so that
451 // these layouts can keep their original names.
452 bool const added_one = tclass.addLayoutIfNeeded(layoutname);
455 docstring const s = bformat(_("Layout `%1$s' was not found."), layoutname);
456 errorList.push_back(ErrorItem(_("Layout Not Found"), s,
457 {par.id(), 0}, {par.id(), -1}));
460 par.setLayout(bp.documentClass()[layoutname]);
462 // Test whether the layout is obsolete.
463 Layout const & layout = par.layout();
464 if (!layout.obsoleted_by().empty())
465 par.setLayout(bp.documentClass()[layout.obsoleted_by()]);
467 par.params().read(lex);
469 } else if (token == "\\end_layout") {
470 LYXERR0("Solitary \\end_layout in line " << lex.lineNumber() << "\n"
471 << "Missing \\begin_layout ?");
472 } else if (token == "\\end_inset") {
473 LYXERR0("Solitary \\end_inset in line " << lex.lineNumber() << "\n"
474 << "Missing \\begin_inset ?");
475 } else if (token == "\\begin_inset") {
476 Inset * inset = readInset(lex, buf);
478 par.insertInset(par.size(), inset, font, change);
481 docstring line = lex.getDocString();
482 errorList.push_back(ErrorItem(_("Unknown Inset"), line,
483 {par.id(), 0}, {par.id(), -1}));
485 } else if (token == "\\family") {
487 setLyXFamily(lex.getString(), font.fontInfo());
488 } else if (token == "\\series") {
490 setLyXSeries(lex.getString(), font.fontInfo());
491 } else if (token == "\\shape") {
493 setLyXShape(lex.getString(), font.fontInfo());
494 } else if (token == "\\size") {
496 setLyXSize(lex.getString(), font.fontInfo());
497 } else if (token == "\\lang") {
499 string const tok = lex.getString();
500 Language const * lang = languages.getLanguage(tok);
502 font.setLanguage(lang);
504 font.setLanguage(bp.language);
505 lex.printError("Unknown language `$$Token'");
507 } else if (token == "\\numeric") {
509 font.fontInfo().setNumber(setLyXMisc(lex.getString()));
510 } else if (token == "\\nospellcheck") {
512 font.fontInfo().setNoSpellcheck(setLyXMisc(lex.getString()));
513 } else if (token == "\\emph") {
515 font.fontInfo().setEmph(setLyXMisc(lex.getString()));
516 } else if (token == "\\bar") {
518 string const tok = lex.getString();
521 font.fontInfo().setUnderbar(FONT_ON);
522 else if (tok == "no")
523 font.fontInfo().setUnderbar(FONT_OFF);
524 else if (tok == "default")
525 font.fontInfo().setUnderbar(FONT_INHERIT);
527 lex.printError("Unknown bar font flag "
529 } else if (token == "\\strikeout") {
531 font.fontInfo().setStrikeout(setLyXMisc(lex.getString()));
532 } else if (token == "\\xout") {
534 font.fontInfo().setXout(setLyXMisc(lex.getString()));
535 } else if (token == "\\uuline") {
537 font.fontInfo().setUuline(setLyXMisc(lex.getString()));
538 } else if (token == "\\uwave") {
540 font.fontInfo().setUwave(setLyXMisc(lex.getString()));
541 } else if (token == "\\noun") {
543 font.fontInfo().setNoun(setLyXMisc(lex.getString()));
544 } else if (token == "\\color") {
546 setLyXColor(lex.getString(), font.fontInfo());
547 } else if (token == "\\SpecialChar" ||
548 (token == "\\SpecialCharNoPassThru" &&
549 !par.layout().pass_thru && !inset().isPassThru())) {
550 auto inset = make_unique<InsetSpecialChar>();
552 inset->setBuffer(*buf);
553 par.insertInset(par.size(), inset.release(), font, change);
554 } else if (token == "\\SpecialCharNoPassThru") {
556 docstring const s = ltrim(lex.getDocString(), "\\");
557 par.insert(par.size(), s, font, change);
558 } else if (token == "\\IPAChar") {
559 auto inset = make_unique<InsetIPAChar>();
561 inset->setBuffer(*buf);
562 par.insertInset(par.size(), inset.release(), font, change);
563 } else if (token == "\\twohyphens" || token == "\\threehyphens") {
564 // Ideally, this should be done by lyx2lyx, but lyx2lyx does not know the
565 // running font and does not know anything about layouts (and CopyStyle).
566 Layout const & layout(par.layout());
567 FontInfo info = font.fontInfo();
568 info.realize(layout.resfont);
569 if (layout.pass_thru || inset().isPassThru() ||
570 info.family() == TYPEWRITER_FAMILY) {
571 if (token == "\\twohyphens")
572 par.insert(par.size(), from_ascii("--"), font, change);
574 par.insert(par.size(), from_ascii("---"), font, change);
576 if (token == "\\twohyphens")
577 par.insertChar(par.size(), 0x2013, font, change);
579 par.insertChar(par.size(), 0x2014, font, change);
581 } else if (token == "\\backslash") {
582 par.appendChar('\\', font, change);
583 } else if (token == "\\LyXTable") {
584 auto inset = make_unique<InsetTabular>(buf);
586 par.insertInset(par.size(), inset.release(), font, change);
587 } else if (token == "\\change_unchanged") {
588 change = Change(Change::UNCHANGED);
589 } else if (token == "\\change_inserted" || token == "\\change_deleted") {
591 istringstream is(lex.getString());
595 BufferParams::AuthorMap const & am = bp.author_map_;
596 if (am.find(aid) == am.end()) {
597 errorList.push_back(ErrorItem(
598 _("Change tracking author index missing"),
599 bformat(_("A change tracking author information for index "
600 "%1$d is missing. This can happen after a wrong "
601 "merge by a version control system. In this case, "
602 "either fix the merge, or have this information "
603 "missing until the corresponding tracked changes "
604 "are merged or this user edits the file again.\n"),
606 {par.id(), par.size()}, {par.id(), par.size() + 1}));
607 bp.addAuthor(Author(aid));
609 if (token == "\\change_inserted")
610 change = Change(Change::INSERTED, am.find(aid)->second, ct);
612 change = Change(Change::DELETED, am.find(aid)->second, ct);
615 errorList.push_back(ErrorItem(_("Unknown token"),
616 bformat(_("Unknown token: %1$s %2$s\n"),
619 {par.id(), 0}, {par.id(), -1}));
624 void Text::readParagraph(Paragraph & par, Lexer & lex,
625 ErrorList & errorList)
628 string token = lex.getString();
630 Change change(Change::UNCHANGED);
633 readParToken(par, lex, token, font, change, errorList);
636 token = lex.getString();
641 if (token == "\\end_layout") {
642 //Ok, paragraph finished
646 LYXERR(Debug::PARSER, "Handling paragraph token: `" << token << '\'');
647 if (token == "\\begin_layout" || token == "\\end_document"
648 || token == "\\end_inset" || token == "\\begin_deeper"
649 || token == "\\end_deeper") {
650 lex.pushToken(token);
651 lyxerr << "Paragraph ended in line "
652 << lex.lineNumber() << "\n"
653 << "Missing \\end_layout.\n";
657 // Final change goes to paragraph break:
658 if (inset().allowMultiPar())
659 par.setChange(par.size(), change);
661 // Initialize begin_of_body_ on load; redoParagraph maintains
662 par.setBeginOfBody();
664 // mark paragraph for spell checking on load
665 // par.requestSpellCheck();
669 class TextCompletionList : public CompletionList
673 TextCompletionList(Cursor const & cur, WordList const & list)
674 : buffer_(cur.buffer()), list_(list)
677 virtual ~TextCompletionList() {}
680 bool sorted() const override { return true; }
682 size_t size() const override
687 docstring const & data(size_t idx) const override
689 return list_.word(idx);
694 Buffer const * buffer_;
696 WordList const & list_;
700 bool Text::empty() const
702 return pars_.empty() || (pars_.size() == 1 && pars_[0].empty()
703 // FIXME: Should we consider the labeled type as empty too?
704 && pars_[0].layout().labeltype == LABEL_NO_LABEL);
708 double Text::spacing(Paragraph const & par) const
710 if (par.params().spacing().isDefault())
711 return owner_->buffer().params().spacing().getValue();
712 return par.params().spacing().getValue();
717 * This breaks a paragraph at the specified position.
718 * The new paragraph will:
719 * - Decrease depth by one (or change layout to default layout) when
720 * keep_layout == false
721 * - keep current depth and layout when keep_layout == true
723 static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
726 BufferParams const & bparams = text.inset().buffer().params();
727 ParagraphList & pars = text.paragraphs();
728 // create a new paragraph, and insert into the list
729 ParagraphList::iterator tmp =
730 pars.insert(pars.iterator_at(par_offset + 1), Paragraph());
732 Paragraph & par = pars[par_offset];
734 // remember to set the inset_owner
735 tmp->setInsetOwner(&par.inInset());
736 // without doing that we get a crash when typing <Return> at the
737 // end of a paragraph
738 tmp->setPlainOrDefaultLayout(bparams.documentClass());
741 tmp->setLayout(par.layout());
742 tmp->setLabelWidthString(par.params().labelWidthString());
743 tmp->params().depth(par.params().depth());
744 } else if (par.params().depth() > 0) {
745 Paragraph const & hook = pars[text.outerHook(par_offset)];
746 tmp->setLayout(hook.layout());
747 // not sure the line below is useful
748 tmp->setLabelWidthString(par.params().labelWidthString());
749 tmp->params().depth(hook.params().depth());
752 bool const isempty = (par.allowEmpty() && par.empty());
754 if (!isempty && (par.size() > pos || par.empty())) {
755 tmp->setLayout(par.layout());
756 tmp->params().align(par.params().align());
757 tmp->setLabelWidthString(par.params().labelWidthString());
759 tmp->params().depth(par.params().depth());
760 tmp->params().noindent(par.params().noindent());
761 tmp->params().spacing(par.params().spacing());
763 // move everything behind the break position
764 // to the new paragraph
766 /* Note: if !keepempty, empty() == true, then we reach
767 * here with size() == 0. So pos_end becomes - 1. This
768 * doesn't cause problems because both loops below
769 * enforce pos <= pos_end and 0 <= pos
771 pos_type pos_end = par.size() - 1;
773 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
774 if (moveItem(par, pos, *tmp, j, bparams)) {
780 // Move over the end-of-par change information
781 tmp->setChange(tmp->size(), par.lookupChange(par.size()));
782 par.setChange(par.size(), Change(bparams.track_changes ?
783 Change::INSERTED : Change::UNCHANGED));
786 // Make sure that we keep the language when
787 // breaking paragraph.
789 Font changed = tmp->getFirstFontSettings(bparams);
790 Font const & old = par.getFontSettings(bparams, par.size());
791 changed.setLanguage(old.language());
792 tmp->setFont(0, changed);
799 bool const soa = par.params().startOfAppendix();
800 par.params().clear();
801 // do not lose start of appendix marker (bug 4212)
802 par.params().startOfAppendix(soa);
803 par.setPlainOrDefaultLayout(bparams.documentClass());
807 par.setLayout(tmp->layout());
808 par.setLabelWidthString(tmp->params().labelWidthString());
809 par.params().depth(tmp->params().depth());
814 void Text::breakParagraph(Cursor & cur, bool inverse_logic)
816 LBUFERR(this == cur.text());
818 Paragraph & cpar = cur.paragraph();
819 pit_type cpit = cur.pit();
821 DocumentClass const & tclass = cur.buffer()->params().documentClass();
822 Layout const & layout = cpar.layout();
824 if (cur.lastpos() == 0 && !cpar.allowEmpty()) {
825 if (changeDepthAllowed(cur, DEC_DEPTH)) {
826 changeDepth(cur, DEC_DEPTH);
827 pit_type const prev = depthHook(cpit, cpar.getDepth());
828 docstring const & lay = pars_[prev].layout().name();
829 if (lay != layout.name())
832 docstring const & lay = cur.paragraph().usePlainLayout()
833 ? tclass.plainLayoutName() : tclass.defaultLayoutName();
834 if (lay != layout.name())
842 // Always break behind a space
843 // It is better to erase the space (Dekel)
844 if (cur.pos() != cur.lastpos() && cpar.isLineSeparator(cur.pos()))
845 cpar.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
847 // What should the layout for the new paragraph be?
848 bool keep_layout = layout.isEnvironment()
849 || (layout.isParagraph() && layout.parbreak_is_newline);
851 keep_layout = !keep_layout;
853 // We need to remember this before we break the paragraph, because
854 // that invalidates the layout variable
855 bool sensitive = layout.labeltype == LABEL_SENSITIVE;
857 // we need to set this before we insert the paragraph.
858 bool const isempty = cpar.allowEmpty() && cpar.empty();
860 lyx::breakParagraph(*this, cpit, cur.pos(), keep_layout);
862 // After this, neither paragraph contains any rows!
865 pit_type next_par = cpit + 1;
867 // well this is the caption hack since one caption is really enough
870 // set to standard-layout
871 //FIXME Check if this should be plainLayout() in some cases
872 pars_[cpit].applyLayout(tclass.defaultLayout());
874 // set to standard-layout
875 //FIXME Check if this should be plainLayout() in some cases
876 pars_[next_par].applyLayout(tclass.defaultLayout());
879 while (!pars_[next_par].empty() && pars_[next_par].isNewline(0)) {
880 if (!pars_[next_par].eraseChar(0, cur.buffer()->params().track_changes))
881 break; // the character couldn't be deleted physically due to change tracking
884 // A singlePar update is not enough in this case.
885 cur.screenUpdateFlags(Update::Force);
886 cur.forceBufferUpdate();
888 // This check is necessary. Otherwise the new empty paragraph will
889 // be deleted automatically. And it is more friendly for the user!
890 if (cur.pos() != 0 || isempty)
891 setCursor(cur, cur.pit() + 1, 0);
893 setCursor(cur, cur.pit(), 0);
897 // needed to insert the selection
898 void Text::insertStringAsLines(Cursor & cur, docstring const & str,
901 BufferParams const & bparams = owner_->buffer().params();
902 pit_type pit = cur.pit();
903 pos_type pos = cur.pos();
905 // The special chars we handle
906 static map<wchar_t, InsetSpecialChar::Kind> specialchars = {
907 { 0x200c, InsetSpecialChar::LIGATURE_BREAK },
908 { 0x200b, InsetSpecialChar::ALLOWBREAK },
909 { 0x2026, InsetSpecialChar::LDOTS },
910 { 0x2011, InsetSpecialChar::NOBREAKDASH }
913 // insert the string, don't insert doublespace
914 bool space_inserted = true;
915 for (auto const & ch : str) {
916 Paragraph & par = pars_[pit];
918 if (inset().allowMultiPar() && (!par.empty() || par.allowEmpty())) {
919 lyx::breakParagraph(*this, pit, pos,
920 par.layout().isEnvironment());
923 space_inserted = true;
927 // do not insert consecutive spaces if !free_spacing
928 } else if ((ch == ' ' || ch == '\t') &&
929 space_inserted && !par.isFreeSpacing()) {
931 } else if (ch == '\t') {
932 if (!par.isFreeSpacing()) {
933 // tabs are like spaces here
934 par.insertChar(pos, ' ', font, bparams.track_changes);
936 space_inserted = true;
938 par.insertChar(pos, ch, font, bparams.track_changes);
940 space_inserted = true;
942 } else if (specialchars.find(ch) != specialchars.end()
943 && (par.insertInset(pos, new InsetSpecialChar(specialchars.find(ch)->second),
944 font, bparams.track_changes
945 ? Change(Change::INSERTED)
946 : Change(Change::UNCHANGED)))) {
948 space_inserted = false;
949 } else if (!isPrintable(ch)) {
950 // Ignore (other) unprintables
953 // just insert the character
954 par.insertChar(pos, ch, font, bparams.track_changes);
956 space_inserted = (ch == ' ');
959 setCursor(cur, pit, pos);
963 // turn double CR to single CR, others are converted into one
964 // blank. Then insertStringAsLines is called
965 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str,
968 docstring linestr = str;
969 bool newline_inserted = false;
971 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
972 if (linestr[i] == '\n') {
973 if (newline_inserted) {
974 // we know that \r will be ignored by
975 // insertStringAsLines. Of course, it is a dirty
976 // trick, but it works...
977 linestr[i - 1] = '\r';
981 newline_inserted = true;
983 } else if (isPrintable(linestr[i])) {
984 newline_inserted = false;
987 insertStringAsLines(cur, linestr, font);
993 bool canInsertChar(Cursor const & cur, char_type c)
995 Paragraph const & par = cur.paragraph();
996 // If not in free spacing mode, check if there will be two blanks together or a blank at
997 // the beginning of a paragraph.
998 if (!par.isFreeSpacing() && isLineSeparatorChar(c)) {
999 if (cur.pos() == 0) {
1001 "You cannot insert a space at the "
1002 "beginning of a paragraph. Please read the Tutorial."));
1005 // If something is wrong, ignore this character.
1006 LASSERT(cur.pos() > 0, return false);
1007 if ((par.isLineSeparator(cur.pos() - 1) || par.isNewline(cur.pos() - 1))
1008 && !par.isDeleted(cur.pos() - 1)) {
1010 "You cannot type two spaces this way. "
1011 "Please read the Tutorial."));
1016 // Prevent to insert uncodable characters in verbatim and ERT.
1017 // The encoding is inherited from the context here.
1018 if (par.isPassThru() && cur.getEncoding()) {
1019 Encoding const * e = cur.getEncoding();
1020 if (!e->encodable(c)) {
1021 cur.message(_("Character is uncodable in this verbatim context."));
1031 // insert a character, moves all the following breaks in the
1032 // same Paragraph one to the right and make a rebreak
1033 void Text::insertChar(Cursor & cur, char_type c)
1035 LBUFERR(this == cur.text());
1037 if (!canInsertChar(cur,c))
1040 cur.recordUndo(INSERT_UNDO);
1042 TextMetrics const & tm = cur.bv().textMetrics(this);
1043 Buffer const & buffer = *cur.buffer();
1044 Paragraph & par = cur.paragraph();
1045 // try to remove this
1046 pit_type const pit = cur.pit();
1048 if (lyxrc.auto_number) {
1049 static docstring const number_operators = from_ascii("+-/*");
1050 static docstring const number_unary_operators = from_ascii("+-");
1052 // Common Number Separators: comma, dot etc.
1053 // European Number Terminators: percent, permille, degree, euro etc.
1054 if (cur.current_font.fontInfo().number() == FONT_ON) {
1055 if (!isDigitASCII(c) && !contains(number_operators, c) &&
1056 !(isCommonNumberSeparator(c) &&
1058 cur.pos() != cur.lastpos() &&
1059 tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1060 tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON) &&
1061 !(isEuropeanNumberTerminator(c) &&
1063 tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1064 tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON)
1066 number(cur); // Set current_font.number to OFF
1067 } else if (isDigitASCII(c) &&
1068 cur.real_current_font.isVisibleRightToLeft()) {
1069 number(cur); // Set current_font.number to ON
1071 if (cur.pos() != 0) {
1072 char_type const ch = par.getChar(cur.pos() - 1);
1073 if (contains(number_unary_operators, ch) &&
1075 || par.isSeparator(cur.pos() - 2)
1076 || par.isEnvSeparator(cur.pos() - 2)
1077 || par.isNewline(cur.pos() - 2))
1079 setCharFont(pit, cur.pos() - 1, cur.current_font,
1081 } else if (isCommonNumberSeparator(ch)
1083 && tm.displayFont(pit, cur.pos() - 2).fontInfo().number() == FONT_ON) {
1084 setCharFont(pit, cur.pos() - 1, cur.current_font,
1091 // In Bidi text, we want spaces to be treated in a special way: spaces
1092 // which are between words in different languages should get the
1093 // paragraph's language; otherwise, spaces should keep the language
1094 // they were originally typed in. This is only in effect while typing;
1095 // after the text is already typed in, the user can always go back and
1096 // explicitly set the language of a space as desired. But 99.9% of the
1097 // time, what we're doing here is what the user actually meant.
1099 // The following cases are the ones in which the language of the space
1100 // should be changed to match that of the containing paragraph. In the
1101 // depictions, lowercase is LTR, uppercase is RTL, underscore (_)
1102 // represents a space, pipe (|) represents the cursor position (so the
1103 // character before it is the one just typed in). The different cases
1104 // are depicted logically (not visually), from left to right:
1109 // Theoretically, there are other situations that we should, perhaps, deal
1110 // with (e.g.: a|_A, A|_a). In practice, though, there really isn't any
1111 // point (to understand why, just try to create this situation...).
1113 if ((cur.pos() >= 2) && (par.isLineSeparator(cur.pos() - 1))) {
1114 // get font in front and behind the space in question. But do NOT
1115 // use getFont(cur.pos()) because the character c is not inserted yet
1116 Font const pre_space_font = tm.displayFont(cur.pit(), cur.pos() - 2);
1117 Font const & post_space_font = cur.real_current_font;
1118 bool pre_space_rtl = pre_space_font.isVisibleRightToLeft();
1119 bool post_space_rtl = post_space_font.isVisibleRightToLeft();
1121 if (pre_space_rtl != post_space_rtl) {
1122 // Set the space's language to match the language of the
1123 // adjacent character whose direction is the paragraph's
1124 // direction; don't touch other properties of the font
1125 Language const * lang =
1126 (pre_space_rtl == par.isRTL(buffer.params())) ?
1127 pre_space_font.language() : post_space_font.language();
1129 Font space_font = tm.displayFont(cur.pit(), cur.pos() - 1);
1130 space_font.setLanguage(lang);
1131 par.setFont(cur.pos() - 1, space_font);
1135 pos_type pos = cur.pos();
1136 if (!cur.paragraph().isPassThru() && owner_->lyxCode() != IPA_CODE &&
1137 cur.real_current_font.fontInfo().family() != TYPEWRITER_FAMILY &&
1138 c == '-' && pos > 0) {
1139 if (par.getChar(pos - 1) == '-') {
1140 // convert "--" to endash
1141 par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1144 } else if (par.getChar(pos - 1) == 0x2013) {
1145 // convert "---" to emdash
1146 par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1152 par.insertChar(pos, c, cur.current_font,
1153 cur.buffer()->params().track_changes);
1154 cur.checkBufferStructure();
1156 // cur.screenUpdateFlags(Update::Force);
1157 bool boundary = cur.boundary()
1158 || tm.isRTLBoundary(cur.pit(), pos + 1);
1159 setCursor(cur, cur.pit(), pos + 1, false, boundary);
1164 void Text::charInserted(Cursor & cur)
1166 Paragraph & par = cur.paragraph();
1168 // register word if a non-letter was entered
1170 && !par.isWordSeparator(cur.pos() - 2)
1171 && par.isWordSeparator(cur.pos() - 1)) {
1172 // get the word in front of cursor
1173 LBUFERR(this == cur.text());
1179 // the cursor set functions have a special mechanism. When they
1180 // realize, that you left an empty paragraph, they will delete it.
1182 bool Text::cursorForwardOneWord(Cursor & cur)
1184 LBUFERR(this == cur.text());
1186 if (lyxrc.mac_like_cursor_movement) {
1187 DocIterator dit(cur);
1188 DocIterator prv(cur);
1189 bool inword = false;
1190 bool intext = dit.inTexted();
1191 while (!dit.atEnd()) {
1192 if (dit.inTexted()) { // no paragraphs in mathed
1193 Paragraph const & par = dit.paragraph();
1194 pos_type const pos = dit.pos();
1196 if (!par.isDeleted(pos)) {
1197 bool wordsep = par.isWordSeparator(pos);
1198 if (inword && wordsep)
1199 break; // stop at word end
1200 else if (!inword && !wordsep)
1204 } else if (intext) {
1205 // move to end of math
1206 while (!dit.inTexted() && !dit.atEnd()) dit.forwardPos();
1210 dit.forwardPosIgnoreCollapsed();
1212 if (dit.atEnd()) dit = prv;
1213 if (dit == cur) return false; // we didn't move
1216 // see comment above
1217 cur.bv().checkDepm(cur, orig);
1220 pos_type const lastpos = cur.lastpos();
1221 pit_type pit = cur.pit();
1222 pos_type pos = cur.pos();
1223 Paragraph const & par = cur.paragraph();
1225 // Paragraph boundary is a word boundary
1226 if (pos == lastpos || (pos + 1 == lastpos && par.isEnvSeparator(pos))) {
1227 if (pit != cur.lastpit())
1228 return setCursor(cur, pit + 1, 0);
1233 LASSERT(pos < lastpos, return false); // see above
1234 if (!par.isWordSeparator(pos))
1235 while (pos != lastpos && !par.isWordSeparator(pos))
1237 else if (par.isChar(pos))
1238 while (pos != lastpos && par.isChar(pos))
1240 else if (!par.isSpace(pos)) // non-char inset
1243 // Skip over white space
1244 while (pos != lastpos && par.isSpace(pos))
1247 // Don't skip a separator inset at the end of a paragraph
1248 if (pos == lastpos && pos && par.isEnvSeparator(pos - 1))
1251 return setCursor(cur, pit, pos);
1256 bool Text::cursorBackwardOneWord(Cursor & cur)
1258 LBUFERR(this == cur.text());
1260 if (lyxrc.mac_like_cursor_movement) {
1261 DocIterator dit(cur);
1262 bool inword = false;
1263 bool intext = dit.inTexted();
1264 while (!dit.atBegin()) {
1265 DocIterator prv(dit);
1266 dit.backwardPosIgnoreCollapsed();
1267 if (dit.inTexted()) { // no paragraphs in mathed
1268 Paragraph const & par = dit.paragraph();
1269 pos_type pos = dit.pos();
1271 if (!par.isDeleted(pos)) {
1272 bool wordsep = par.isWordSeparator(pos);
1273 if (inword && wordsep) {
1275 break; // stop at word begin
1276 } else if (!inword && !wordsep)
1280 } else if (intext) {
1281 // move to begin of math
1282 while (!dit.inTexted() && !dit.atBegin()) dit.backwardPos();
1286 if (dit == cur) return false; // we didn't move
1289 // see comment above cursorForwardOneWord
1290 cur.bv().checkDepm(cur, orig);
1293 Paragraph const & par = cur.paragraph();
1294 pit_type const pit = cur.pit();
1295 pos_type pos = cur.pos();
1297 // Paragraph boundary is a word boundary
1298 if (pos == 0 && pit != 0) {
1299 Paragraph & prevpar = getPar(pit - 1);
1300 pos = prevpar.size();
1301 // Don't stop after an environment separator
1302 if (pos && prevpar.isEnvSeparator(pos - 1))
1304 return setCursor(cur, pit - 1, pos);
1306 // Skip over white space
1307 while (pos != 0 && par.isSpace(pos - 1))
1310 if (pos != 0 && !par.isWordSeparator(pos - 1))
1311 while (pos != 0 && !par.isWordSeparator(pos - 1))
1313 else if (pos != 0 && par.isChar(pos - 1))
1314 while (pos != 0 && par.isChar(pos - 1))
1316 else if (pos != 0 && !par.isSpace(pos - 1)) // non-char inset
1319 return setCursor(cur, pit, pos);
1324 bool Text::cursorVisLeftOneWord(Cursor & cur)
1326 LBUFERR(this == cur.text());
1328 pos_type left_pos, right_pos;
1330 Cursor temp_cur = cur;
1332 // always try to move at least once...
1333 while (temp_cur.posVisLeft(true /* skip_inset */)) {
1335 // collect some information about current cursor position
1336 temp_cur.getSurroundingPos(left_pos, right_pos);
1337 bool left_is_letter =
1338 (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1339 bool right_is_letter =
1340 (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1342 // if we're not at a letter/non-letter boundary, continue moving
1343 if (left_is_letter == right_is_letter)
1346 // we should stop when we have an LTR word on our right or an RTL word
1348 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1349 temp_cur.buffer()->params(), left_pos).isRightToLeft())
1350 || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1351 temp_cur.buffer()->params(), right_pos).isRightToLeft()))
1355 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1356 true, temp_cur.boundary());
1360 bool Text::cursorVisRightOneWord(Cursor & cur)
1362 LBUFERR(this == cur.text());
1364 pos_type left_pos, right_pos;
1366 Cursor temp_cur = cur;
1368 // always try to move at least once...
1369 while (temp_cur.posVisRight(true /* skip_inset */)) {
1371 // collect some information about current cursor position
1372 temp_cur.getSurroundingPos(left_pos, right_pos);
1373 bool left_is_letter =
1374 (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1375 bool right_is_letter =
1376 (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1378 // if we're not at a letter/non-letter boundary, continue moving
1379 if (left_is_letter == right_is_letter)
1382 // we should stop when we have an LTR word on our right or an RTL word
1384 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1385 temp_cur.buffer()->params(),
1386 left_pos).isRightToLeft())
1387 || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1388 temp_cur.buffer()->params(),
1389 right_pos).isRightToLeft()))
1393 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1394 true, temp_cur.boundary());
1398 void Text::selectWord(Cursor & cur, word_location loc)
1400 LBUFERR(this == cur.text());
1401 CursorSlice from = cur.top();
1403 getWord(from, to, loc);
1404 if (cur.top() != from)
1405 setCursor(cur, from.pit(), from.pos());
1408 if (!cur.selection())
1410 setCursor(cur, to.pit(), to.pos());
1412 cur.setWordSelection(true);
1416 void Text::expandWordSel(Cursor & cur)
1418 // get selection of word around cur
1421 c.text()->selectWord(c, WHOLE_WORD);
1422 // use the correct word boundary, depending on selection direction
1423 if (cur.top() > cur.normalAnchor())
1424 cur.pos() = c.selEnd().pos();
1426 cur.pos() = c.selBegin().pos();
1430 void Text::selectAll(Cursor & cur)
1432 LBUFERR(this == cur.text());
1433 if (cur.lastpos() == 0 && cur.lastpit() == 0)
1435 // If the cursor is at the beginning, make sure the cursor ends there
1436 if (cur.pit() == 0 && cur.pos() == 0) {
1437 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1439 setCursor(cur, 0, 0);
1441 setCursor(cur, 0, 0);
1443 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1449 // Select the word currently under the cursor when no
1450 // selection is currently set
1451 bool Text::selectWordWhenUnderCursor(Cursor & cur, word_location loc)
1453 LBUFERR(this == cur.text());
1454 if (cur.selection())
1456 selectWord(cur, loc);
1457 return cur.selection();
1461 void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
1463 LBUFERR(this == cur.text());
1465 if (!cur.selection()) {
1466 if (!selectChange(cur))
1470 cur.recordUndoSelection();
1472 pit_type begPit = cur.selectionBegin().pit();
1473 pit_type endPit = cur.selectionEnd().pit();
1475 pos_type begPos = cur.selectionBegin().pos();
1476 pos_type endPos = cur.selectionEnd().pos();
1478 // keep selection info, because endPos becomes invalid after the first loop
1479 bool const endsBeforeEndOfPar = (endPos < pars_[endPit].size());
1481 // first, accept/reject changes within each individual paragraph (do not consider end-of-par)
1482 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1483 pos_type parSize = pars_[pit].size();
1485 // ignore empty paragraphs; otherwise, an assertion will fail for
1486 // acceptChanges(bparams, 0, 0) or rejectChanges(bparams, 0, 0)
1490 // do not consider first paragraph if the cursor starts at pos size()
1491 if (pit == begPit && begPos == parSize)
1494 // do not consider last paragraph if the cursor ends at pos 0
1495 if (pit == endPit && endPos == 0)
1496 break; // last iteration anyway
1498 pos_type const left = (pit == begPit ? begPos : 0);
1499 pos_type const right = (pit == endPit ? endPos : parSize);
1502 // there is no change here
1506 pars_[pit].acceptChanges(left, right);
1508 pars_[pit].rejectChanges(left, right);
1512 // next, accept/reject imaginary end-of-par characters
1514 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1515 pos_type pos = pars_[pit].size();
1517 // skip if the selection ends before the end-of-par
1518 if (pit == endPit && endsBeforeEndOfPar)
1519 break; // last iteration anyway
1521 // skip if this is not the last paragraph of the document
1522 // note: the user should be able to accept/reject the par break of the last par!
1523 if (pit == endPit && pit + 1 != int(pars_.size()))
1524 break; // last iteration anway
1527 if (pars_[pit].isInserted(pos)) {
1528 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1529 } else if (pars_[pit].isDeleted(pos)) {
1530 if (pit + 1 == int(pars_.size())) {
1531 // we cannot remove a par break at the end of the last paragraph;
1532 // instead, we mark it unchanged
1533 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1535 mergeParagraph(cur.buffer()->params(), pars_, pit);
1541 if (pars_[pit].isDeleted(pos)) {
1542 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1543 } else if (pars_[pit].isInserted(pos)) {
1544 if (pit + 1 == int(pars_.size())) {
1545 // we mark the par break at the end of the last paragraph unchanged
1546 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1548 mergeParagraph(cur.buffer()->params(), pars_, pit);
1556 // finally, invoke the DEPM
1557 deleteEmptyParagraphMechanism(begPit, endPit, begPos, endPos,
1558 cur.buffer()->params().track_changes);
1561 cur.clearSelection();
1562 setCursorIntern(cur, begPit, begPos);
1563 cur.screenUpdateFlags(Update::Force);
1564 cur.forceBufferUpdate();
1568 void Text::acceptChanges()
1570 BufferParams const & bparams = owner_->buffer().params();
1571 lyx::acceptChanges(pars_, bparams);
1572 deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.track_changes);
1576 void Text::rejectChanges()
1578 BufferParams const & bparams = owner_->buffer().params();
1579 pit_type pars_size = static_cast<pit_type>(pars_.size());
1581 // first, reject changes within each individual paragraph
1582 // (do not consider end-of-par)
1583 for (pit_type pit = 0; pit < pars_size; ++pit) {
1584 if (!pars_[pit].empty()) // prevent assertion failure
1585 pars_[pit].rejectChanges(0, pars_[pit].size());
1588 // next, reject imaginary end-of-par characters
1589 for (pit_type pit = 0; pit < pars_size; ++pit) {
1590 pos_type pos = pars_[pit].size();
1592 if (pars_[pit].isDeleted(pos)) {
1593 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1594 } else if (pars_[pit].isInserted(pos)) {
1595 if (pit == pars_size - 1) {
1596 // we mark the par break at the end of the last
1597 // paragraph unchanged
1598 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1600 mergeParagraph(bparams, pars_, pit);
1607 // finally, invoke the DEPM
1608 deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.track_changes);
1612 void Text::deleteWordForward(Cursor & cur, bool const force)
1614 LBUFERR(this == cur.text());
1615 if (cur.lastpos() == 0)
1619 cur.selection(true);
1620 cursorForwardOneWord(cur);
1622 if (force || !cur.confirmDeletion()) {
1623 cutSelection(cur, false);
1624 cur.checkBufferStructure();
1630 void Text::deleteWordBackward(Cursor & cur, bool const force)
1632 LBUFERR(this == cur.text());
1633 if (cur.lastpos() == 0)
1634 cursorBackward(cur);
1637 cur.selection(true);
1638 cursorBackwardOneWord(cur);
1640 if (force || !cur.confirmDeletion()) {
1641 cutSelection(cur, false);
1642 cur.checkBufferStructure();
1648 // Kill to end of line.
1649 void Text::changeCase(Cursor & cur, TextCase action, bool partial)
1651 LBUFERR(this == cur.text());
1655 bool const gotsel = cur.selection();
1657 from = cur.selBegin();
1661 getWord(from, to, partial ? PARTIAL_WORD : WHOLE_WORD);
1662 cursorForwardOneWord(cur);
1665 cur.recordUndoSelection();
1667 pit_type begPit = from.pit();
1668 pit_type endPit = to.pit();
1670 pos_type begPos = from.pos();
1671 pos_type endPos = to.pos();
1673 pos_type right = 0; // needed after the for loop
1675 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1676 Paragraph & par = pars_[pit];
1677 pos_type const pos = (pit == begPit ? begPos : 0);
1678 right = (pit == endPit ? endPos : par.size());
1679 par.changeCase(cur.buffer()->params(), pos, right, action);
1682 // the selection may have changed due to logically-only deleted chars
1684 setCursor(cur, begPit, begPos);
1686 setCursor(cur, endPit, right);
1689 setCursor(cur, endPit, right);
1691 cur.checkBufferStructure();
1695 bool Text::handleBibitems(Cursor & cur)
1697 if (cur.paragraph().layout().labeltype != LABEL_BIBLIO)
1703 BufferParams const & bufparams = cur.buffer()->params();
1704 Paragraph const & par = cur.paragraph();
1705 Cursor prevcur = cur;
1706 if (cur.pit() > 0) {
1708 prevcur.pos() = prevcur.lastpos();
1710 Paragraph const & prevpar = prevcur.paragraph();
1712 // if a bibitem is deleted, merge with previous paragraph
1713 // if this is a bibliography item as well
1714 if (cur.pit() > 0 && par.layout() == prevpar.layout()) {
1715 cur.recordUndo(prevcur.pit());
1716 mergeParagraph(bufparams, cur.text()->paragraphs(),
1718 cur.forceBufferUpdate();
1719 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1720 cur.screenUpdateFlags(Update::Force);
1724 // otherwise reset to default
1725 cur.paragraph().setPlainOrDefaultLayout(bufparams.documentClass());
1730 bool Text::erase(Cursor & cur)
1732 LASSERT(this == cur.text(), return false);
1733 bool needsUpdate = false;
1734 Paragraph & par = cur.paragraph();
1736 if (cur.pos() != cur.lastpos()) {
1737 // this is the code for a normal delete, not pasting
1739 cur.recordUndo(DELETE_UNDO);
1740 bool const was_inset = cur.paragraph().isInset(cur.pos());
1741 if(!par.eraseChar(cur.pos(), cur.buffer()->params().track_changes))
1742 // the character has been logically deleted only => skip it
1743 cur.top().forwardPos();
1746 cur.forceBufferUpdate();
1748 cur.checkBufferStructure();
1751 if (cur.pit() == cur.lastpit())
1752 return dissolveInset(cur);
1754 if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1755 cur.recordUndo(DELETE_UNDO);
1756 par.setChange(cur.pos(), Change(Change::DELETED));
1760 setCursorIntern(cur, cur.pit() + 1, 0);
1761 needsUpdate = backspacePos0(cur);
1765 needsUpdate |= handleBibitems(cur);
1768 // Make sure the cursor is correct. Is this really needed?
1769 // No, not really... at least not here!
1770 cur.top().setPitPos(cur.pit(), cur.pos());
1771 cur.checkBufferStructure();
1778 bool Text::backspacePos0(Cursor & cur)
1780 LBUFERR(this == cur.text());
1784 BufferParams const & bufparams = cur.buffer()->params();
1785 ParagraphList & plist = cur.text()->paragraphs();
1786 Paragraph const & par = cur.paragraph();
1787 Cursor prevcur = cur;
1789 prevcur.pos() = prevcur.lastpos();
1790 Paragraph const & prevpar = prevcur.paragraph();
1792 // is it an empty paragraph?
1793 if (cur.lastpos() == 0
1794 || (cur.lastpos() == 1 && par.isSeparator(0))) {
1795 cur.recordUndo(prevcur.pit());
1796 plist.erase(plist.iterator_at(cur.pit()));
1798 // is previous par empty?
1799 else if (prevcur.lastpos() == 0
1800 || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) {
1801 cur.recordUndo(prevcur.pit());
1802 plist.erase(plist.iterator_at(prevcur.pit()));
1804 // FIXME: Do we really not want to allow this???
1805 // Pasting is not allowed, if the paragraphs have different
1806 // layouts. I think it is a real bug of all other
1807 // word processors to allow it. It confuses the user.
1808 // Correction: Pasting is always allowed with standard-layout
1809 // or the empty layout.
1811 cur.recordUndo(prevcur.pit());
1812 mergeParagraph(bufparams, plist, prevcur.pit());
1815 cur.forceBufferUpdate();
1816 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1822 bool Text::backspace(Cursor & cur)
1824 LBUFERR(this == cur.text());
1825 bool needsUpdate = false;
1826 if (cur.pos() == 0) {
1828 return dissolveInset(cur);
1830 Cursor prev_cur = cur;
1833 if (!cur.paragraph().empty()
1834 && !prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1835 cur.recordUndo(prev_cur.pit(), prev_cur.pit());
1836 prev_cur.paragraph().setChange(prev_cur.lastpos(), Change(Change::DELETED));
1837 setCursorIntern(cur, prev_cur.pit(), prev_cur.lastpos());
1840 // The cursor is at the beginning of a paragraph, so
1841 // the backspace will collapse two paragraphs into one.
1842 needsUpdate = backspacePos0(cur);
1845 // this is the code for a normal backspace, not pasting
1847 cur.recordUndo(DELETE_UNDO);
1848 // We used to do cursorBackwardIntern() here, but it is
1849 // not a good idea since it triggers the auto-delete
1850 // mechanism. So we do a cursorBackwardIntern()-lite,
1851 // without the dreaded mechanism. (JMarc)
1852 setCursorIntern(cur, cur.pit(), cur.pos() - 1,
1853 false, cur.boundary());
1854 bool const was_inset = cur.paragraph().isInset(cur.pos());
1855 cur.paragraph().eraseChar(cur.pos(), cur.buffer()->params().track_changes);
1857 cur.forceBufferUpdate();
1859 cur.checkBufferStructure();
1862 if (cur.pos() == cur.lastpos())
1863 cur.setCurrentFont();
1865 needsUpdate |= handleBibitems(cur);
1867 // A singlePar update is not enough in this case.
1868 // cur.screenUpdateFlags(Update::Force);
1869 cur.top().setPitPos(cur.pit(), cur.pos());
1875 bool Text::dissolveInset(Cursor & cur)
1877 LASSERT(this == cur.text(), return false);
1879 if (isMainText() || cur.inset().nargs() != 1)
1882 cur.recordUndoInset();
1884 cur.selHandle(false);
1885 // save position inside inset
1886 pos_type spos = cur.pos();
1887 pit_type spit = cur.pit();
1888 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1890 // update cursor offset
1894 // remember position outside inset to delete inset later
1895 // we do not do it now to avoid memory reuse issues (see #10667).
1896 DocIterator inset_it = cur;
1900 Buffer & b = *cur.buffer();
1901 // Is there anything in this text?
1902 if (inset_non_empty) {
1904 // we clear the cache so that we won't get conflicts with labels
1905 // that get pasted into the buffer. we should update this before
1906 // its being empty matters. if not (i.e., if we encounter bugs),
1907 // then this should instead be:
1908 // cur.buffer().updateBuffer();
1909 // but we'll try the cheaper solution here.
1910 cur.buffer()->clearReferenceCache();
1912 ParagraphList & plist = paragraphs();
1913 if (!lyxrc.ct_markup_copied)
1914 // Do not revive deleted text
1915 lyx::acceptChanges(plist, b.params());
1917 // ERT paragraphs have the Language latex_language.
1918 // This is invalid outside of ERT, so we need to
1919 // change it to the buffer language.
1920 for (auto & p : plist)
1921 p.changeLanguage(b.params(), latex_language, b.language());
1923 /* If the inset is the only thing in paragraph and the layout
1924 * is not plain, then the layout of the first paragraph of
1925 * inset should be remembered.
1926 * FIXME: this does not work as expected when change tracking
1927 * is on However, we do not really know what to do in this
1930 DocumentClass const & tclass = cur.buffer()->params().documentClass();
1931 if (inset_it.lastpos() == 1
1932 && !tclass.isPlainLayout(plist[0].layout())
1933 && !tclass.isDefaultLayout(plist[0].layout())) {
1934 // Copy all parameters except depth.
1935 Paragraph & par = cur.paragraph();
1936 par.setLayout(plist[0].layout());
1937 depth_type const dpth = par.getDepth();
1938 par.params() = plist[0].params();
1939 par.params().depth(dpth);
1942 pasteParagraphList(cur, plist, b.params().documentClassPtr(),
1943 b.params().authors(),
1944 b.errorList("Paste"));
1947 // delete the inset now
1948 inset_it.paragraph().eraseChar(inset_it.pos(), b.params().track_changes);
1951 cur.pit() = min(cur.lastpit(), spit);
1952 cur.pos() = min(cur.lastpos(), spos);
1953 // Ensure the current language is set correctly (bug 6292)
1954 cur.text()->setCursor(cur, cur.pit(), cur.pos());
1955 cur.clearSelection();
1957 cur.forceBufferUpdate();
1963 bool Text::splitInset(Cursor & cur)
1965 LASSERT(this == cur.text(), return false);
1967 if (isMainText() || cur.inset().nargs() != 1)
1971 if (cur.selection()) {
1972 // start from selection begin
1973 setCursor(cur, cur.selBegin().pit(), cur.selBegin().pos());
1974 cur.clearSelection();
1976 // save split position inside inset
1977 // (we need to copy the whole inset first)
1978 pos_type spos = cur.pos();
1979 pit_type spit = cur.pit();
1980 // some things only need to be done if the inset has content
1981 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1983 // move right before the inset
1986 // remember position outside inset
1987 pos_type ipos = cur.pos();
1988 pit_type ipit = cur.pit();
1993 cap::copySelectionToTemp(cur);
1994 cur.clearSelection();
1996 // paste copied inset
1997 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
1998 cur.forceBufferUpdate();
2000 // if the inset has text, cut after split position
2001 // and paste to new inset
2002 if (inset_non_empty) {
2003 // go back to first inset
2004 cur.text()->setCursor(cur, ipit, ipos);
2006 setCursor(cur, spit, spos);
2008 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
2010 cap::cutSelectionToTemp(cur);
2012 cur.selHandle(false);
2014 bool atlastpos = false;
2015 if (cur.pos() == 0 && cur.pit() > 0) {
2016 // if we are at par start, remove this par
2017 cur.text()->backspace(cur);
2018 cur.forceBufferUpdate();
2019 } else if (cur.pos() == cur.lastpos())
2021 // Move out of and jump over inset
2029 cur.text()->selectAll(cur);
2030 cutSelection(cur, false);
2031 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
2032 cur.text()->setCursor(cur, 0, 0);
2033 if (atlastpos && cur.paragraph().isFreeSpacing() && cur.paragraph().empty()) {
2034 // We started from par end, remove extra empty par in free spacing insets
2035 cur.text()->erase(cur);
2036 cur.forceBufferUpdate();
2045 void Text::getWord(CursorSlice & from, CursorSlice & to,
2046 word_location const loc) const
2049 pars_[to.pit()].locateWord(from.pos(), to.pos(), loc);
2053 void Text::write(ostream & os) const
2055 Buffer const & buf = owner_->buffer();
2056 ParagraphList::const_iterator pit = paragraphs().begin();
2057 ParagraphList::const_iterator end = paragraphs().end();
2059 for (; pit != end; ++pit)
2060 pit->write(os, buf.params(), dth);
2062 // Close begin_deeper
2063 for(; dth > 0; --dth)
2064 os << "\n\\end_deeper";
2068 bool Text::read(Lexer & lex,
2069 ErrorList & errorList, InsetText * insetPtr)
2071 Buffer const & buf = owner_->buffer();
2072 depth_type depth = 0;
2075 while (lex.isOK()) {
2077 string const token = lex.getString();
2082 if (token == "\\end_inset")
2085 if (token == "\\end_body")
2088 if (token == "\\begin_body")
2091 if (token == "\\end_document") {
2096 if (token == "\\begin_layout") {
2097 lex.pushToken(token);
2100 par.setInsetOwner(insetPtr);
2101 par.params().depth(depth);
2102 par.setFont(0, Font(inherit_font, buf.params().language));
2103 pars_.push_back(par);
2104 readParagraph(pars_.back(), lex, errorList);
2106 // register the words in the global word list
2107 pars_.back().updateWords();
2108 } else if (token == "\\begin_deeper") {
2110 } else if (token == "\\end_deeper") {
2112 lex.printError("\\end_deeper: " "depth is already null");
2116 LYXERR0("Handling unknown body token: `" << token << '\'');
2120 // avoid a crash on weird documents (bug 4859)
2121 if (pars_.empty()) {
2123 par.setInsetOwner(insetPtr);
2124 par.params().depth(depth);
2125 par.setFont(0, Font(inherit_font,
2126 buf.params().language));
2127 par.setPlainOrDefaultLayout(buf.params().documentClass());
2128 pars_.push_back(par);
2135 // Returns the current state (font, depth etc.) as a message for status bar.
2136 docstring Text::currentState(CursorData const & cur, bool devel_mode) const
2138 LBUFERR(this == cur.text());
2139 Buffer & buf = *cur.buffer();
2140 Paragraph const & par = cur.paragraph();
2141 odocstringstream os;
2143 if (buf.params().track_changes)
2144 os << _("[Change Tracking] ");
2146 Change change = par.lookupChange(cur.pos());
2148 if (change.changed()) {
2149 docstring const author =
2150 buf.params().authors().get(change.author).nameAndEmail();
2151 docstring const date = formatted_datetime(change.changetime);
2152 os << bformat(_("Changed by %1$s[[author]] on %2$s[[date]]. "),
2156 // I think we should only show changes from the default
2158 // No, from the document font (MV)
2159 Font font = cur.real_current_font;
2160 font.fontInfo().reduce(buf.params().getFont().fontInfo());
2162 os << bformat(_("Font: %1$s"), font.stateText(&buf.params()));
2164 // The paragraph depth
2165 int depth = par.getDepth();
2167 os << bformat(_(", Depth: %1$d"), depth);
2169 // The paragraph spacing, but only if different from
2171 Spacing const & spacing = par.params().spacing();
2172 if (!spacing.isDefault()) {
2173 os << _(", Spacing: ");
2174 switch (spacing.getSpace()) {
2175 case Spacing::Single:
2178 case Spacing::Onehalf:
2181 case Spacing::Double:
2184 case Spacing::Other:
2185 os << _("Other (") << from_ascii(spacing.getValueAsString()) << ')';
2187 case Spacing::Default:
2188 // should never happen, do nothing
2193 // Custom text style
2194 InsetLayout const & layout = cur.inset().getLayout();
2195 if (layout.lyxtype() == InsetLyXType::CHARSTYLE)
2196 os << _(", Style: ") << translateIfPossible(layout.labelstring());
2199 os << _(", Inset: ") << &cur.inset();
2200 if (cur.lastidx() > 0)
2201 os << _(", Cell: ") << cur.idx();
2202 os << _(", Paragraph: ") << cur.pit();
2203 os << _(", Id: ") << par.id();
2204 os << _(", Position: ") << cur.pos();
2205 // FIXME: Why is the check for par.size() needed?
2206 // We are called with cur.pos() == par.size() quite often.
2207 if (!par.empty() && cur.pos() < par.size()) {
2208 // Force output of code point, not character
2209 size_t const c = par.getChar(cur.pos());
2210 if (c == META_INSET)
2211 os << ", Char: INSET";
2213 os << _(", Char: 0x") << hex << c;
2215 os << _(", Boundary: ") << cur.boundary();
2216 // Row & row = cur.textRow();
2217 // os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos());
2223 docstring Text::getPossibleLabel(DocIterator const & cur) const
2225 pit_type textpit = cur.pit();
2226 Layout const * layout = &(pars_[textpit].layout());
2228 // Will contain the label prefix.
2231 // For captions, we just take the caption type
2232 Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE);
2233 if (caption_inset) {
2234 string const & ftype = static_cast<InsetCaption *>(caption_inset)->floattype();
2235 FloatList const & fl = cur.buffer()->params().documentClass().floats();
2236 if (fl.typeExist(ftype)) {
2237 Floating const & flt = fl.getType(ftype);
2238 name = from_utf8(flt.refPrefix());
2241 name = from_utf8(ftype.substr(0,3));
2243 // For section, subsection, etc...
2244 if (layout->latextype == LATEX_PARAGRAPH && textpit != 0) {
2245 Layout const * layout2 = &(pars_[textpit - 1].layout());
2246 if (layout2->latextype != LATEX_PARAGRAPH) {
2251 if (layout->latextype != LATEX_PARAGRAPH)
2252 name = layout->refprefix;
2254 // If none of the above worked, see if the inset knows.
2256 InsetLayout const & il = cur.inset().getLayout();
2257 name = il.refprefix();
2262 docstring par_text = pars_[textpit].asString(AS_STR_SKIPDELETE);
2264 // The return string of math matrices might contain linebreaks
2265 par_text = subst(par_text, '\n', '-');
2266 int const numwords = 3;
2267 for (int i = 0; i < numwords; ++i) {
2268 if (par_text.empty())
2271 par_text = split(par_text, head, ' ');
2272 // Is it legal to use spaces in labels ?
2278 // Make sure it isn't too long
2279 unsigned int const max_label_length = 32;
2280 if (text.size() > max_label_length)
2281 text.resize(max_label_length);
2284 text = name + ':' + text;
2286 // We need a unique label
2287 docstring label = text;
2289 while (cur.buffer()->activeLabel(label)) {
2290 label = text + '-' + convert<docstring>(i);
2298 docstring Text::asString(int options) const
2300 return asString(0, pars_.size(), options);
2304 docstring Text::asString(pit_type beg, pit_type end, int options) const
2306 size_t i = size_t(beg);
2307 docstring str = pars_[i].asString(options);
2308 for (++i; i != size_t(end); ++i) {
2310 str += pars_[i].asString(options);
2316 void Text::shortenForOutliner(docstring & str, size_t const maxlen)
2318 support::truncateWithEllipsis(str, maxlen);
2319 for (char_type & c : str)
2320 if (c == L'\n' || c == L'\t')
2325 void Text::forOutliner(docstring & os, size_t const maxlen,
2326 bool const shorten) const
2328 pit_type end = pars_.size() - 1;
2329 if (0 <= end && !pars_[0].labelString().empty())
2330 os += pars_[0].labelString() + ' ';
2331 forOutliner(os, maxlen, 0, end, shorten);
2335 void Text::forOutliner(docstring & os, size_t const maxlen,
2336 pit_type pit_start, pit_type pit_end,
2337 bool const shorten) const
2339 size_t tmplen = shorten ? maxlen + 1 : maxlen;
2340 pit_type end = min(size_t(pit_end), pars_.size() - 1);
2342 for (pit_type i = pit_start; i <= end && os.length() < tmplen; ++i) {
2345 // This function lets the first label be treated separately
2346 pars_[i].forOutliner(os, tmplen, false, !first);
2350 shortenForOutliner(os, maxlen);
2354 void Text::charsTranspose(Cursor & cur)
2356 LBUFERR(this == cur.text());
2358 pos_type pos = cur.pos();
2360 // If cursor is at beginning or end of paragraph, do nothing.
2361 if (pos == cur.lastpos() || pos == 0)
2364 Paragraph & par = cur.paragraph();
2366 // Get the positions of the characters to be transposed.
2367 pos_type pos1 = pos - 1;
2368 pos_type pos2 = pos;
2370 // In change tracking mode, ignore deleted characters.
2371 while (pos2 < cur.lastpos() && par.isDeleted(pos2))
2373 if (pos2 == cur.lastpos())
2376 while (pos1 >= 0 && par.isDeleted(pos1))
2381 // Don't do anything if one of the "characters" is not regular text.
2382 if (par.isInset(pos1) || par.isInset(pos2))
2385 // Store the characters to be transposed (including font information).
2386 char_type const char1 = par.getChar(pos1);
2388 par.getFontSettings(cur.buffer()->params(), pos1);
2390 char_type const char2 = par.getChar(pos2);
2392 par.getFontSettings(cur.buffer()->params(), pos2);
2394 // And finally, we are ready to perform the transposition.
2395 // Track the changes if Change Tracking is enabled.
2396 bool const trackChanges = cur.buffer()->params().track_changes;
2400 par.eraseChar(pos2, trackChanges);
2401 par.eraseChar(pos1, trackChanges);
2402 par.insertChar(pos1, char2, font2, trackChanges);
2403 par.insertChar(pos2, char1, font1, trackChanges);
2405 cur.checkBufferStructure();
2407 // After the transposition, move cursor to after the transposition.
2408 setCursor(cur, cur.pit(), pos2);
2413 DocIterator Text::macrocontextPosition() const
2415 return macrocontext_position_;
2419 void Text::setMacrocontextPosition(DocIterator const & pos)
2421 macrocontext_position_ = pos;
2425 bool Text::completionSupported(Cursor const & cur) const
2427 Paragraph const & par = cur.paragraph();
2428 return !cur.buffer()->isReadonly()
2431 && (cur.pos() >= par.size() || par.isWordSeparator(cur.pos()))
2432 && !par.isWordSeparator(cur.pos() - 1);
2436 CompletionList const * Text::createCompletionList(Cursor const & cur) const
2438 WordList const & list = theWordList(cur.getFont().language()->lang());
2439 return new TextCompletionList(cur, list);
2443 bool Text::insertCompletion(Cursor & cur, docstring const & s)
2445 LBUFERR(cur.bv().cursor() == cur);
2446 if (cur.buffer()->isReadonly())
2450 cur.bv().cursor() = cur;
2451 if (!(cur.result().screenUpdate() & Update::Force))
2452 cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
2457 docstring Text::completionPrefix(Cursor const & cur) const
2459 CursorSlice from = cur.top();
2460 CursorSlice to = from;
2461 getWord(from, to, PREVIOUS_WORD);
2463 return cur.paragraph().asString(from.pos(), to.pos());
2466 bool Text::isMainText() const
2468 return &owner_->buffer().text() == this;
2472 // Note that this is supposed to return a fully realized font.
2473 FontInfo Text::layoutFont(pit_type const pit) const
2475 Layout const & layout = pars_[pit].layout();
2477 if (!pars_[pit].getDepth()) {
2478 FontInfo lf = layout.resfont;
2479 // In case the default family has been customized
2480 if (layout.font.family() == INHERIT_FAMILY)
2481 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
2482 FontInfo icf = 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 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
2912 Paragraph & par = c.paragraph();
2913 ParagraphParameters params = par.params();
2914 params.read(argument, merge);
2915 // Changes to label width string apply to all paragraphs
2916 // with same layout in a sequence.
2917 // Do this only once for a selected range of paragraphs
2918 // of the same layout and depth.
2920 par.params().apply(params, par.layout());
2921 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2922 setLabelWidthStringToSequence(c, params.labelWidthString());
2923 priordepth = par.getDepth();
2924 priorlayout = par.layout();
2929 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
2931 LBUFERR(cur.text());
2933 depth_type priordepth = -1;
2936 c.setCursor(cur.selectionBegin());
2937 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
2938 Paragraph & par = c.paragraph();
2939 // Changes to label width string apply to all paragraphs
2940 // with same layout in a sequence.
2941 // Do this only once for a selected range of paragraphs
2942 // of the same layout and depth.
2944 par.params().apply(p, par.layout());
2945 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2946 setLabelWidthStringToSequence(c,
2947 par.params().labelWidthString());
2948 priordepth = par.getDepth();
2949 priorlayout = par.layout();
2954 // this really should just insert the inset and not move the cursor.
2955 void Text::insertInset(Cursor & cur, Inset * inset)
2957 LBUFERR(this == cur.text());
2959 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
2960 Change(cur.buffer()->params().track_changes
2961 ? Change::INSERTED : Change::UNCHANGED));
2965 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
2966 bool setfont, bool boundary)
2968 TextMetrics const & tm = cur.bv().textMetrics(this);
2969 bool const update_needed = !tm.contains(pit);
2971 setCursorIntern(cur, pit, pos, setfont, boundary);
2972 return cur.bv().checkDepm(cur, old) || update_needed;
2976 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
2977 bool setfont, bool boundary)
2979 LBUFERR(this == cur.text());
2980 cur.boundary(boundary);
2981 cur.top().setPitPos(pit, pos);
2983 cur.setCurrentFont();
2987 bool Text::checkAndActivateInset(Cursor & cur, bool front)
2989 if (front && cur.pos() == cur.lastpos())
2991 if (!front && cur.pos() == 0)
2993 Inset * inset = front ? cur.nextInset() : cur.prevInset();
2994 if (!inset || !inset->editable())
2996 if (cur.selection() && cur.realAnchor().find(inset) == -1)
2999 * Apparently, when entering an inset we are expected to be positioned
3000 * *before* it in the containing paragraph, regardless of the direction
3001 * from which we are entering. Otherwise, cursor placement goes awry,
3002 * and when we exit from the beginning, we'll be placed *after* the
3007 inset->edit(cur, front);
3008 cur.setCurrentFont();
3009 cur.boundary(false);
3014 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
3016 if (cur.pos() == -1)
3018 if (cur.pos() == cur.lastpos())
3020 Paragraph & par = cur.paragraph();
3021 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
3022 if (!inset || !inset->editable())
3024 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3026 inset->edit(cur, movingForward,
3027 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
3028 cur.setCurrentFont();
3029 cur.boundary(false);
3034 bool Text::cursorBackward(Cursor & cur)
3036 // Tell BufferView to test for FitCursor in any case!
3037 cur.screenUpdateFlags(Update::FitCursor);
3039 // not at paragraph start?
3040 if (cur.pos() > 0) {
3041 // if on right side of boundary (i.e. not at paragraph end, but line end)
3042 // -> skip it, i.e. set boundary to true, i.e. go only logically left
3043 // there are some exceptions to ignore this: lineseps, newlines, spaces
3045 // some effectless debug code to see the values in the debugger
3046 bool bound = cur.boundary();
3047 int rowpos = cur.textRow().pos();
3048 int pos = cur.pos();
3049 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
3050 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
3051 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
3053 if (!cur.boundary() &&
3054 cur.textRow().pos() == cur.pos() &&
3055 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
3056 !cur.paragraph().isNewline(cur.pos() - 1) &&
3057 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
3058 !cur.paragraph().isSeparator(cur.pos() - 1)) {
3059 return setCursor(cur, cur.pit(), cur.pos(), true, true);
3062 // go left and try to enter inset
3063 if (checkAndActivateInset(cur, false))
3066 // normal character left
3067 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
3070 // move to the previous paragraph or do nothing
3071 if (cur.pit() > 0) {
3072 Paragraph & par = getPar(cur.pit() - 1);
3073 pos_type lastpos = par.size();
3074 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
3075 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
3077 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
3083 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
3085 Cursor temp_cur = cur;
3086 temp_cur.posVisLeft(skip_inset);
3087 if (temp_cur.depth() > cur.depth()) {
3091 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3092 true, temp_cur.boundary());
3096 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
3098 Cursor temp_cur = cur;
3099 temp_cur.posVisRight(skip_inset);
3100 if (temp_cur.depth() > cur.depth()) {
3104 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3105 true, temp_cur.boundary());
3109 bool Text::cursorForward(Cursor & cur)
3111 // Tell BufferView to test for FitCursor in any case!
3112 cur.screenUpdateFlags(Update::FitCursor);
3114 // not at paragraph end?
3115 if (cur.pos() != cur.lastpos()) {
3116 // in front of editable inset, i.e. jump into it?
3117 if (checkAndActivateInset(cur, true))
3120 TextMetrics const & tm = cur.bv().textMetrics(this);
3121 // if left of boundary -> just jump to right side
3122 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
3123 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
3124 return setCursor(cur, cur.pit(), cur.pos(), true, false);
3126 // next position is left of boundary,
3127 // but go to next line for special cases like space, newline, linesep
3129 // some effectless debug code to see the values in the debugger
3130 int endpos = cur.textRow().endpos();
3131 int lastpos = cur.lastpos();
3132 int pos = cur.pos();
3133 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
3134 bool newline = cur.paragraph().isNewline(cur.pos());
3135 bool sep = cur.paragraph().isSeparator(cur.pos());
3136 if (cur.pos() != cur.lastpos()) {
3137 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
3138 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
3139 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
3142 if (cur.textRow().endpos() == cur.pos() + 1) {
3143 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
3144 cur.pos() + 1 == cur.lastpos() &&
3145 cur.pit() != cur.lastpit()) {
3146 // move to next paragraph
3147 return setCursor(cur, cur.pit() + 1, 0, true, false);
3148 } else if (cur.textRow().endpos() != cur.lastpos() &&
3149 !cur.paragraph().isNewline(cur.pos()) &&
3150 !cur.paragraph().isEnvSeparator(cur.pos()) &&
3151 !cur.paragraph().isLineSeparator(cur.pos()) &&
3152 !cur.paragraph().isSeparator(cur.pos())) {
3153 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3157 // in front of RTL boundary? Stay on this side of the boundary because:
3158 // ab|cDDEEFFghi -> abc|DDEEFFghi
3159 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
3160 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3163 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
3166 // move to next paragraph
3167 if (cur.pit() != cur.lastpit())
3168 return setCursor(cur, cur.pit() + 1, 0, true, false);
3173 bool Text::cursorUpParagraph(Cursor & cur)
3175 bool updated = false;
3177 updated = setCursor(cur, cur.pit(), 0);
3178 else if (cur.pit() != 0)
3179 updated = setCursor(cur, cur.pit() - 1, 0);
3184 bool Text::cursorDownParagraph(Cursor & cur)
3186 bool updated = false;
3187 if (cur.pit() != cur.lastpit())
3188 if (lyxrc.mac_like_cursor_movement)
3189 if (cur.pos() == cur.lastpos())
3190 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
3192 updated = setCursor(cur, cur.pit(), cur.lastpos());
3194 updated = setCursor(cur, cur.pit() + 1, 0);
3196 updated = setCursor(cur, cur.pit(), cur.lastpos());
3202 /** delete num_spaces characters between from and to. Return the
3203 * number of spaces that got physically deleted (not marked as
3205 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
3206 int num_spaces, bool const trackChanges)
3208 if (num_spaces <= 0)
3211 // First, delete spaces marked as inserted
3213 while (pos < to && num_spaces > 0) {
3214 Change const & change = par.lookupChange(pos);
3215 if (change.inserted() && change.currentAuthor()) {
3216 par.eraseChar(pos, trackChanges);
3223 // Then remove remaining spaces
3224 int const psize = par.size();
3225 par.eraseChars(from, from + num_spaces, trackChanges);
3226 return psize - par.size();
3232 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
3233 Cursor & old, bool & need_anchor_change)
3235 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
3237 Paragraph & oldpar = old.paragraph();
3238 bool const trackChanges = cur.buffer()->params().track_changes;
3239 bool result = false;
3241 // We do nothing if cursor did not move
3242 if (cur.top() == old.top())
3245 // We do not do anything on read-only documents
3246 if (cur.buffer()->isReadonly())
3249 // Whether a common inset is found and whether the cursor is still in
3250 // the same paragraph (possibly nested).
3251 int const depth = cur.find(&old.inset());
3252 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
3253 && old.pit() == cur[depth].pit();
3256 * (1) If the chars around the old cursor were spaces and the
3257 * paragraph is not in free spacing mode, delete some of them, but
3258 * only if the cursor has really moved.
3261 /* There are still some small problems that can lead to
3262 double spaces stored in the document file or space at
3263 the beginning of paragraphs(). This happens if you have
3264 the cursor between two spaces and then save. Or if you
3265 cut and paste and the selection has a space at the
3266 beginning and then save right after the paste. (Lgb)
3268 if (!oldpar.isFreeSpacing()) {
3269 // find range of spaces around cursors
3270 pos_type from = old.pos();
3272 && oldpar.isLineSeparator(from - 1)
3273 && !oldpar.isDeleted(from - 1))
3275 pos_type to = old.pos();
3276 while (to < old.lastpos()
3277 && oldpar.isLineSeparator(to)
3278 && !oldpar.isDeleted(to))
3281 int num_spaces = to - from;
3282 // If we are not at the start of the paragraph, keep one space
3283 if (from != to && from > 0)
3286 // If cursor is inside range, keep one additional space
3287 if (same_par && cur.pos() > from && cur.pos() < to)
3290 // Remove spaces and adapt cursor.
3291 if (num_spaces > 0) {
3294 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
3295 // correct cur position
3296 // FIXME: there can be other cursors pointing there, we should update them
3298 if (cur[depth].pos() >= to)
3299 cur[depth].pos() -= deleted;
3300 else if (cur[depth].pos() > from)
3301 cur[depth].pos() = min(from + 1, old.lastpos());
3302 need_anchor_change = true;
3309 * (2) If the paragraph where the cursor was is empty, delete it
3312 // only do our other magic if we changed paragraph
3316 // only do our magic if the paragraph is empty
3317 if (!oldpar.empty())
3320 // don't delete anything if this is the ONLY paragraph!
3321 if (old.lastpit() == 0)
3324 // Do not delete empty paragraphs with keepempty set.
3325 if (oldpar.allowEmpty())
3329 old.recordUndo(max(old.pit() - 1, pit_type(0)),
3330 min(old.pit() + 1, old.lastpit()));
3331 ParagraphList & plist = old.text()->paragraphs();
3332 bool const soa = oldpar.params().startOfAppendix();
3333 plist.erase(plist.iterator_at(old.pit()));
3334 // do not lose start of appendix marker (bug 4212)
3335 if (soa && old.pit() < pit_type(plist.size()))
3336 plist[old.pit()].params().startOfAppendix(true);
3338 // see #warning (FIXME?) above
3339 if (cur.depth() >= old.depth()) {
3340 CursorSlice & curslice = cur[old.depth() - 1];
3341 if (&curslice.inset() == &old.inset()
3342 && curslice.idx() == old.idx()
3343 && curslice.pit() > old.pit()) {
3345 // since a paragraph has been deleted, all the
3346 // insets after `old' have been copied and
3347 // their address has changed. Therefore we
3348 // need to `regenerate' cur. (JMarc)
3349 cur.updateInsets(&(cur.bottom().inset()));
3350 need_anchor_change = true;
3358 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
3360 pos_type last_pos = pars_[last].size() - 1;
3361 deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
3365 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
3366 pos_type first_pos, pos_type last_pos,
3369 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
3371 for (pit_type pit = first; pit <= last; ++pit) {
3372 Paragraph & par = pars_[pit];
3375 * (1) Delete consecutive spaces
3377 if (!par.isFreeSpacing()) {
3378 pos_type from = (pit == first) ? first_pos : 0;
3379 pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
3380 while (from < to_pos) {
3382 while (from < par.size()
3383 && (!par.isLineSeparator(from) || par.isDeleted(from)))
3385 // find string of spaces
3387 while (to < par.size()
3388 && par.isLineSeparator(to) && !par.isDeleted(to))
3390 // empty? We are done
3394 int num_spaces = to - from;
3396 // If we are not at the extremity of the paragraph, keep one space
3397 if (from != to && from > 0 && to < par.size())
3400 // Remove spaces if needed
3401 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
3402 from = to - deleted;
3407 * (2) Delete empty pragraphs
3410 // don't delete anything if this is the only remaining paragraph
3411 // within the given range. Note: Text::acceptOrRejectChanges()
3412 // sets the cursor to 'first' after calling DEPM
3416 // don't delete empty paragraphs with keepempty set
3417 if (par.allowEmpty())
3420 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
3421 pars_.erase(pars_.iterator_at(pit));
3433 typedef limited_stack<pair<docstring, Font>> FontStack;
3434 static FontStack freeFonts(15);
3435 static bool toggleall = false;
3437 void toggleAndShow(Cursor & cur, Text * text,
3438 Font const & font, bool togall = true)
3440 text->toggleFree(cur, font, togall);
3442 if (font.language() != ignore_language ||
3443 font.fontInfo().number() != FONT_IGNORE) {
3444 TextMetrics const & tm = cur.bv().textMetrics(text);
3445 if (cur.boundary() != tm.isRTLBoundary(cur.pit(), cur.pos(),
3446 cur.real_current_font))
3447 text->setCursor(cur, cur.pit(), cur.pos(),
3448 false, !cur.boundary());
3449 if (font.language() != ignore_language)
3450 // We need a buffer update if we change the language
3451 // (e.g., with info insets or if the selection contains
3453 cur.forceBufferUpdate();
3458 void moveCursor(Cursor & cur, bool selecting)
3460 if (selecting || cur.mark())
3465 void finishChange(Cursor & cur, bool selecting)
3468 moveCursor(cur, selecting);
3472 void mathDispatch(Cursor & cur, FuncRequest const & cmd)
3475 docstring sel = cur.selectionAsString(false);
3477 // It may happen that sel is empty but there is a selection
3478 replaceSelection(cur);
3480 // Is this a valid formula?
3484 #ifdef ENABLE_ASSERTIONS
3485 const int old_pos = cur.pos();
3487 cur.insert(new InsetMathHull(cur.buffer(), hullSimple));
3488 #ifdef ENABLE_ASSERTIONS
3489 LATTEST(old_pos == cur.pos());
3491 cur.nextInset()->edit(cur, true);
3492 if (cmd.action() != LFUN_MATH_MODE)
3493 // LFUN_MATH_MODE has a different meaning in math mode
3496 InsetMathHull * formula = new InsetMathHull(cur.buffer());
3497 string const selstr = to_utf8(sel);
3498 istringstream is(selstr);
3501 if (!formula->readQuiet(lex)) {
3502 // No valid formula, let's try with delims
3503 is.str("$" + selstr + "$");
3505 if (!formula->readQuiet(lex)) {
3506 // Still not valid, leave it as is
3513 cur.insert(formula);
3514 cur.nextInset()->edit(cur, true);
3515 LASSERT(cur.inMathed(), return);
3518 cur.selection(true);
3519 cur.pos() = cur.lastpos();
3520 if (cmd.action() != LFUN_MATH_MODE)
3521 // LFUN_MATH_MODE has a different meaning in math mode
3523 cur.clearSelection();
3524 cur.pos() = cur.lastpos();
3528 cur.message(from_utf8(N_("Math editor mode")));
3530 cur.message(from_utf8(N_("No valid math formula")));
3534 void regexpDispatch(Cursor & cur, FuncRequest const & cmd)
3536 LASSERT(cmd.action() == LFUN_REGEXP_MODE, return);
3537 if (cur.inRegexped()) {
3538 cur.message(_("Already in regular expression mode"));
3542 docstring sel = cur.selectionAsString(false);
3544 // It may happen that sel is empty but there is a selection
3545 replaceSelection(cur);
3547 cur.insert(new InsetMathHull(cur.buffer(), hullRegexp));
3548 cur.nextInset()->edit(cur, true);
3549 cur.niceInsert(sel);
3551 cur.message(_("Regexp editor mode"));
3555 void specialChar(Cursor & cur, InsetSpecialChar::Kind kind)
3558 cap::replaceSelection(cur);
3559 cur.insert(new InsetSpecialChar(kind));
3564 void ipaChar(Cursor & cur, InsetIPAChar::Kind kind)
3567 cap::replaceSelection(cur);
3568 cur.insert(new InsetIPAChar(kind));
3573 bool doInsertInset(Cursor & cur, Text * text,
3574 FuncRequest const & cmd, bool edit,
3575 bool pastesel, bool resetfont = false)
3577 Buffer & buffer = cur.bv().buffer();
3578 BufferParams const & bparams = buffer.params();
3579 Inset * inset = createInset(&buffer, cmd);
3583 if (InsetCollapsible * ci = inset->asInsetCollapsible())
3584 ci->setButtonLabel();
3587 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3588 bool cotextinsert = false;
3589 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3590 Layout const & lay = cur.paragraph().layout();
3591 Layout::LaTeXArgMap args = lay.args();
3592 Layout::LaTeXArgMap::const_iterator const lait = args.find(ia->name());
3593 if (lait != args.end())
3594 cotextinsert = (*lait).second.insertcotext;
3596 InsetLayout const & il = cur.inset().getLayout();
3598 Layout::LaTeXArgMap::const_iterator const ilait = args.find(ia->name());
3599 if (ilait != args.end())
3600 cotextinsert = (*ilait).second.insertcotext;
3602 // The argument requests to insert a copy of the co-text to the inset
3605 // If we have a selection within a paragraph, use this
3606 if (cur.selection() && cur.selBegin().pit() == cur.selEnd().pit())
3607 ds = cur.selectionAsString(false);
3608 // else use the whole paragraph
3610 ds = cur.paragraph().asString();
3611 text->insertInset(cur, inset);
3612 ia->init(cur.paragraph());
3614 inset->edit(cur, true);
3615 // Now put co-text into inset
3616 Font const f(inherit_font, cur.current_font.language());
3618 cur.text()->insertStringAsLines(cur, ds, f);
3619 cur.leaveInset(*inset);
3625 bool gotsel = false;
3626 bool move_layout = false;
3627 if (cur.selection()) {
3628 if (cmd.action() == LFUN_INDEX_INSERT)
3629 copySelectionToTemp(cur);
3631 cutSelectionToTemp(cur, pastesel);
3632 /* Move layout information inside the inset if the whole
3633 * paragraph and the inset allows setting layout
3634 * FIXME: this does not work as expected when change tracking is on
3635 * However, we do not really know what to do in this case.
3636 * FIXME: figure out a good test in the environment case (see #12251).
3638 if (cur.paragraph().layout().isCommand()
3639 && cur.paragraph().empty()
3640 && !inset->forcePlainLayout()) {
3641 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3645 cur.clearSelection();
3647 } else if (cmd.action() == LFUN_INDEX_INSERT) {
3648 gotsel = text->selectWordWhenUnderCursor(cur, WHOLE_WORD);
3649 copySelectionToTemp(cur);
3650 cur.clearSelection();
3652 text->insertInset(cur, inset);
3654 InsetText * inset_text = inset->asInsetText();
3656 Font const & font = inset->inheritFont()
3657 ? cur.bv().textMetrics(text).displayFont(cur.pit(), cur.pos())
3658 : bparams.getFont();
3659 inset_text->setOuterFont(cur.bv(), font.fontInfo());
3662 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3663 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3664 ia->init(cur.paragraph());
3668 inset->edit(cur, true);
3670 if (!gotsel || !pastesel)
3673 pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
3674 cur.buffer()->errors("Paste");
3675 cur.clearSelection(); // bug 393
3679 // Reset of font (not language) is requested.
3680 // Used by InsetIndex (#11961).
3681 Language const * lang = cur.getFont().language();
3682 Font font(bparams.getFont().fontInfo(), lang);
3683 cur.paragraph().resetFonts(font);
3685 inset_text->fixParagraphsFont();
3688 /* If the containing paragraph has kept its layout, reset the
3689 * layout of the first paragraph of the inset.
3692 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3693 // FIXME: what does this do?
3694 if (cmd.action() == LFUN_FLEX_INSERT)
3697 cur.leaveInset(*inset);
3698 if (cmd.action() == LFUN_PREVIEW_INSERT
3699 || cmd.action() == LFUN_IPA_INSERT)
3701 notifyCursorLeavesOrEnters(old, cur);
3703 cur.leaveInset(*inset);
3704 // reset surrounding par to default
3705 DocumentClass const & dc = bparams.documentClass();
3706 docstring const layoutname = inset->usePlainLayout()
3707 ? dc.plainLayoutName()
3708 : dc.defaultLayoutName();
3709 text->setLayout(cur, layoutname);
3715 /// the type of outline operation
3717 OutlineUp, // Move this header with text down
3718 OutlineDown, // Move this header with text up
3719 OutlineIn, // Make this header deeper
3720 OutlineOut // Make this header shallower
3724 void insertSeparator(Cursor const & cur, depth_type const depth)
3726 Buffer & buf = *cur.buffer();
3727 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
3728 DocumentClass const & tc = buf.params().documentClass();
3729 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
3730 + from_ascii("\" ignoreautonests")));
3731 // FIXME: Bibitem mess!
3732 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
3733 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
3734 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
3735 while (cur.paragraph().params().depth() > depth)
3736 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
3740 void outline(OutlineOp mode, Cursor & cur, Text * text)
3742 Buffer & buf = *cur.buffer();
3743 pit_type & pit = cur.pit();
3744 ParagraphList & pars = buf.text().paragraphs();
3745 ParagraphList::iterator const bgn = pars.begin();
3746 // The first paragraph of the area to be copied:
3747 ParagraphList::iterator start = pars.iterator_at(pit);
3748 // The final paragraph of area to be copied:
3749 ParagraphList::iterator finish = start;
3750 ParagraphList::iterator const end = pars.end();
3751 depth_type const current_depth = cur.paragraph().params().depth();
3753 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
3756 // Move out (down) from this section header
3760 // Seek the one (on same level) below
3761 for (; finish != end; ++finish) {
3762 toclevel = buf.text().getTocLevel(distance(bgn, finish));
3763 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
3769 if (start == pars.begin())
3772 ParagraphList::iterator dest = start;
3773 // Move out (up) from this header
3776 // Search previous same-level header above
3779 toclevel = buf.text().getTocLevel(distance(bgn, dest));
3781 && (toclevel == Layout::NOT_IN_TOC
3782 || toclevel > thistoclevel));
3783 // Not found; do nothing
3784 if (toclevel == Layout::NOT_IN_TOC || toclevel > thistoclevel)
3786 pit_type newpit = distance(bgn, dest);
3787 pit_type const len = distance(start, finish);
3788 pit_type const deletepit = pit + len;
3789 buf.undo().recordUndo(cur, newpit, deletepit - 1);
3790 // If we move an environment upwards, make sure it is
3791 // separated from its new neighbour below:
3792 // If an environment of the same layout follows, and the moved
3793 // paragraph sequence does not end with a separator, insert one.
3794 ParagraphList::iterator lastmoved = finish;
3796 if (start->layout().isEnvironment()
3797 && dest->layout() == start->layout()
3798 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3799 cur.pit() = distance(bgn, lastmoved);
3800 cur.pos() = cur.lastpos();
3801 insertSeparator(cur, current_depth);
3804 // Likewise, if we moved an environment upwards, make sure it
3805 // is separated from its new neighbour above.
3806 // The paragraph before the target of movement
3808 ParagraphList::iterator before = dest;
3810 // Get the parent paragraph (outer in nested context)
3811 pit_type const parent =
3812 before->params().depth() > current_depth
3813 ? text->depthHook(distance(bgn, before), current_depth)
3814 : distance(bgn, before);
3815 // If a environment with same layout preceeds the moved one in the new
3816 // position, and there is no separator yet, insert one.
3817 if (start->layout().isEnvironment()
3818 && pars[parent].layout() == start->layout()
3819 && !before->isEnvSeparator(before->beginOfBody())) {
3820 cur.pit() = distance(bgn, before);
3821 cur.pos() = cur.lastpos();
3822 insertSeparator(cur, current_depth);
3826 newpit = distance(bgn, dest);
3827 pars.splice(dest, start, finish);
3835 // Go one down from *this* header:
3836 ParagraphList::iterator dest = next(finish, 1);
3837 // Go further down to find header to insert in front of:
3838 for (; dest != end; ++dest) {
3839 toclevel = buf.text().getTocLevel(distance(bgn, dest));
3840 if (toclevel != Layout::NOT_IN_TOC
3841 && toclevel <= thistoclevel)
3844 // One such was found, so go on...
3845 // If we move an environment downwards, make sure it is
3846 // separated from its new neighbour above.
3847 pit_type newpit = distance(bgn, dest);
3848 buf.undo().recordUndo(cur, pit, newpit - 1);
3849 // The paragraph before the target of movement
3850 ParagraphList::iterator before = dest;
3852 // Get the parent paragraph (outer in nested context)
3853 pit_type const parent =
3854 before->params().depth() > current_depth
3855 ? text->depthHook(distance(bgn, before), current_depth)
3856 : distance(bgn, before);
3857 // If a environment with same layout preceeds the moved one in the new
3858 // position, and there is no separator yet, insert one.
3859 if (start->layout().isEnvironment()
3860 && pars[parent].layout() == start->layout()
3861 && !before->isEnvSeparator(before->beginOfBody())) {
3862 cur.pit() = distance(bgn, before);
3863 cur.pos() = cur.lastpos();
3864 insertSeparator(cur, current_depth);
3867 // Likewise, make sure moved environments are separated
3868 // from their new neighbour below:
3869 // If an environment of the same layout follows, and the moved
3870 // paragraph sequence does not end with a separator, insert one.
3871 ParagraphList::iterator lastmoved = finish;
3874 && start->layout().isEnvironment()
3875 && dest->layout() == start->layout()
3876 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3877 cur.pit() = distance(bgn, lastmoved);
3878 cur.pos() = cur.lastpos();
3879 insertSeparator(cur, current_depth);
3882 newpit = distance(bgn, dest);
3883 pit_type const len = distance(start, finish);
3884 pars.splice(dest, start, finish);
3885 cur.pit() = newpit - len;
3890 // We first iterate without actually doing something
3891 // in order to check whether the action flattens the structure.
3892 // If so, warn (#11178).
3893 ParagraphList::iterator cstart = start;
3894 bool strucchange = false;
3895 for (; cstart != finish; ++cstart) {
3896 toclevel = buf.text().getTocLevel(distance(bgn, cstart));
3897 if (toclevel == Layout::NOT_IN_TOC)
3900 DocumentClass const & tc = buf.params().documentClass();
3901 int const newtoclevel =
3902 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3905 for (auto const & lay : tc) {
3906 if (lay.toclevel == newtoclevel
3907 && lay.isNumHeadingLabelType()
3908 && cstart->layout().isNumHeadingLabelType()) {
3919 && frontend::Alert::prompt(_("Action flattens document structure"),
3920 _("This action will cause some headings that have been "
3921 "on different level before to be on the same level "
3922 "since there is no more lower or higher heading level. "
3925 _("&Yes, continue nonetheless"),
3926 _("&No, quit operation")) == 1)
3929 pit_type const len = distance(start, finish);
3930 buf.undo().recordUndo(cur, pit, pit + len - 1);
3931 for (; start != finish; ++start) {
3932 toclevel = buf.text().getTocLevel(distance(bgn, start));
3933 if (toclevel == Layout::NOT_IN_TOC)
3936 DocumentClass const & tc = buf.params().documentClass();
3937 int const newtoclevel =
3938 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3940 for (auto const & lay : tc) {
3941 if (lay.toclevel == newtoclevel
3942 && lay.isNumHeadingLabelType()
3943 && start->layout().isNumHeadingLabelType()) {
3944 start->setLayout(lay);
3958 void Text::number(Cursor & cur)
3960 FontInfo font = ignore_font;
3961 font.setNumber(FONT_TOGGLE);
3962 toggleAndShow(cur, this, Font(font, ignore_language));
3966 bool Text::isRTL(pit_type const pit) const
3968 Buffer const & buffer = owner_->buffer();
3969 return pars_[pit].isRTL(buffer.params());
3975 Language const * getLanguage(Cursor const & cur, string const & lang)
3977 return lang.empty() ? cur.getFont().language() : languages.getLanguage(lang);
3981 docstring resolveLayout(docstring layout, DocIterator const & dit)
3983 Paragraph const & par = dit.paragraph();
3984 DocumentClass const & tclass = dit.buffer()->params().documentClass();
3987 layout = tclass.defaultLayoutName();
3989 if (dit.inset().forcePlainLayout(dit.idx()))
3990 // in this case only the empty layout is allowed
3991 layout = tclass.plainLayoutName();
3992 else if (par.usePlainLayout()) {
3993 // in this case, default layout maps to empty layout
3994 if (layout == tclass.defaultLayoutName())
3995 layout = tclass.plainLayoutName();
3997 // otherwise, the empty layout maps to the default
3998 if (layout == tclass.plainLayoutName())
3999 layout = tclass.defaultLayoutName();
4002 // If the entry is obsolete, use the new one instead.
4003 if (tclass.hasLayout(layout)) {
4004 docstring const & obs = tclass[layout].obsoleted_by();
4008 if (!tclass.hasLayout(layout))
4014 bool isAlreadyLayout(docstring const & layout, CursorData const & cur)
4016 ParagraphList const & pars = cur.text()->paragraphs();
4018 pit_type pit = cur.selBegin().pit();
4019 pit_type const epit = cur.selEnd().pit() + 1;
4020 for ( ; pit != epit; ++pit)
4021 if (pars[pit].layout().name() != layout)
4031 void Text::dispatch(Cursor & cur, FuncRequest & cmd)
4033 LYXERR(Debug::ACTION, "Text::dispatch: cmd: " << cmd);
4035 // Dispatch if the cursor is inside the text. It is not the
4036 // case for context menus (bug 5797).
4037 if (cur.text() != this) {
4042 BufferView * bv = &cur.bv();
4043 TextMetrics * tm = &bv->textMetrics(this);
4044 if (!tm->contains(cur.pit())) {
4045 lyx::dispatch(FuncRequest(LFUN_SCREEN_SHOW_CURSOR));
4046 tm = &bv->textMetrics(this);
4049 // FIXME: We use the update flag to indicates wether a singlePar or a
4050 // full screen update is needed. We reset it here but shall we restore it
4052 cur.noScreenUpdate();
4054 LBUFERR(this == cur.text());
4056 // NOTE: This should NOT be a reference. See commit 94a5481a.
4057 CursorSlice const oldTopSlice = cur.top();
4058 bool const oldBoundary = cur.boundary();
4059 bool const oldSelection = cur.selection();
4060 // Signals that, even if needsUpdate == false, an update of the
4061 // cursor paragraph is required
4062 bool singleParUpdate = lyxaction.funcHasFlag(cmd.action(),
4063 LyXAction::SingleParUpdate);
4064 // Signals that a full-screen update is required
4065 bool needsUpdate = !(lyxaction.funcHasFlag(cmd.action(),
4066 LyXAction::NoUpdate) || singleParUpdate);
4067 bool const last_misspelled = lyxrc.spellcheck_continuously
4068 && cur.paragraph().isMisspelled(cur.pos(), true);
4070 FuncCode const act = cmd.action();
4073 case LFUN_PARAGRAPH_MOVE_DOWN: {
4074 pit_type const pit = cur.pit();
4075 cur.recordUndo(pit, pit + 1);
4076 pars_.swap(pit, pit + 1);
4078 cur.forceBufferUpdate();
4083 case LFUN_PARAGRAPH_MOVE_UP: {
4084 pit_type const pit = cur.pit();
4085 cur.recordUndo(pit - 1, pit);
4087 pars_.swap(pit, pit - 1);
4090 cur.forceBufferUpdate();
4094 case LFUN_APPENDIX: {
4095 Paragraph & par = cur.paragraph();
4096 bool start = !par.params().startOfAppendix();
4098 // FIXME: The code below only makes sense at top level.
4099 // Should LFUN_APPENDIX be restricted to top-level paragraphs?
4100 // ensure that we have only one start_of_appendix in this document
4101 // FIXME: this don't work for multipart document!
4102 for (pit_type tmp = 0, end = pars_.size(); tmp != end; ++tmp) {
4103 if (pars_[tmp].params().startOfAppendix()) {
4104 cur.recordUndo(tmp, tmp);
4105 pars_[tmp].params().startOfAppendix(false);
4111 par.params().startOfAppendix(start);
4113 // we can set the refreshing parameters now
4114 cur.forceBufferUpdate();
4118 case LFUN_WORD_DELETE_FORWARD:
4119 if (cur.selection())
4120 cutSelection(cur, false);
4122 deleteWordForward(cur, cmd.getArg(0) != "confirm");
4123 finishChange(cur, false);
4126 case LFUN_WORD_DELETE_BACKWARD:
4127 if (cur.selection())
4128 cutSelection(cur, false);
4130 deleteWordBackward(cur, cmd.getArg(0) != "confirm");
4131 finishChange(cur, false);
4134 case LFUN_LINE_DELETE_FORWARD:
4135 if (cur.selection())
4136 cutSelection(cur, false);
4138 tm->deleteLineForward(cur);
4139 finishChange(cur, false);
4142 case LFUN_BUFFER_BEGIN:
4143 case LFUN_BUFFER_BEGIN_SELECT:
4144 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_BEGIN_SELECT);
4145 if (cur.depth() == 1)
4146 needsUpdate |= cursorTop(cur);
4149 cur.screenUpdateFlags(Update::FitCursor);
4152 case LFUN_BUFFER_END:
4153 case LFUN_BUFFER_END_SELECT:
4154 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_END_SELECT);
4155 if (cur.depth() == 1)
4156 needsUpdate |= cursorBottom(cur);
4159 cur.screenUpdateFlags(Update::FitCursor);
4162 case LFUN_INSET_BEGIN:
4163 case LFUN_INSET_BEGIN_SELECT:
4164 needsUpdate |= cur.selHandle(act == LFUN_INSET_BEGIN_SELECT);
4165 if (cur.depth() == 1 || !cur.top().at_begin())
4166 needsUpdate |= cursorTop(cur);
4169 cur.screenUpdateFlags(Update::FitCursor);
4172 case LFUN_INSET_END:
4173 case LFUN_INSET_END_SELECT:
4174 needsUpdate |= cur.selHandle(act == LFUN_INSET_END_SELECT);
4175 if (cur.depth() == 1 || !cur.top().at_end())
4176 needsUpdate |= cursorBottom(cur);
4179 cur.screenUpdateFlags(Update::FitCursor);
4182 case LFUN_CHAR_FORWARD:
4183 case LFUN_CHAR_FORWARD_SELECT: {
4184 //LYXERR0(" LFUN_CHAR_FORWARD[SEL]:\n" << cur);
4185 needsUpdate |= cur.selHandle(act == LFUN_CHAR_FORWARD_SELECT);
4186 bool const cur_moved = cursorForward(cur);
4187 needsUpdate |= cur_moved;
4189 if (!cur_moved && cur.depth() > 1
4190 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4192 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4194 // we will be moving out the inset, so we should execute
4195 // the depm-mechanism.
4196 // The cursor hasn't changed yet. To give the DEPM the
4197 // possibility of doing something we must provide it with
4198 // two different cursors.
4200 dummy.pos() = dummy.pit() = 0;
4201 if (cur.bv().checkDepm(dummy, cur))
4202 cur.forceBufferUpdate();
4207 case LFUN_CHAR_BACKWARD:
4208 case LFUN_CHAR_BACKWARD_SELECT: {
4209 //lyxerr << "handle LFUN_CHAR_BACKWARD[_SELECT]:\n" << cur << endl;
4210 needsUpdate |= cur.selHandle(act == LFUN_CHAR_BACKWARD_SELECT);
4211 bool const cur_moved = cursorBackward(cur);
4212 needsUpdate |= cur_moved;
4214 if (!cur_moved && cur.depth() > 1
4215 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4217 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4219 // we will be moving out the inset, so we should execute
4220 // the depm-mechanism.
4221 // The cursor hasn't changed yet. To give the DEPM the
4222 // possibility of doing something we must provide it with
4223 // two different cursors.
4225 dummy.pos() = cur.lastpos();
4226 dummy.pit() = cur.lastpit();
4227 if (cur.bv().checkDepm(dummy, cur))
4228 cur.forceBufferUpdate();
4233 case LFUN_CHAR_LEFT:
4234 case LFUN_CHAR_LEFT_SELECT:
4235 if (lyxrc.visual_cursor) {
4236 needsUpdate |= cur.selHandle(act == LFUN_CHAR_LEFT_SELECT);
4237 bool const cur_moved = cursorVisLeft(cur);
4238 needsUpdate |= cur_moved;
4239 if (!cur_moved && cur.depth() > 1
4240 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4242 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4245 if (cur.reverseDirectionNeeded()) {
4246 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4247 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4249 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4250 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4257 case LFUN_CHAR_RIGHT:
4258 case LFUN_CHAR_RIGHT_SELECT:
4259 if (lyxrc.visual_cursor) {
4260 needsUpdate |= cur.selHandle(cmd.action() == LFUN_CHAR_RIGHT_SELECT);
4261 bool const cur_moved = cursorVisRight(cur);
4262 needsUpdate |= cur_moved;
4263 if (!cur_moved && cur.depth() > 1
4264 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4266 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4269 if (cur.reverseDirectionNeeded()) {
4270 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4271 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4273 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4274 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4282 case LFUN_UP_SELECT:
4283 case LFUN_DOWN_SELECT:
4286 // stop/start the selection
4287 bool select = cmd.action() == LFUN_DOWN_SELECT ||
4288 cmd.action() == LFUN_UP_SELECT;
4290 // move cursor up/down
4291 bool up = cmd.action() == LFUN_UP_SELECT || cmd.action() == LFUN_UP;
4292 bool const atFirstOrLastRow = cur.atFirstOrLastRow(up);
4294 if (!atFirstOrLastRow) {
4295 needsUpdate |= cur.selHandle(select);
4296 cur.upDownInText(up, needsUpdate);
4297 needsUpdate |= cur.beforeDispatchCursor().inMathed();
4299 pos_type newpos = up ? 0 : cur.lastpos();
4300 if (lyxrc.mac_like_cursor_movement && cur.pos() != newpos) {
4301 needsUpdate |= cur.selHandle(select);
4302 // we do not reset the targetx of the cursor
4304 needsUpdate |= bv->checkDepm(cur, bv->cursor());
4305 cur.updateTextTargetOffset();
4307 cur.forceBufferUpdate();
4311 // if the cursor cannot be moved up or down do not remove
4312 // the selection right now, but wait for the next dispatch.
4314 needsUpdate |= cur.selHandle(select);
4315 cur.upDownInText(up, needsUpdate);
4322 case LFUN_PARAGRAPH_SELECT:
4324 needsUpdate |= setCursor(cur, cur.pit(), 0);
4325 needsUpdate |= cur.selHandle(true);
4326 if (cur.pos() < cur.lastpos())
4327 needsUpdate |= setCursor(cur, cur.pit(), cur.lastpos());
4330 case LFUN_PARAGRAPH_UP:
4331 case LFUN_PARAGRAPH_UP_SELECT:
4332 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_UP_SELECT);
4333 needsUpdate |= cursorUpParagraph(cur);
4336 case LFUN_PARAGRAPH_DOWN:
4337 case LFUN_PARAGRAPH_DOWN_SELECT:
4338 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_DOWN_SELECT);
4339 needsUpdate |= cursorDownParagraph(cur);
4342 case LFUN_LINE_BEGIN:
4343 case LFUN_LINE_BEGIN_SELECT:
4344 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_BEGIN_SELECT);
4345 needsUpdate |= tm->cursorHome(cur);
4349 case LFUN_LINE_END_SELECT:
4350 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_END_SELECT);
4351 needsUpdate |= tm->cursorEnd(cur);
4354 case LFUN_SECTION_SELECT: {
4355 Buffer const & buf = *cur.buffer();
4356 pit_type const pit = cur.pit();
4357 ParagraphList & pars = buf.text().paragraphs();
4358 ParagraphList::iterator bgn = pars.begin();
4359 // The first paragraph of the area to be selected:
4360 ParagraphList::iterator start = pars.iterator_at(pit);
4361 // The final paragraph of area to be selected:
4362 ParagraphList::iterator finish = start;
4363 ParagraphList::iterator end = pars.end();
4365 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
4366 if (thistoclevel == Layout::NOT_IN_TOC)
4370 Cursor const old_cur = cur;
4371 needsUpdate |= cur.selHandle(true);
4373 // Move out (down) from this section header
4377 // Seek the one (on same level) below
4378 for (; finish != end; ++finish, ++cur.pit()) {
4379 int const toclevel = buf.text().getTocLevel(distance(bgn, finish));
4380 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
4383 cur.pos() = cur.lastpos();
4384 cur.boundary(false);
4385 cur.setCurrentFont();
4387 needsUpdate |= cur != old_cur;
4391 case LFUN_WORD_RIGHT:
4392 case LFUN_WORD_RIGHT_SELECT:
4393 if (lyxrc.visual_cursor) {
4394 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_RIGHT_SELECT);
4395 bool const cur_moved = cursorVisRightOneWord(cur);
4396 needsUpdate |= cur_moved;
4397 if (!cur_moved && cur.depth() > 1
4398 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4400 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4403 if (cur.reverseDirectionNeeded()) {
4404 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4405 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4407 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4408 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4415 case LFUN_WORD_FORWARD:
4416 case LFUN_WORD_FORWARD_SELECT: {
4417 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_FORWARD_SELECT);
4418 bool const cur_moved = cursorForwardOneWord(cur);
4419 needsUpdate |= cur_moved;
4421 if (!cur_moved && cur.depth() > 1
4422 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4424 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4426 // we will be moving out the inset, so we should execute
4427 // the depm-mechanism.
4428 // The cursor hasn't changed yet. To give the DEPM the
4429 // possibility of doing something we must provide it with
4430 // two different cursors.
4432 dummy.pos() = dummy.pit() = 0;
4433 if (cur.bv().checkDepm(dummy, cur))
4434 cur.forceBufferUpdate();
4439 case LFUN_WORD_LEFT:
4440 case LFUN_WORD_LEFT_SELECT:
4441 if (lyxrc.visual_cursor) {
4442 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_LEFT_SELECT);
4443 bool const cur_moved = cursorVisLeftOneWord(cur);
4444 needsUpdate |= cur_moved;
4445 if (!cur_moved && cur.depth() > 1
4446 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4448 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4451 if (cur.reverseDirectionNeeded()) {
4452 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4453 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4455 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4456 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4463 case LFUN_WORD_BACKWARD:
4464 case LFUN_WORD_BACKWARD_SELECT: {
4465 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_BACKWARD_SELECT);
4466 bool const cur_moved = cursorBackwardOneWord(cur);
4467 needsUpdate |= cur_moved;
4469 if (!cur_moved && cur.depth() > 1
4470 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4472 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4474 // we will be moving out the inset, so we should execute
4475 // the depm-mechanism.
4476 // The cursor hasn't changed yet. To give the DEPM the
4477 // possibility of doing something we must provide it with
4478 // two different cursors.
4480 dummy.pos() = cur.lastpos();
4481 dummy.pit() = cur.lastpit();
4482 if (cur.bv().checkDepm(dummy, cur))
4483 cur.forceBufferUpdate();
4488 case LFUN_WORD_SELECT: {
4489 selectWord(cur, WHOLE_WORD);
4490 finishChange(cur, true);
4494 case LFUN_NEWLINE_INSERT: {
4495 InsetNewlineParams inp;
4496 docstring const & arg = cmd.argument();
4497 if (arg == "linebreak")
4498 inp.kind = InsetNewlineParams::LINEBREAK;
4500 inp.kind = InsetNewlineParams::NEWLINE;
4501 cap::replaceSelection(cur);
4503 cur.insert(new InsetNewline(inp));
4505 moveCursor(cur, false);
4509 case LFUN_TAB_INSERT: {
4510 bool const multi_par_selection = cur.selection() &&
4511 cur.selBegin().pit() != cur.selEnd().pit();
4512 if (multi_par_selection) {
4513 // If there is a multi-paragraph selection, a tab is inserted
4514 // at the beginning of each paragraph.
4515 cur.recordUndoSelection();
4516 pit_type const pit_end = cur.selEnd().pit();
4517 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4518 pars_[pit].insertChar(0, '\t',
4519 bv->buffer().params().track_changes);
4520 // Update the selection pos to make sure the selection does not
4521 // change as the inserted tab will increase the logical pos.
4522 if (cur.realAnchor().pit() == pit)
4523 cur.realAnchor().forwardPos();
4524 if (cur.pit() == pit)
4529 // Maybe we shouldn't allow tabs within a line, because they
4530 // are not (yet) aligned as one might do expect.
4531 FuncRequest ncmd(LFUN_SELF_INSERT, from_ascii("\t"));
4532 dispatch(cur, ncmd);
4537 case LFUN_TAB_DELETE: {
4538 bool const tc = bv->buffer().params().track_changes;
4539 if (cur.selection()) {
4540 // If there is a selection, a tab (if present) is removed from
4541 // the beginning of each paragraph.
4542 cur.recordUndoSelection();
4543 pit_type const pit_end = cur.selEnd().pit();
4544 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4545 Paragraph & par = paragraphs()[pit];
4548 char_type const c = par.getChar(0);
4549 if (c == '\t' || c == ' ') {
4550 // remove either 1 tab or 4 spaces.
4551 int const n = (c == ' ' ? 4 : 1);
4552 for (int i = 0; i < n
4553 && !par.empty() && par.getChar(0) == c; ++i) {
4554 if (cur.pit() == pit)
4556 if (cur.realAnchor().pit() == pit
4557 && cur.realAnchor().pos() > 0 )
4558 cur.realAnchor().backwardPos();
4559 par.eraseChar(0, tc);
4565 // If there is no selection, try to remove a tab or some spaces
4566 // before the position of the cursor.
4567 Paragraph & par = paragraphs()[cur.pit()];
4568 pos_type const pos = cur.pos();
4573 char_type const c = par.getChar(pos - 1);
4577 par.eraseChar(cur.pos(), tc);
4579 for (int n_spaces = 0;
4581 && par.getChar(cur.pos() - 1) == ' '
4585 par.eraseChar(cur.pos(), tc);
4592 case LFUN_CHAR_DELETE_FORWARD:
4593 if (!cur.selection()) {
4594 if (cur.pos() == cur.paragraph().size())
4595 // Par boundary, force full-screen update
4596 singleParUpdate = false;
4597 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion()) {
4599 cur.selection(true);
4604 needsUpdate |= erase(cur);
4607 cutSelection(cur, false);
4608 cur.setCurrentFont();
4609 singleParUpdate = false;
4611 moveCursor(cur, false);
4614 case LFUN_CHAR_DELETE_BACKWARD:
4615 if (!cur.selection()) {
4616 if (bv->getIntl().getTransManager().backspace()) {
4617 bool par_boundary = cur.pos() == 0;
4618 bool first_par = cur.pit() == 0;
4619 // Par boundary, full-screen update
4621 singleParUpdate = false;
4622 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion(true)) {
4624 cur.selection(true);
4629 needsUpdate |= backspace(cur);
4631 if (par_boundary && !first_par && cur.pos() > 0
4632 && cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
4633 needsUpdate |= backspace(cur);
4638 DocIterator const dit = cur.selectionBegin();
4639 cutSelection(cur, false);
4640 if (cur.buffer()->params().track_changes)
4641 // since we're doing backwards deletion,
4642 // and the selection is not really cut,
4643 // move cursor before selection (#11630)
4645 cur.setCurrentFont();
4646 singleParUpdate = false;
4650 case LFUN_PARAGRAPH_BREAK: {
4651 cap::replaceSelection(cur);
4652 pit_type pit = cur.pit();
4653 Paragraph const & par = pars_[pit];
4654 bool lastpar = (pit == pit_type(pars_.size() - 1));
4655 Paragraph const & nextpar = lastpar ? par : pars_[pit + 1];
4656 pit_type prev = pit > 0 ? depthHook(pit, par.getDepth()) : pit;
4657 if (prev < pit && cur.pos() == par.beginOfBody()
4658 && par.empty() && !par.isEnvSeparator(cur.pos())
4659 && !par.layout().keepempty
4660 && !par.layout().isCommand()
4661 && pars_[prev].layout() != par.layout()
4662 && pars_[prev].layout().isEnvironment()
4663 && !nextpar.isEnvSeparator(nextpar.beginOfBody())) {
4664 if (par.layout().isEnvironment()
4665 && pars_[prev].getDepth() == par.getDepth()) {
4666 docstring const layout = par.layout().name();
4667 DocumentClass const & tc = bv->buffer().params().documentClass();
4668 lyx::dispatch(FuncRequest(LFUN_LAYOUT, tc.plainLayout().name()));
4669 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4670 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
4671 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
4673 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4674 breakParagraph(cur);
4676 Font const f(inherit_font, cur.current_font.language());
4677 pars_[cur.pit() - 1].resetFonts(f);
4679 if (par.isEnvSeparator(cur.pos()) && cmd.getArg(1) != "ignoresep")
4681 breakParagraph(cur, cmd.getArg(0) == "inverse");
4684 // If we have a list and autoinsert item insets,
4686 Layout::LaTeXArgMap args = par.layout().args();
4687 for (auto const & thearg : args) {
4688 Layout::latexarg arg = thearg.second;
4689 if (arg.autoinsert && prefixIs(thearg.first, "item:")) {
4690 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, thearg.first);
4691 lyx::dispatch(cmd2);
4697 case LFUN_INSET_INSERT: {
4700 // We have to avoid triggering InstantPreview loading
4701 // before inserting into the document. See bug #5626.
4702 bool loaded = bv->buffer().isFullyLoaded();
4703 bv->buffer().setFullyLoaded(false);
4704 Inset * inset = createInset(&bv->buffer(), cmd);
4705 bv->buffer().setFullyLoaded(loaded);
4708 // FIXME (Abdel 01/02/2006):
4709 // What follows would be a partial fix for bug 2154:
4710 // http://www.lyx.org/trac/ticket/2154
4711 // This automatically put the label inset _after_ a
4712 // numbered section. It should be possible to extend the mechanism
4713 // to any kind of LateX environement.
4714 // The correct way to fix that bug would be at LateX generation.
4715 // I'll let the code here for reference as it could be used for some
4716 // other feature like "automatic labelling".
4718 Paragraph & par = pars_[cur.pit()];
4719 if (inset->lyxCode() == LABEL_CODE
4720 && !par.layout().counter.empty()) {
4721 // Go to the end of the paragraph
4722 // Warning: Because of Change-Tracking, the last
4723 // position is 'size()' and not 'size()-1':
4724 cur.pos() = par.size();
4725 // Insert a new paragraph
4726 FuncRequest fr(LFUN_PARAGRAPH_BREAK);
4730 if (cur.selection())
4731 cutSelection(cur, false);
4733 cur.forceBufferUpdate();
4734 if (inset->editable() && inset->asInsetText())
4735 inset->edit(cur, true);
4739 // trigger InstantPreview now
4740 if (inset->lyxCode() == EXTERNAL_CODE) {
4741 InsetExternal & ins =
4742 static_cast<InsetExternal &>(*inset);
4743 ins.updatePreview();
4750 case LFUN_INSET_DISSOLVE: {
4751 if (dissolveInset(cur)) {
4753 cur.forceBufferUpdate();
4758 case LFUN_INSET_SPLIT: {
4759 if (splitInset(cur)) {
4761 cur.forceBufferUpdate();
4766 case LFUN_GRAPHICS_SET_GROUP: {
4767 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
4773 string id = to_utf8(cmd.argument());
4774 string grp = graphics::getGroupParams(bv->buffer(), id);
4775 InsetGraphicsParams tmp, inspar = ins->getParams();
4778 inspar.groupId = to_utf8(cmd.argument());
4780 InsetGraphics::string2params(grp, bv->buffer(), tmp);
4781 tmp.filename = inspar.filename;
4785 ins->setParams(inspar);
4789 case LFUN_SPACE_INSERT:
4790 if (cur.paragraph().layout().free_spacing)
4791 insertChar(cur, ' ');
4793 doInsertInset(cur, this, cmd, false, false);
4796 moveCursor(cur, false);
4799 case LFUN_SPECIALCHAR_INSERT: {
4800 string const name = to_utf8(cmd.argument());
4801 if (name == "hyphenation")
4802 specialChar(cur, InsetSpecialChar::HYPHENATION);
4803 else if (name == "allowbreak")
4804 specialChar(cur, InsetSpecialChar::ALLOWBREAK);
4805 else if (name == "ligature-break")
4806 specialChar(cur, InsetSpecialChar::LIGATURE_BREAK);
4807 else if (name == "slash")
4808 specialChar(cur, InsetSpecialChar::SLASH);
4809 else if (name == "nobreakdash")
4810 specialChar(cur, InsetSpecialChar::NOBREAKDASH);
4811 else if (name == "dots")
4812 specialChar(cur, InsetSpecialChar::LDOTS);
4813 else if (name == "end-of-sentence")
4814 specialChar(cur, InsetSpecialChar::END_OF_SENTENCE);
4815 else if (name == "menu-separator")
4816 specialChar(cur, InsetSpecialChar::MENU_SEPARATOR);
4817 else if (name == "lyx")
4818 specialChar(cur, InsetSpecialChar::PHRASE_LYX);
4819 else if (name == "tex")
4820 specialChar(cur, InsetSpecialChar::PHRASE_TEX);
4821 else if (name == "latex")
4822 specialChar(cur, InsetSpecialChar::PHRASE_LATEX);
4823 else if (name == "latex2e")
4824 specialChar(cur, InsetSpecialChar::PHRASE_LATEX2E);
4825 else if (name.empty())
4826 lyxerr << "LyX function 'specialchar-insert' needs an argument." << endl;
4828 lyxerr << "Wrong argument for LyX function 'specialchar-insert'." << endl;
4832 case LFUN_IPAMACRO_INSERT: {
4833 string const arg = cmd.getArg(0);
4834 if (arg == "deco") {
4835 // Open the inset, and move the current selection
4837 doInsertInset(cur, this, cmd, true, true);
4839 // Some insets are numbered, others are shown in the outline pane so
4840 // let's update the labels and the toc backend.
4841 cur.forceBufferUpdate();
4844 if (arg == "tone-falling")
4845 ipaChar(cur, InsetIPAChar::TONE_FALLING);
4846 else if (arg == "tone-rising")
4847 ipaChar(cur, InsetIPAChar::TONE_RISING);
4848 else if (arg == "tone-high-rising")
4849 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING);
4850 else if (arg == "tone-low-rising")
4851 ipaChar(cur, InsetIPAChar::TONE_LOW_RISING);
4852 else if (arg == "tone-high-rising-falling")
4853 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING_FALLING);
4854 else if (arg.empty())
4855 lyxerr << "LyX function 'ipamacro-insert' needs an argument." << endl;
4857 lyxerr << "Wrong argument for LyX function 'ipamacro-insert'." << endl;
4861 case LFUN_WORD_UPCASE:
4862 changeCase(cur, text_uppercase, cmd.getArg(0) == "partial");
4865 case LFUN_WORD_LOWCASE:
4866 changeCase(cur, text_lowercase, cmd.getArg(0) == "partial");
4869 case LFUN_WORD_CAPITALIZE:
4870 changeCase(cur, text_capitalization, cmd.getArg(0) == "partial");
4873 case LFUN_CHARS_TRANSPOSE:
4874 charsTranspose(cur);
4878 cur.message(_("Paste"));
4879 LASSERT(cur.selBegin().idx() == cur.selEnd().idx(), break);
4880 cap::replaceSelection(cur);
4882 // without argument?
4883 string const arg = to_utf8(cmd.argument());
4885 bool tryGraphics = true;
4886 if (theClipboard().isInternal())
4887 pasteFromStack(cur, bv->buffer().errorList("Paste"), 0);
4888 else if (theClipboard().hasTextContents()) {
4889 if (pasteClipboardText(cur, bv->buffer().errorList("Paste"),
4890 !cur.paragraph().parbreakIsNewline(),
4891 Clipboard::AnyTextType))
4892 tryGraphics = false;
4894 if (tryGraphics && theClipboard().hasGraphicsContents())
4895 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"));
4896 } else if (isStrUnsignedInt(arg)) {
4897 // we have a numerical argument
4898 pasteFromStack(cur, bv->buffer().errorList("Paste"),
4899 convert<unsigned int>(arg));
4900 } else if (arg == "html" || arg == "latex") {
4901 Clipboard::TextType type = (arg == "html") ?
4902 Clipboard::HtmlTextType : Clipboard::LaTeXTextType;
4903 pasteClipboardText(cur, bv->buffer().errorList("Paste"), true, type);
4905 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
4907 type = Clipboard::PdfGraphicsType;
4908 else if (arg == "png")
4909 type = Clipboard::PngGraphicsType;
4910 else if (arg == "jpeg")
4911 type = Clipboard::JpegGraphicsType;
4912 else if (arg == "linkback")
4913 type = Clipboard::LinkBackGraphicsType;
4914 else if (arg == "emf")
4915 type = Clipboard::EmfGraphicsType;
4916 else if (arg == "wmf")
4917 type = Clipboard::WmfGraphicsType;
4919 // we also check in getStatus()
4920 LYXERR0("Unrecognized graphics type: " << arg);
4922 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"), type);
4925 bv->buffer().errors("Paste");
4926 bv->buffer().updatePreviews(); // bug 11619
4927 cur.clearSelection(); // bug 393
4933 cutSelection(cur, true);
4934 cur.message(_("Cut"));
4937 case LFUN_SERVER_GET_XY:
4938 cur.message(from_utf8(
4939 convert<string>(tm->cursorX(cur.top(), cur.boundary()))
4940 + ' ' + convert<string>(tm->cursorY(cur.top(), cur.boundary()))));
4943 case LFUN_SERVER_SET_XY: {
4946 istringstream is(to_utf8(cmd.argument()));
4949 lyxerr << "SETXY: Could not parse coordinates in '"
4950 << to_utf8(cmd.argument()) << endl;
4952 tm->setCursorFromCoordinates(cur, x, y);
4956 case LFUN_SERVER_GET_LAYOUT:
4957 cur.message(cur.paragraph().layout().name());
4961 case LFUN_LAYOUT_TOGGLE: {
4962 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
4963 docstring req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
4964 LYXERR(Debug::INFO, "LFUN_LAYOUT: (arg) " << to_utf8(req_layout));
4966 docstring layout = resolveLayout(req_layout, cur);
4967 if (layout.empty()) {
4968 cur.errorMessage(from_utf8(N_("Layout ")) + req_layout +
4969 from_utf8(N_(" not known")));
4973 docstring const old_layout = cur.paragraph().layout().name();
4974 bool change_layout = !isAlreadyLayout(layout, cur);
4976 if (cmd.action() == LFUN_LAYOUT_TOGGLE && !change_layout) {
4977 change_layout = true;
4978 layout = resolveLayout(docstring(), cur);
4981 if (change_layout) {
4982 setLayout(cur, layout);
4983 if (cur.pit() > 0 && !ignoreautonests) {
4984 pit_type prev_pit = cur.pit() - 1;
4985 depth_type const cur_depth = pars_[cur.pit()].getDepth();
4986 // Scan for the previous par on same nesting level
4987 while (prev_pit > 0 && pars_[prev_pit].getDepth() > cur_depth)
4989 set<docstring> const & autonests =
4990 pars_[prev_pit].layout().autonests();
4991 set<docstring> const & autonested =
4992 pars_[cur.pit()].layout().isAutonestedBy();
4993 if (autonests.find(layout) != autonests.end()
4994 || autonested.find(old_layout) != autonested.end())
4995 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
4999 DocumentClass const & tclass = bv->buffer().params().documentClass();
5000 bool inautoarg = false;
5001 for (auto const & la_pair : tclass[layout].args()) {
5002 Layout::latexarg const & arg = la_pair.second;
5003 if (arg.autoinsert) {
5004 // If we had already inserted an arg automatically,
5005 // leave this now in order to insert the next one.
5007 cur.leaveInset(cur.inset());
5010 FuncRequest const cmd2(LFUN_ARGUMENT_INSERT, la_pair.first);
5011 lyx::dispatch(cmd2);
5019 case LFUN_ENVIRONMENT_SPLIT: {
5020 bool const outer = cmd.argument() == "outer";
5021 bool const previous = cmd.argument() == "previous";
5022 bool const before = cmd.argument() == "before";
5023 bool const normal = cmd.argument().empty();
5024 Paragraph const & para = cur.paragraph();
5026 if (para.layout().isEnvironment())
5027 layout = para.layout().name();
5028 depth_type split_depth = cur.paragraph().params().depth();
5029 vector<depth_type> nextpars_depth;
5030 if (outer || previous) {
5031 // check if we have an environment in our scope
5032 pit_type pit = cur.pit();
5033 Paragraph cpar = pars_[pit];
5039 if (layout.empty() && previous
5040 && cpar.layout().isEnvironment()
5041 && cpar.params().depth() <= split_depth)
5042 layout = cpar.layout().name();
5043 if (cpar.params().depth() < split_depth
5044 && cpar.layout().isEnvironment()) {
5046 layout = cpar.layout().name();
5047 split_depth = cpar.params().depth();
5049 if (cpar.params().depth() == 0)
5053 if ((outer || normal) && cur.pit() < cur.lastpit()) {
5054 // save nesting of following paragraphs if they are deeper
5056 pit_type offset = 1;
5057 depth_type cur_depth = pars_[cur.pit()].params().depth();
5058 while (cur.pit() + offset <= cur.lastpit()) {
5059 Paragraph cpar = pars_[cur.pit() + offset];
5060 depth_type nextpar_depth = cpar.params().depth();
5061 if (cur_depth <= nextpar_depth && nextpar_depth > 0) {
5062 nextpars_depth.push_back(nextpar_depth);
5063 cur_depth = nextpar_depth;
5070 cur.top().setPitPos(cur.pit(), 0);
5071 if (before || cur.pos() > 0)
5072 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
5073 else if (previous && cur.nextInset() && cur.nextInset()->lyxCode() == SEPARATOR_CODE)
5074 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5076 while (cur.paragraph().params().depth() > split_depth)
5077 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
5079 DocumentClass const & tc = bv->buffer().params().documentClass();
5080 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
5081 + from_ascii("\" ignoreautonests")));
5082 // FIXME: Bibitem mess!
5083 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
5084 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
5085 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
5088 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5089 while (cur.paragraph().params().depth() < split_depth)
5090 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5093 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
5094 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
5095 if ((outer || normal) && !nextpars_depth.empty()) {
5096 // restore nesting of following paragraphs
5097 DocIterator scur = cur;
5098 depth_type max_depth = cur.paragraph().params().depth() + 1;
5099 for (auto nextpar_depth : nextpars_depth) {
5101 while (cur.paragraph().params().depth() < min(nextpar_depth, max_depth)) {
5102 depth_type const olddepth = cur.paragraph().params().depth();
5103 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5104 if (olddepth == cur.paragraph().params().depth())
5105 // leave loop if no incrementation happens
5108 max_depth = cur.paragraph().params().depth() + 1;
5110 cur.setCursor(scur);
5116 case LFUN_CLIPBOARD_PASTE:
5117 cap::replaceSelection(cur);
5118 pasteClipboardText(cur, bv->buffer().errorList("Paste"),
5119 cmd.argument() == "paragraph");
5120 bv->buffer().errors("Paste");
5123 case LFUN_CLIPBOARD_PASTE_SIMPLE:
5124 cap::replaceSelection(cur);
5125 pasteSimpleText(cur, cmd.argument() == "paragraph");
5128 case LFUN_PRIMARY_SELECTION_PASTE:
5129 cap::replaceSelection(cur);
5130 pasteString(cur, theSelection().get(),
5131 cmd.argument() == "paragraph");
5134 case LFUN_SELECTION_PASTE:
5135 // Copy the selection buffer to the clipboard stack,
5136 // because we want it to appear in the "Edit->Paste
5138 cap::replaceSelection(cur);
5139 cap::copySelectionToStack();
5140 cap::pasteSelection(bv->cursor(), bv->buffer().errorList("Paste"));
5141 bv->buffer().errors("Paste");
5144 case LFUN_QUOTE_INSERT: {
5145 cap::replaceSelection(cur);
5148 Paragraph const & par = cur.paragraph();
5149 pos_type pos = cur.pos();
5150 // Ignore deleted text before cursor
5151 while (pos > 0 && par.isDeleted(pos - 1))
5154 bool const inner = (cmd.getArg(0) == "single" || cmd.getArg(0) == "inner");
5156 // Guess quote side.
5157 // A space triggers an opening quote. This is passed if the preceding
5158 // char/inset is a space or at paragraph start.
5160 if (pos > 0 && !par.isSpace(pos - 1)) {
5161 if (cur.prevInset() && cur.prevInset()->lyxCode() == QUOTE_CODE) {
5162 // If an opening double quotation mark precedes, and this
5163 // is a single quote, make it opening as well
5165 static_cast<InsetQuotes &>(*cur.prevInset());
5166 string const type = ins.getType();
5167 if (!suffixIs(type, "ld") || !inner)
5168 c = par.getChar(pos - 1);
5170 else if (!cur.prevInset()
5171 || (cur.prevInset() && cur.prevInset()->isChar()))
5172 // If a char precedes, pass that and let InsetQuote decide
5173 c = par.getChar(pos - 1);
5176 if (par.getInset(pos - 1)
5177 && !par.getInset(pos - 1)->isPartOfTextSequence()) {
5178 // skip "invisible" insets
5182 c = par.getChar(pos - 1);
5187 QuoteLevel const quote_level = inner
5188 ? QuoteLevel::Secondary : QuoteLevel::Primary;
5189 cur.insert(new InsetQuotes(cur.buffer(), c, quote_level, cmd.getArg(1), cmd.getArg(2)));
5190 cur.buffer()->updateBuffer();
5195 case LFUN_MOUSE_TRIPLE:
5196 if (cmd.button() == mouse_button::button1) {
5198 setCursor(cur, cur.pit(), 0);
5201 if (cur.pos() < cur.lastpos())
5202 setCursor(cur, cur.pit(), cur.lastpos());
5208 case LFUN_MOUSE_DOUBLE:
5209 if (cmd.button() == mouse_button::button1) {
5210 selectWord(cur, WHOLE_WORD);
5215 // Single-click on work area
5216 case LFUN_MOUSE_PRESS: {
5217 // We are not marking a selection with the keyboard in any case.
5218 Cursor & bvcur = cur.bv().cursor();
5219 bvcur.setMark(false);
5220 switch (cmd.button()) {
5221 case mouse_button::button1:
5222 if (!bvcur.selection())
5224 bvcur.resetAnchor();
5225 if (!bv->mouseSetCursor(cur, cmd.modifier() == ShiftModifier))
5226 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5227 // FIXME: move this to mouseSetCursor?
5228 if (bvcur.wordSelection() && bvcur.inTexted())
5229 expandWordSel(bvcur);
5232 case mouse_button::button2:
5233 if (lyxrc.mouse_middlebutton_paste) {
5234 // Middle mouse pasting.
5235 bv->mouseSetCursor(cur);
5237 FuncRequest(LFUN_COMMAND_ALTERNATIVES,
5238 "selection-paste ; primary-selection-paste paragraph"));
5240 cur.noScreenUpdate();
5243 case mouse_button::button3: {
5244 // Don't do anything if we right-click a
5245 // selection, a context menu will popup.
5246 if (bvcur.selection() && cur >= bvcur.selectionBegin()
5247 && cur <= bvcur.selectionEnd()) {
5248 cur.noScreenUpdate();
5251 if (!bv->mouseSetCursor(cur, false))
5252 cur.screenUpdateFlags(Update::FitCursor);
5258 } // switch (cmd.button())
5261 case LFUN_MOUSE_MOTION: {
5262 // Mouse motion with right or middle mouse do nothing for now.
5263 if (cmd.button() != mouse_button::button1) {
5264 cur.noScreenUpdate();
5267 // ignore motions deeper nested than the real anchor
5268 Cursor & bvcur = cur.bv().cursor();
5269 if (!bvcur.realAnchor().hasPart(cur)) {
5273 CursorSlice old = bvcur.top();
5275 int const wh = bv->workHeight();
5276 int const y = max(0, min(wh - 1, cmd.y()));
5278 tm->setCursorFromCoordinates(cur, cmd.x(), y);
5279 cur.setTargetX(cmd.x());
5280 // Don't allow selecting a separator inset
5281 if (cur.pos() && cur.paragraph().isEnvSeparator(cur.pos() - 1))
5284 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5285 else if (cmd.y() < 0)
5286 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5287 // This is to allow jumping over large insets
5288 if (cur.top() == old) {
5290 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5291 else if (cmd.y() < 0)
5292 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5294 // We continue with our existing selection or start a new one, so don't
5295 // reset the anchor.
5296 bvcur.setCursor(cur);
5297 if (bvcur.wordSelection() && bvcur.inTexted())
5298 expandWordSel(bvcur);
5299 bvcur.selection(true);
5300 bvcur.setCurrentFont();
5301 if (cur.top() == old) {
5302 // We didn't move one iota, so no need to update the screen.
5303 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5304 //cur.noScreenUpdate();
5310 case LFUN_MOUSE_RELEASE:
5311 switch (cmd.button()) {
5312 case mouse_button::button1:
5313 // Cursor was set at LFUN_MOUSE_PRESS or LFUN_MOUSE_MOTION time.
5314 // If there is a new selection, update persistent selection;
5315 // otherwise, single click does not clear persistent selection
5317 if (cur.selection()) {
5318 // Finish selection. If double click,
5319 // cur is moved to the end of word by
5320 // selectWord but bvcur is current
5322 cur.bv().cursor().setSelection();
5323 // We might have removed an empty but drawn selection
5324 // (probably a margin)
5325 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5327 cur.noScreenUpdate();
5328 // FIXME: We could try to handle drag and drop of selection here.
5331 case mouse_button::button2:
5332 // Middle mouse pasting is handled at mouse press time,
5333 // see LFUN_MOUSE_PRESS.
5334 cur.noScreenUpdate();
5337 case mouse_button::button3:
5338 // Cursor was set at LFUN_MOUSE_PRESS time.
5339 // FIXME: If there is a selection we could try to handle a special
5340 // drag & drop context menu.
5341 cur.noScreenUpdate();
5344 case mouse_button::none:
5345 case mouse_button::button4:
5346 case mouse_button::button5:
5348 } // switch (cmd.button())
5352 case LFUN_SELF_INSERT: {
5353 if (cmd.argument().empty())
5356 // Automatically delete the currently selected
5357 // text and replace it with what is being
5358 // typed in now. Depends on lyxrc settings
5359 // "auto_region_delete", which defaults to
5362 if (lyxrc.auto_region_delete && cur.selection()) {
5363 cutSelection(cur, false);
5364 cur.setCurrentFont();
5366 cur.clearSelection();
5368 for (char_type c : cmd.argument())
5369 bv->translateAndInsert(c, this, cur);
5372 moveCursor(cur, false);
5373 cur.markNewWordPosition();
5374 bv->bookmarkEditPosition();
5378 case LFUN_HREF_INSERT: {
5379 docstring content = cmd.argument();
5380 if (content.empty() && cur.selection())
5381 content = cur.selectionAsString(false);
5383 InsetCommandParams p(HYPERLINK_CODE);
5384 if (!content.empty()){
5385 // if it looks like a link, we'll put it as target,
5386 // otherwise as name (bug #8792).
5389 // regex_match(to_utf8(content), matches, link_re)
5390 // because smatch stores pointers to the substrings rather
5391 // than making copies of them. And those pointers become
5392 // invalid after regex_match returns, since it is then
5393 // being given a temporary object. (Thanks to Georg for
5394 // figuring that out.)
5395 regex const link_re("^([a-z]+):.*");
5397 string const c = to_utf8(lowercase(content));
5399 if (c.substr(0,7) == "mailto:") {
5400 p["target"] = content;
5401 p["type"] = from_ascii("mailto:");
5402 } else if (regex_match(c, matches, link_re)) {
5403 p["target"] = content;
5404 string protocol = matches.str(1);
5405 if (protocol == "file")
5406 p["type"] = from_ascii("file:");
5408 p["name"] = content;
5410 string const data = InsetCommand::params2string(p);
5412 // we need to have a target. if we already have one, then
5413 // that gets used at the default for the name, too, which
5414 // is probably what is wanted.
5415 if (p["target"].empty()) {
5416 bv->showDialog("href", data);
5418 FuncRequest fr(LFUN_INSET_INSERT, data);
5424 case LFUN_LABEL_INSERT: {
5425 InsetCommandParams p(LABEL_CODE);
5426 // Try to generate a valid label
5427 p["name"] = (cmd.argument().empty()) ?
5428 cur.getPossibleLabel() :
5430 string const data = InsetCommand::params2string(p);
5432 if (cmd.argument().empty()) {
5433 bv->showDialog("label", data);
5435 FuncRequest fr(LFUN_INSET_INSERT, data);
5441 case LFUN_INFO_INSERT: {
5442 if (cmd.argument().empty()) {
5443 bv->showDialog("info", cur.current_font.language()->lang());
5446 inset = createInset(cur.buffer(), cmd);
5450 insertInset(cur, inset);
5451 cur.forceBufferUpdate();
5456 case LFUN_CAPTION_INSERT:
5457 case LFUN_FOOTNOTE_INSERT:
5458 case LFUN_NOTE_INSERT:
5459 case LFUN_BOX_INSERT:
5460 case LFUN_BRANCH_INSERT:
5461 case LFUN_PHANTOM_INSERT:
5462 case LFUN_ERT_INSERT:
5463 case LFUN_INDEXMACRO_INSERT:
5464 case LFUN_LISTING_INSERT:
5465 case LFUN_MARGINALNOTE_INSERT:
5466 case LFUN_ARGUMENT_INSERT:
5467 case LFUN_INDEX_INSERT:
5468 case LFUN_PREVIEW_INSERT:
5469 case LFUN_SCRIPT_INSERT:
5470 case LFUN_IPA_INSERT: {
5471 // Indexes reset font formatting (#11961)
5472 bool const resetfont = cmd.action() == LFUN_INDEX_INSERT;
5473 // Open the inset, and move the current selection
5475 doInsertInset(cur, this, cmd, true, true, resetfont);
5477 cur.setCurrentFont();
5478 // Some insets are numbered, others are shown in the outline pane so
5479 // let's update the labels and the toc backend.
5480 cur.forceBufferUpdate();
5484 case LFUN_FLEX_INSERT: {
5485 // Open the inset, and move the current selection
5487 bool const sel = cur.selection();
5488 doInsertInset(cur, this, cmd, true, true);
5489 // Insert auto-insert arguments
5490 bool autoargs = false, inautoarg = false;
5491 Layout::LaTeXArgMap args = cur.inset().getLayout().args();
5492 for (auto const & argt : args) {
5493 Layout::latexarg arg = argt.second;
5494 if (!inautoarg && arg.insertonnewline && cur.pos() > 0) {
5495 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5496 lyx::dispatch(cmd2);
5498 if (arg.autoinsert) {
5499 // The cursor might have been invalidated by the replaceSelection.
5500 cur.buffer()->changed(true);
5501 // If we had already inserted an arg automatically,
5502 // leave this now in order to insert the next one.
5504 cur.leaveInset(cur.inset());
5505 cur.setCurrentFont();
5507 if (arg.insertonnewline && cur.pos() > 0) {
5508 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5509 lyx::dispatch(cmd2);
5512 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, argt.first);
5513 lyx::dispatch(cmd2);
5520 cur.leaveInset(cur.inset());
5523 // Some insets are numbered, others are shown in the outline pane so
5524 // let's update the labels and the toc backend.
5525 cur.forceBufferUpdate();
5529 case LFUN_TABULAR_INSERT: {
5530 // if there were no arguments, just open the dialog
5531 if (cmd.argument().empty()) {
5532 bv->showDialog("tabularcreate");
5534 } else if (cur.buffer()->masterParams().tablestyle != "default"
5535 || bv->buffer().params().documentClass().tablestyle() != "default") {
5536 string tabstyle = cur.buffer()->masterParams().tablestyle;
5537 if (tabstyle == "default")
5538 tabstyle = bv->buffer().params().documentClass().tablestyle();
5539 if (!libFileSearch("tabletemplates", tabstyle + ".lyx").empty()) {
5540 FuncRequest fr(LFUN_TABULAR_STYLE_INSERT,
5541 tabstyle + " " + to_ascii(cmd.argument()));
5545 // Unknown style. Report and fall back to default.
5546 cur.errorMessage(from_utf8(N_("Table Style ")) + from_utf8(tabstyle) +
5547 from_utf8(N_(" not known")));
5549 if (doInsertInset(cur, this, cmd, false, true))
5554 case LFUN_TABULAR_STYLE_INSERT: {
5555 string const style = cmd.getArg(0);
5556 string const rows = cmd.getArg(1);
5557 string const cols = cmd.getArg(2);
5558 if (cols.empty() || !isStrInt(cols)
5559 || rows.empty() || !isStrInt(rows))
5561 int const r = convert<int>(rows);
5562 int const c = convert<int>(cols);
5569 FileName const tabstyle = libFileSearch("tabletemplates",
5570 style + suffix + ".lyx", "lyx");
5571 if (tabstyle.empty())
5573 UndoGroupHelper ugh(cur.buffer());
5575 FuncRequest cmd2(LFUN_FILE_INSERT, tabstyle.absFileName() + " ignorelang");
5576 lyx::dispatch(cmd2);
5580 // move one cell up to middle cell
5582 // add the missing rows
5583 int const addrows = r - 3;
5584 for (int i = 0 ; i < addrows ; ++i) {
5585 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-row");
5589 // add the missing columns
5590 int const addcols = c - 1;
5591 for (int i = 0 ; i < addcols ; ++i) {
5592 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-column");
5601 case LFUN_FLOAT_INSERT:
5602 case LFUN_FLOAT_WIDE_INSERT:
5603 case LFUN_WRAP_INSERT: {
5604 // will some content be moved into the inset?
5605 bool const content = cur.selection();
5606 // does the content consist of multiple paragraphs?
5607 bool const singlepar = (cur.selBegin().pit() == cur.selEnd().pit());
5609 doInsertInset(cur, this, cmd, true, true);
5612 // If some single-par content is moved into the inset,
5613 // doInsertInset puts the cursor outside the inset.
5614 // To insert the caption we put it back into the inset.
5615 // FIXME cleanup doInsertInset to avoid such dances!
5616 if (content && singlepar)
5619 ParagraphList & pars = cur.text()->paragraphs();
5621 DocumentClass const & tclass = bv->buffer().params().documentClass();
5623 // add a separate paragraph for the caption inset
5624 pars.push_back(Paragraph());
5625 pars.back().setInsetOwner(&cur.text()->inset());
5626 pars.back().setPlainOrDefaultLayout(tclass);
5627 int cap_pit = pars.size() - 1;
5629 // if an empty inset was created, we create an additional empty
5630 // paragraph at the bottom so that the user can choose where to put
5631 // the graphics (or table).
5633 pars.push_back(Paragraph());
5634 pars.back().setInsetOwner(&cur.text()->inset());
5635 pars.back().setPlainOrDefaultLayout(tclass);
5638 // reposition the cursor to the caption
5639 cur.pit() = cap_pit;
5641 // FIXME: This Text/Cursor dispatch handling is a mess!
5642 // We cannot use Cursor::dispatch here it needs access to up to
5644 FuncRequest cmd_caption(LFUN_CAPTION_INSERT);
5645 doInsertInset(cur, cur.text(), cmd_caption, true, false);
5646 cur.forceBufferUpdate();
5647 cur.screenUpdateFlags(Update::Force);
5648 // FIXME: When leaving the Float (or Wrap) inset we should
5649 // delete any empty paragraph left above or below the
5654 case LFUN_NOMENCL_INSERT: {
5655 InsetCommandParams p(NOMENCL_CODE);
5656 if (cmd.argument().empty()) {
5658 bv->cursor().innerText()->getStringForDialog(bv->cursor());
5659 cur.clearSelection();
5661 p["symbol"] = cmd.argument();
5662 string const data = InsetCommand::params2string(p);
5663 bv->showDialog("nomenclature", data);
5667 case LFUN_INDEX_PRINT: {
5668 InsetCommandParams p(INDEX_PRINT_CODE);
5669 if (cmd.argument().empty())
5670 p["type"] = from_ascii("idx");
5672 p["type"] = cmd.argument();
5673 string const data = InsetCommand::params2string(p);
5674 FuncRequest fr(LFUN_INSET_INSERT, data);
5679 case LFUN_NOMENCL_PRINT:
5680 case LFUN_NEWPAGE_INSERT:
5682 doInsertInset(cur, this, cmd, false, false);
5686 case LFUN_SEPARATOR_INSERT: {
5687 doInsertInset(cur, this, cmd, false, false);
5689 // remove a following space
5690 Paragraph & par = cur.paragraph();
5691 if (cur.pos() != cur.lastpos() && par.isLineSeparator(cur.pos()))
5692 par.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
5696 case LFUN_DEPTH_DECREMENT:
5697 changeDepth(cur, DEC_DEPTH);
5700 case LFUN_DEPTH_INCREMENT:
5701 changeDepth(cur, INC_DEPTH);
5704 case LFUN_REGEXP_MODE:
5705 regexpDispatch(cur, cmd);
5708 case LFUN_MATH_MODE: {
5709 if (cmd.argument() == "on" || cmd.argument() == "") {
5710 // don't pass "on" as argument
5711 // (it would appear literally in the first cell)
5712 docstring sel = cur.selectionAsString(false);
5713 InsetMathMacroTemplate * macro = new InsetMathMacroTemplate(cur.buffer());
5714 // create a macro template if we see "\\newcommand" somewhere, and
5715 // an ordinary formula otherwise
5717 && (sel.find(from_ascii("\\newcommand")) != string::npos
5718 || sel.find(from_ascii("\\newlyxcommand")) != string::npos
5719 || sel.find(from_ascii("\\def")) != string::npos)
5720 && macro->fromString(sel)) {
5722 replaceSelection(cur);
5725 // no meaningful macro template was found
5727 mathDispatch(cur,FuncRequest(LFUN_MATH_MODE));
5730 // The argument is meaningful
5731 // We replace cmd with LFUN_MATH_INSERT because LFUN_MATH_MODE
5732 // has a different meaning in math mode
5733 mathDispatch(cur, FuncRequest(LFUN_MATH_INSERT,cmd.argument()));
5737 case LFUN_MATH_MACRO:
5738 if (cmd.argument().empty())
5739 cur.errorMessage(from_utf8(N_("Missing argument")));
5742 string s = to_utf8(cmd.argument());
5743 string const s1 = token(s, ' ', 1);
5744 int const nargs = s1.empty() ? 0 : convert<int>(s1);
5745 string const s2 = token(s, ' ', 2);
5746 MacroType type = MacroTypeNewcommand;
5748 type = MacroTypeDef;
5749 InsetMathMacroTemplate * inset = new InsetMathMacroTemplate(cur.buffer(),
5750 from_utf8(token(s, ' ', 0)), nargs, false, type);
5751 inset->setBuffer(bv->buffer());
5752 insertInset(cur, inset);
5754 // enter macro inset and select the name
5756 cur.top().pos() = cur.top().lastpos();
5758 cur.selection(true);
5759 cur.top().pos() = 0;
5763 case LFUN_MATH_DISPLAY:
5764 case LFUN_MATH_SUBSCRIPT:
5765 case LFUN_MATH_SUPERSCRIPT:
5766 case LFUN_MATH_INSERT:
5767 case LFUN_MATH_AMS_MATRIX:
5768 case LFUN_MATH_MATRIX:
5769 case LFUN_MATH_DELIM:
5770 case LFUN_MATH_BIGDELIM:
5771 mathDispatch(cur, cmd);
5774 case LFUN_FONT_EMPH: {
5775 Font font(ignore_font, ignore_language);
5776 font.fontInfo().setEmph(FONT_TOGGLE);
5777 toggleAndShow(cur, this, font);
5781 case LFUN_FONT_ITAL: {
5782 Font font(ignore_font, ignore_language);
5783 font.fontInfo().setShape(ITALIC_SHAPE);
5784 toggleAndShow(cur, this, font);
5788 case LFUN_FONT_BOLD:
5789 case LFUN_FONT_BOLDSYMBOL: {
5790 Font font(ignore_font, ignore_language);
5791 font.fontInfo().setSeries(BOLD_SERIES);
5792 toggleAndShow(cur, this, font);
5796 case LFUN_FONT_NOUN: {
5797 Font font(ignore_font, ignore_language);
5798 font.fontInfo().setNoun(FONT_TOGGLE);
5799 toggleAndShow(cur, this, font);
5803 case LFUN_FONT_TYPEWRITER: {
5804 Font font(ignore_font, ignore_language);
5805 font.fontInfo().setFamily(TYPEWRITER_FAMILY); // no good
5806 toggleAndShow(cur, this, font);
5810 case LFUN_FONT_SANS: {
5811 Font font(ignore_font, ignore_language);
5812 font.fontInfo().setFamily(SANS_FAMILY);
5813 toggleAndShow(cur, this, font);
5817 case LFUN_FONT_ROMAN: {
5818 Font font(ignore_font, ignore_language);
5819 font.fontInfo().setFamily(ROMAN_FAMILY);
5820 toggleAndShow(cur, this, font);
5824 case LFUN_FONT_DEFAULT: {
5825 Font font(inherit_font, ignore_language);
5826 toggleAndShow(cur, this, font);
5830 case LFUN_FONT_STRIKEOUT: {
5831 Font font(ignore_font, ignore_language);
5832 font.fontInfo().setStrikeout(FONT_TOGGLE);
5833 toggleAndShow(cur, this, font);
5837 case LFUN_FONT_CROSSOUT: {
5838 Font font(ignore_font, ignore_language);
5839 font.fontInfo().setXout(FONT_TOGGLE);
5840 toggleAndShow(cur, this, font);
5844 case LFUN_FONT_UNDERUNDERLINE: {
5845 Font font(ignore_font, ignore_language);
5846 font.fontInfo().setUuline(FONT_TOGGLE);
5847 toggleAndShow(cur, this, font);
5851 case LFUN_FONT_UNDERWAVE: {
5852 Font font(ignore_font, ignore_language);
5853 font.fontInfo().setUwave(FONT_TOGGLE);
5854 toggleAndShow(cur, this, font);
5858 case LFUN_FONT_UNDERLINE: {
5859 Font font(ignore_font, ignore_language);
5860 font.fontInfo().setUnderbar(FONT_TOGGLE);
5861 toggleAndShow(cur, this, font);
5865 case LFUN_FONT_NO_SPELLCHECK: {
5866 Font font(ignore_font, ignore_language);
5867 font.fontInfo().setNoSpellcheck(FONT_TOGGLE);
5868 toggleAndShow(cur, this, font);
5872 case LFUN_FONT_SIZE: {
5873 Font font(ignore_font, ignore_language);
5874 setLyXSize(to_utf8(cmd.argument()), font.fontInfo());
5875 toggleAndShow(cur, this, font);
5879 case LFUN_LANGUAGE: {
5880 string const lang_arg = cmd.getArg(0);
5881 bool const reset = (lang_arg.empty() || lang_arg == "reset");
5882 Language const * lang =
5883 reset ? cur.bv().buffer().params().language
5884 : languages.getLanguage(lang_arg);
5885 // we allow reset_language, which is 0, but only if it
5886 // was requested via empty or "reset" arg.
5887 if (!lang && !reset)
5889 bool const toggle = (cmd.getArg(1) != "set");
5890 selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
5891 Font font(ignore_font, lang);
5892 toggleAndShow(cur, this, font, toggle);
5896 case LFUN_TEXTSTYLE_APPLY: {
5897 unsigned int num = 0;
5898 string const arg = to_utf8(cmd.argument());
5901 if (isStrUnsignedInt(arg)) {
5902 num = convert<uint>(arg);
5903 if (num >= freeFonts.size()) {
5904 cur.message(_("Invalid argument (number exceeds stack size)!"));
5908 cur.message(_("Invalid argument (must be a non-negative number)!"));
5912 toggleAndShow(cur, this, freeFonts[num].second, toggleall);
5913 cur.message(bformat(_("Text properties applied: %1$s"), freeFonts[num].first));
5917 // Set the freefont using the contents of \param data dispatched from
5918 // the frontends and apply it at the current cursor location.
5919 case LFUN_TEXTSTYLE_UPDATE: {
5920 Font font(ignore_font, ignore_language);
5922 if (font.fromString(to_utf8(cmd.argument()), toggle)) {
5923 docstring const props = font.stateText(&bv->buffer().params(), true);
5924 freeFonts.push(make_pair(props, font));
5926 toggleAndShow(cur, this, font, toggleall);
5927 cur.message(bformat(_("Text properties applied: %1$s"), props));
5929 LYXERR0("Invalid argument of textstyle-update");
5933 case LFUN_FINISHED_LEFT:
5934 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_LEFT:\n" << cur);
5935 // We're leaving an inset, going left. If the inset is LTR, we're
5936 // leaving from the front, so we should not move (remain at --- but
5937 // not in --- the inset). If the inset is RTL, move left, without
5938 // entering the inset itself; i.e., move to after the inset.
5939 if (cur.paragraph().getFontSettings(
5940 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5941 cursorVisLeft(cur, true);
5944 case LFUN_FINISHED_RIGHT:
5945 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_RIGHT:\n" << cur);
5946 // We're leaving an inset, going right. If the inset is RTL, we're
5947 // leaving from the front, so we should not move (remain at --- but
5948 // not in --- the inset). If the inset is LTR, move right, without
5949 // entering the inset itself; i.e., move to after the inset.
5950 if (!cur.paragraph().getFontSettings(
5951 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5952 cursorVisRight(cur, true);
5955 case LFUN_FINISHED_BACKWARD:
5956 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_BACKWARD:\n" << cur);
5957 cur.setCurrentFont();
5960 case LFUN_FINISHED_FORWARD:
5961 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_FORWARD:\n" << cur);
5963 cur.setCurrentFont();
5966 case LFUN_LAYOUT_PARAGRAPH: {
5968 params2string(cur.paragraph(), data);
5969 data = "show\n" + data;
5970 bv->showDialog("paragraph", data);
5974 case LFUN_PARAGRAPH_UPDATE: {
5976 params2string(cur.paragraph(), data);
5978 // Will the paragraph accept changes from the dialog?
5980 cur.inset().allowParagraphCustomization(cur.idx());
5982 data = "update " + convert<string>(accept) + '\n' + data;
5983 bv->updateDialog("paragraph", data);
5987 case LFUN_ACCENT_UMLAUT:
5988 case LFUN_ACCENT_CIRCUMFLEX:
5989 case LFUN_ACCENT_GRAVE:
5990 case LFUN_ACCENT_ACUTE:
5991 case LFUN_ACCENT_TILDE:
5992 case LFUN_ACCENT_PERISPOMENI:
5993 case LFUN_ACCENT_CEDILLA:
5994 case LFUN_ACCENT_MACRON:
5995 case LFUN_ACCENT_DOT:
5996 case LFUN_ACCENT_UNDERDOT:
5997 case LFUN_ACCENT_UNDERBAR:
5998 case LFUN_ACCENT_CARON:
5999 case LFUN_ACCENT_BREVE:
6000 case LFUN_ACCENT_TIE:
6001 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
6002 case LFUN_ACCENT_CIRCLE:
6003 case LFUN_ACCENT_OGONEK:
6004 theApp()->handleKeyFunc(cmd.action());
6005 if (!cmd.argument().empty())
6006 // FIXME: Are all these characters encoded in one byte in utf8?
6007 bv->translateAndInsert(cmd.argument()[0], this, cur);
6008 cur.screenUpdateFlags(Update::FitCursor);
6011 case LFUN_FLOAT_LIST_INSERT: {
6012 DocumentClass const & tclass = bv->buffer().params().documentClass();
6013 if (tclass.floats().typeExist(to_utf8(cmd.argument()))) {
6015 if (cur.selection())
6016 cutSelection(cur, false);
6017 breakParagraph(cur);
6019 if (cur.lastpos() != 0) {
6020 cursorBackward(cur);
6021 breakParagraph(cur);
6024 docstring const laystr = cur.inset().usePlainLayout() ?
6025 tclass.plainLayoutName() :
6026 tclass.defaultLayoutName();
6027 setLayout(cur, laystr);
6028 ParagraphParameters p;
6029 // FIXME If this call were replaced with one to clearParagraphParams(),
6030 // then we could get rid of this method altogether.
6031 setParagraphs(cur, p);
6032 // FIXME This should be simplified when InsetFloatList takes a
6033 // Buffer in its constructor.
6034 InsetFloatList * ifl = new InsetFloatList(cur.buffer(), to_utf8(cmd.argument()));
6035 ifl->setBuffer(bv->buffer());
6036 insertInset(cur, ifl);
6039 lyxerr << "Non-existent float type: "
6040 << to_utf8(cmd.argument()) << endl;
6045 case LFUN_CHANGE_ACCEPT: {
6046 acceptOrRejectChanges(cur, ACCEPT);
6050 case LFUN_CHANGE_REJECT: {
6051 acceptOrRejectChanges(cur, REJECT);
6055 case LFUN_THESAURUS_ENTRY: {
6056 Language const * language = cur.getFont().language();
6057 docstring arg = cmd.argument();
6059 arg = cur.selectionAsString(false);
6060 // Too large. We unselect if needed and try to get
6061 // the first word in selection or under cursor
6062 if (arg.size() > 100 || arg.empty()) {
6063 if (cur.selection()) {
6064 DocIterator selbeg = cur.selectionBegin();
6065 cur.clearSelection();
6066 setCursorIntern(cur, selbeg.pit(), selbeg.pos());
6067 cur.screenUpdateFlags(Update::Force);
6069 // Get word or selection
6070 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6071 arg = cur.selectionAsString(false);
6072 arg += " lang=" + from_ascii(language->lang());
6075 string lang = cmd.getArg(1);
6076 // This duplicates the code in GuiThesaurus::initialiseParams
6077 if (prefixIs(lang, "lang=")) {
6078 language = languages.getLanguage(lang.substr(5));
6080 language = cur.getFont().language();
6083 string lang = language->code();
6084 if (lyxrc.thesaurusdir_path.empty() && !thesaurus.thesaurusInstalled(from_ascii(lang))) {
6085 LYXERR(Debug::ACTION, "Command " << cmd << ". Thesaurus not found for language " << lang);
6086 frontend::Alert::warning(_("Path to thesaurus directory not set!"),
6087 _("The path to the thesaurus directory has not been specified.\n"
6088 "The thesaurus is not functional.\n"
6089 "Please refer to sec. 6.15.1 of the User's Guide for setup\n"
6092 bv->showDialog("thesaurus", to_utf8(arg));
6096 case LFUN_SPELLING_ADD: {
6097 Language const * language = getLanguage(cur, cmd.getArg(1));
6098 docstring word = from_utf8(cmd.getArg(0));
6100 word = cur.selectionAsString(false);
6102 if (word.size() > 100 || word.empty()) {
6103 // Get word or selection
6104 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6105 word = cur.selectionAsString(false);
6108 WordLangTuple wl(word, language);
6109 theSpellChecker()->insert(wl);
6113 case LFUN_SPELLING_ADD_LOCAL: {
6114 Language const * language = getLanguage(cur, cmd.getArg(1));
6115 docstring word = from_utf8(cmd.getArg(0));
6117 word = cur.selectionAsString(false);
6118 if (word.size() > 100)
6121 // Get word or selection
6122 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6123 word = cur.selectionAsString(false);
6126 WordLangTuple wl(word, language);
6127 if (!bv->buffer().params().spellignored(wl)) {
6128 cur.recordUndoBufferParams();
6129 bv->buffer().params().spellignore().push_back(wl);
6131 // trigger re-check of whole buffer
6132 bv->buffer().requestSpellcheck();
6137 case LFUN_SPELLING_REMOVE_LOCAL: {
6138 Language const * language = getLanguage(cur, cmd.getArg(1));
6139 docstring word = from_utf8(cmd.getArg(0));
6141 word = cur.selectionAsString(false);
6142 if (word.size() > 100)
6145 // Get word or selection
6146 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6147 word = cur.selectionAsString(false);
6150 WordLangTuple wl(word, language);
6151 bool has_item = false;
6152 vector<WordLangTuple>::const_iterator it = bv->buffer().params().spellignore().begin();
6153 for (; it != bv->buffer().params().spellignore().end(); ++it) {
6154 if (it->lang()->code() != wl.lang()->code())
6156 if (it->word() == wl.word()) {
6162 cur.recordUndoBufferParams();
6163 bv->buffer().params().spellignore().erase(it);
6165 // trigger re-check of whole buffer
6166 bv->buffer().requestSpellcheck();
6172 case LFUN_SPELLING_IGNORE: {
6173 Language const * language = getLanguage(cur, cmd.getArg(1));
6174 docstring word = from_utf8(cmd.getArg(0));
6176 word = cur.selectionAsString(false);
6178 if (word.size() > 100 || word.empty()) {
6179 // Get word or selection
6180 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6181 word = cur.selectionAsString(false);
6184 WordLangTuple wl(word, language);
6185 theSpellChecker()->accept(wl);
6189 case LFUN_SPELLING_REMOVE: {
6190 Language const * language = getLanguage(cur, cmd.getArg(1));
6191 docstring word = from_utf8(cmd.getArg(0));
6193 word = cur.selectionAsString(false);
6195 if (word.size() > 100 || word.empty()) {
6196 // Get word or selection
6197 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6198 word = cur.selectionAsString(false);
6201 WordLangTuple wl(word, language);
6202 theSpellChecker()->remove(wl);
6206 case LFUN_PARAGRAPH_PARAMS_APPLY: {
6207 // Given data, an encoding of the ParagraphParameters
6208 // generated in the Paragraph dialog, this function sets
6209 // the current paragraph, or currently selected paragraphs,
6211 // NOTE: This function overrides all existing settings.
6212 setParagraphs(cur, cmd.argument());
6213 cur.message(_("Paragraph layout set"));
6217 case LFUN_PARAGRAPH_PARAMS: {
6218 // Given data, an encoding of the ParagraphParameters as we'd
6219 // find them in a LyX file, this function modifies the current paragraph,
6220 // or currently selected paragraphs.
6221 // NOTE: This function only modifies, and does not override, existing
6223 setParagraphs(cur, cmd.argument(), true);
6224 cur.message(_("Paragraph layout set"));
6229 if (cur.selection()) {
6230 cur.selection(false);
6233 // This used to be LFUN_FINISHED_RIGHT, I think FORWARD is more
6234 // correct, but I'm not 100% sure -- dov, 071019
6235 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
6239 case LFUN_OUTLINE_UP: {
6240 pos_type const opos = cur.pos();
6241 outline(OutlineUp, cur, this);
6242 setCursor(cur, cur.pit(), opos);
6243 cur.forceBufferUpdate();
6248 case LFUN_OUTLINE_DOWN: {
6249 pos_type const opos = cur.pos();
6250 outline(OutlineDown, cur, this);
6251 setCursor(cur, cur.pit(), opos);
6252 cur.forceBufferUpdate();
6257 case LFUN_OUTLINE_IN:
6258 outline(OutlineIn, cur, this);
6259 cur.forceBufferUpdate();
6263 case LFUN_OUTLINE_OUT:
6264 outline(OutlineOut, cur, this);
6265 cur.forceBufferUpdate();
6269 case LFUN_SERVER_GET_STATISTICS: {
6270 DocIterator from, to;
6271 if (cur.selection()) {
6272 from = cur.selectionBegin();
6273 to = cur.selectionEnd();
6275 from = doc_iterator_begin(cur.buffer());
6276 to = doc_iterator_end(cur.buffer());
6279 cur.buffer()->updateStatistics(from, to);
6280 string const arg0 = cmd.getArg(0);
6281 if (arg0 == "words") {
6282 cur.message(convert<docstring>(cur.buffer()->wordCount()));
6283 } else if (arg0 == "chars") {
6284 cur.message(convert<docstring>(cur.buffer()->charCount(false)));
6285 } else if (arg0 == "chars-space") {
6286 cur.message(convert<docstring>(cur.buffer()->charCount(true)));
6288 cur.message(convert<docstring>(cur.buffer()->wordCount()) + " "
6289 + convert<docstring>(cur.buffer()->charCount(false)) + " "
6290 + convert<docstring>(cur.buffer()->charCount(true)));
6296 LYXERR(Debug::ACTION, "Command " << cmd << " not DISPATCHED by Text");
6301 needsUpdate |= (cur.pos() != cur.lastpos()) && cur.selection();
6303 if (lyxrc.spellcheck_continuously && !needsUpdate) {
6304 // Check for misspelled text
6305 // The redraw is useful because of the painting of
6306 // misspelled markers depends on the cursor position.
6307 // Trigger a redraw for cursor moves inside misspelled text.
6308 if (!cur.inTexted()) {
6309 // move from regular text to math
6310 needsUpdate = last_misspelled;
6311 } else if (oldTopSlice != cur.top() || oldBoundary != cur.boundary()) {
6312 // move inside regular text
6313 needsUpdate = last_misspelled
6314 || cur.paragraph().isMisspelled(cur.pos(), true);
6318 // FIXME: The cursor flag is reset two lines below
6319 // so we need to check here if some of the LFUN did touch that.
6320 // for now only Text::erase() and Text::backspace() do that.
6321 // The plan is to verify all the LFUNs and then to remove this
6322 // singleParUpdate boolean altogether.
6323 if (cur.result().screenUpdate() & Update::Force) {
6324 singleParUpdate = false;
6328 // FIXME: the following code should go in favor of fine grained
6329 // update flag treatment.
6330 if (singleParUpdate) {
6331 // Inserting characters does not change par height in general. So, try
6332 // to update _only_ this paragraph. BufferView will detect if a full
6333 // metrics update is needed anyway.
6334 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
6338 && &oldTopSlice.inset() == &cur.inset()
6339 && oldTopSlice.idx() == cur.idx()
6340 && !oldSelection // oldSelection is a backup of cur.selection() at the beginning of the function.
6341 && !cur.selection())
6342 // FIXME: it would be better if we could just do this
6344 //if (cur.result().update() != Update::FitCursor)
6345 // cur.noScreenUpdate();
6347 // But some LFUNs do not set Update::FitCursor when needed, so we
6348 // do it for all. This is not very harmfull as FitCursor will provoke
6349 // a full redraw only if needed but still, a proper review of all LFUN
6350 // should be done and this needsUpdate boolean can then be removed.
6351 cur.screenUpdateFlags(Update::FitCursor);
6353 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
6357 bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
6358 FuncStatus & status) const
6360 LBUFERR(this == cur.text());
6362 FontInfo const & fontinfo = cur.real_current_font.fontInfo();
6364 bool allow_in_passthru = false;
6365 InsetCode code = NO_CODE;
6367 switch (cmd.action()) {
6369 case LFUN_DEPTH_DECREMENT:
6370 enable = changeDepthAllowed(cur, DEC_DEPTH);
6373 case LFUN_DEPTH_INCREMENT:
6374 enable = changeDepthAllowed(cur, INC_DEPTH);
6378 // FIXME We really should not allow this to be put, e.g.,
6379 // in a footnote, or in ERT. But it would make sense in a
6380 // branch, so I'm not sure what to do.
6381 status.setOnOff(cur.paragraph().params().startOfAppendix());
6384 case LFUN_DIALOG_SHOW_NEW_INSET:
6385 if (cmd.argument() == "bibitem")
6386 code = BIBITEM_CODE;
6387 else if (cmd.argument() == "bibtex") {
6389 // not allowed in description items
6390 enable = !inDescriptionItem(cur);
6392 else if (cmd.argument() == "box")
6394 else if (cmd.argument() == "branch")
6396 else if (cmd.argument() == "citation")
6398 else if (cmd.argument() == "counter")
6399 code = COUNTER_CODE;
6400 else if (cmd.argument() == "ert")
6402 else if (cmd.argument() == "external")
6403 code = EXTERNAL_CODE;
6404 else if (cmd.argument() == "float")
6406 else if (cmd.argument() == "graphics")
6407 code = GRAPHICS_CODE;
6408 else if (cmd.argument() == "href")
6409 code = HYPERLINK_CODE;
6410 else if (cmd.argument() == "include")
6411 code = INCLUDE_CODE;
6412 else if (cmd.argument() == "index")
6414 else if (cmd.argument() == "index_print")
6415 code = INDEX_PRINT_CODE;
6416 else if (cmd.argument() == "listings")
6417 code = LISTINGS_CODE;
6418 else if (cmd.argument() == "mathspace")
6419 code = MATH_HULL_CODE;
6420 else if (cmd.argument() == "nomenclature")
6421 code = NOMENCL_CODE;
6422 else if (cmd.argument() == "nomencl_print")
6423 code = NOMENCL_PRINT_CODE;
6424 else if (cmd.argument() == "label")
6426 else if (cmd.argument() == "line")
6428 else if (cmd.argument() == "note")
6430 else if (cmd.argument() == "phantom")
6431 code = PHANTOM_CODE;
6432 else if (cmd.argument() == "ref")
6434 else if (cmd.argument() == "space")
6436 else if (cmd.argument() == "toc")
6438 else if (cmd.argument() == "vspace")
6440 else if (cmd.argument() == "wrap")
6444 case LFUN_ERT_INSERT:
6447 case LFUN_LISTING_INSERT:
6448 code = LISTINGS_CODE;
6449 // not allowed in description items
6450 enable = !inDescriptionItem(cur);
6452 case LFUN_FOOTNOTE_INSERT:
6455 case LFUN_TABULAR_INSERT:
6456 code = TABULAR_CODE;
6458 case LFUN_TABULAR_STYLE_INSERT:
6459 code = TABULAR_CODE;
6461 case LFUN_MARGINALNOTE_INSERT:
6464 case LFUN_FLOAT_INSERT:
6465 case LFUN_FLOAT_WIDE_INSERT:
6466 // FIXME: If there is a selection, we should check whether there
6467 // are floats in the selection, but this has performance issues, see
6468 // LFUN_CHANGE_ACCEPT/REJECT.
6470 if (inDescriptionItem(cur))
6471 // not allowed in description items
6474 InsetCode const inset_code = cur.inset().lyxCode();
6476 // algorithm floats cannot be put in another float
6477 if (to_utf8(cmd.argument()) == "algorithm") {
6478 enable = inset_code != WRAP_CODE && inset_code != FLOAT_CODE;
6482 // for figures and tables: only allow in another
6483 // float or wrap if it is of the same type and
6484 // not a subfloat already
6485 if(cur.inset().lyxCode() == code) {
6486 InsetFloat const & ins =
6487 static_cast<InsetFloat const &>(cur.inset());
6488 enable = ins.params().type == to_utf8(cmd.argument())
6489 && !ins.params().subfloat;
6490 } else if(cur.inset().lyxCode() == WRAP_CODE) {
6491 InsetWrap const & ins =
6492 static_cast<InsetWrap const &>(cur.inset());
6493 enable = ins.params().type == to_utf8(cmd.argument());
6497 case LFUN_WRAP_INSERT:
6499 // not allowed in description items
6500 enable = !inDescriptionItem(cur);
6502 case LFUN_FLOAT_LIST_INSERT: {
6503 code = FLOAT_LIST_CODE;
6504 // not allowed in description items
6505 enable = !inDescriptionItem(cur);
6507 FloatList const & floats = cur.buffer()->params().documentClass().floats();
6508 FloatList::const_iterator cit = floats[to_ascii(cmd.argument())];
6509 // make sure we know about such floats
6510 if (cit == floats.end() ||
6511 // and that we know how to generate a list of them
6512 (!cit->second.usesFloatPkg() && cit->second.listCommand().empty())) {
6513 status.setUnknown(true);
6514 // probably not necessary, but...
6520 case LFUN_CAPTION_INSERT: {
6521 code = CAPTION_CODE;
6522 string arg = cmd.getArg(0);
6523 bool varia = arg != "Unnumbered"
6524 && cur.inset().allowsCaptionVariation(arg);
6525 // not allowed in description items,
6526 // and in specific insets
6527 enable = !inDescriptionItem(cur)
6528 && (varia || arg.empty() || arg == "Standard");
6531 case LFUN_NOTE_INSERT:
6534 case LFUN_FLEX_INSERT: {
6536 docstring s = from_utf8(cmd.getArg(0));
6537 // Prepend "Flex:" prefix if not there
6538 if (!prefixIs(s, from_ascii("Flex:")))
6539 s = from_ascii("Flex:") + s;
6540 if (!cur.buffer()->params().documentClass().hasInsetLayout(s))
6544 cur.buffer()->params().documentClass().insetLayout(s).lyxtype();
6545 if (ilt != InsetLyXType::CHARSTYLE
6546 && ilt != InsetLyXType::CUSTOM
6547 && ilt != InsetLyXType::STANDARD)
6552 case LFUN_BOX_INSERT:
6555 case LFUN_BRANCH_INSERT:
6557 if (cur.buffer()->masterBuffer()->params().branchlist().empty()
6558 && cur.buffer()->params().branchlist().empty())
6561 case LFUN_IPA_INSERT:
6564 case LFUN_PHANTOM_INSERT:
6565 code = PHANTOM_CODE;
6567 case LFUN_LABEL_INSERT:
6570 case LFUN_INFO_INSERT:
6572 enable = cmd.argument().empty()
6573 || infoparams.validateArgument(cur.buffer(), cmd.argument(), true);
6575 case LFUN_ARGUMENT_INSERT: {
6577 allow_in_passthru = true;
6578 string const arg = cmd.getArg(0);
6583 Layout const & lay = cur.paragraph().layout();
6584 Layout::LaTeXArgMap args = lay.args();
6585 Layout::LaTeXArgMap::const_iterator const lait =
6587 if (lait != args.end()) {
6589 pit_type pit = cur.pit();
6590 pit_type lastpit = cur.pit();
6591 if (lay.isEnvironment() && !prefixIs(arg, "item:")) {
6592 // In a sequence of "merged" environment layouts, we only allow
6593 // non-item arguments once.
6594 lastpit = cur.lastpit();
6595 // get the first paragraph in sequence with this layout
6596 depth_type const current_depth = cur.paragraph().params().depth();
6600 Paragraph cpar = pars_[pit - 1];
6601 if (cpar.layout() == lay && cpar.params().depth() == current_depth)
6607 for (; pit <= lastpit; ++pit) {
6608 if (pars_[pit].layout() != lay)
6610 for (auto const & table : pars_[pit].insetList())
6611 if (InsetArgument const * ins = table.inset->asInsetArgument())
6612 if (ins->name() == arg) {
6613 // we have this already
6622 case LFUN_INDEX_INSERT:
6625 case LFUN_INDEX_PRINT:
6626 code = INDEX_PRINT_CODE;
6627 // not allowed in description items
6628 enable = !inDescriptionItem(cur);
6630 case LFUN_NOMENCL_INSERT:
6631 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6635 code = NOMENCL_CODE;
6637 case LFUN_NOMENCL_PRINT:
6638 code = NOMENCL_PRINT_CODE;
6639 // not allowed in description items
6640 enable = !inDescriptionItem(cur);
6642 case LFUN_HREF_INSERT:
6643 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6647 code = HYPERLINK_CODE;
6649 case LFUN_INDEXMACRO_INSERT: {
6650 string const arg = cmd.getArg(0);
6651 if (arg == "sortkey")
6652 code = INDEXMACRO_SORTKEY_CODE;
6654 code = INDEXMACRO_CODE;
6657 case LFUN_IPAMACRO_INSERT: {
6658 string const arg = cmd.getArg(0);
6660 code = IPADECO_CODE;
6662 code = IPACHAR_CODE;
6665 case LFUN_QUOTE_INSERT:
6666 // always allow this, since we will inset a raw quote
6667 // if an inset is not allowed.
6668 allow_in_passthru = true;
6670 case LFUN_SPECIALCHAR_INSERT:
6671 code = SPECIALCHAR_CODE;
6673 case LFUN_SPACE_INSERT:
6674 // slight hack: we know this is allowed in math mode
6678 case LFUN_PREVIEW_INSERT:
6679 code = PREVIEW_CODE;
6681 case LFUN_SCRIPT_INSERT:
6685 case LFUN_MATH_INSERT:
6686 case LFUN_MATH_AMS_MATRIX:
6687 case LFUN_MATH_MATRIX:
6688 case LFUN_MATH_DELIM:
6689 case LFUN_MATH_BIGDELIM:
6690 case LFUN_MATH_DISPLAY:
6691 case LFUN_MATH_MODE:
6692 case LFUN_MATH_MACRO:
6693 case LFUN_MATH_SUBSCRIPT:
6694 case LFUN_MATH_SUPERSCRIPT:
6695 code = MATH_HULL_CODE;
6698 case LFUN_REGEXP_MODE:
6699 code = MATH_HULL_CODE;
6700 enable = cur.buffer()->isInternal() && !cur.inRegexped();
6703 case LFUN_INSET_MODIFY:
6704 // We need to disable this, because we may get called for a
6706 // InsetTabular::getStatus() -> InsetText::getStatus()
6707 // and we don't handle LFUN_INSET_MODIFY.
6711 case LFUN_FONT_EMPH:
6712 status.setOnOff(fontinfo.emph() == FONT_ON);
6713 enable = !cur.paragraph().isPassThru();
6716 case LFUN_FONT_ITAL:
6717 status.setOnOff(fontinfo.shape() == ITALIC_SHAPE);
6718 enable = !cur.paragraph().isPassThru();
6721 case LFUN_FONT_NOUN:
6722 status.setOnOff(fontinfo.noun() == FONT_ON);
6723 enable = !cur.paragraph().isPassThru();
6726 case LFUN_FONT_BOLD:
6727 case LFUN_FONT_BOLDSYMBOL:
6728 status.setOnOff(fontinfo.series() == BOLD_SERIES);
6729 enable = !cur.paragraph().isPassThru();
6732 case LFUN_FONT_SANS:
6733 status.setOnOff(fontinfo.family() == SANS_FAMILY);
6734 enable = !cur.paragraph().isPassThru();
6737 case LFUN_FONT_ROMAN:
6738 status.setOnOff(fontinfo.family() == ROMAN_FAMILY);
6739 enable = !cur.paragraph().isPassThru();
6742 case LFUN_FONT_TYPEWRITER:
6743 status.setOnOff(fontinfo.family() == TYPEWRITER_FAMILY);
6744 enable = !cur.paragraph().isPassThru();
6748 enable = cur.selection();
6752 if (cmd.argument().empty()) {
6753 if (theClipboard().isInternal())
6754 enable = cap::numberOfSelections() > 0;
6756 enable = !theClipboard().empty();
6760 // we have an argument
6761 string const arg = to_utf8(cmd.argument());
6762 if (isStrUnsignedInt(arg)) {
6763 // it's a number and therefore means the internal stack
6764 unsigned int n = convert<unsigned int>(arg);
6765 enable = cap::numberOfSelections() > n;
6769 // explicit text type?
6770 if (arg == "html") {
6771 // Do not enable for PlainTextType, since some tidying in the
6772 // frontend is needed for HTML, which is too unsafe for plain text.
6773 enable = theClipboard().hasTextContents(Clipboard::HtmlTextType);
6775 } else if (arg == "latex") {
6776 // LaTeX is usually not available on the clipboard with
6777 // the correct MIME type, but in plain text.
6778 enable = theClipboard().hasTextContents(Clipboard::PlainTextType) ||
6779 theClipboard().hasTextContents(Clipboard::LaTeXTextType);
6783 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
6785 type = Clipboard::PdfGraphicsType;
6786 else if (arg == "png")
6787 type = Clipboard::PngGraphicsType;
6788 else if (arg == "jpeg")
6789 type = Clipboard::JpegGraphicsType;
6790 else if (arg == "linkback")
6791 type = Clipboard::LinkBackGraphicsType;
6792 else if (arg == "emf")
6793 type = Clipboard::EmfGraphicsType;
6794 else if (arg == "wmf")
6795 type = Clipboard::WmfGraphicsType;
6798 LYXERR0("Unrecognized graphics type: " << arg);
6799 // we don't want to assert if the user just mistyped the LFUN
6800 LATTEST(cmd.origin() != FuncRequest::INTERNAL);
6804 enable = theClipboard().hasGraphicsContents(type);
6808 case LFUN_CLIPBOARD_PASTE:
6809 case LFUN_CLIPBOARD_PASTE_SIMPLE:
6810 enable = !theClipboard().empty();
6813 case LFUN_PRIMARY_SELECTION_PASTE:
6814 status.setUnknown(!theSelection().supported());
6815 enable = cur.selection() || !theSelection().empty();
6818 case LFUN_SELECTION_PASTE:
6819 enable = cap::selection();
6822 case LFUN_PARAGRAPH_MOVE_UP:
6823 enable = cur.pit() > 0 && !cur.selection();
6826 case LFUN_PARAGRAPH_MOVE_DOWN:
6827 enable = cur.pit() < cur.lastpit() && !cur.selection();
6830 case LFUN_CHANGE_ACCEPT:
6831 case LFUN_CHANGE_REJECT:
6832 if (!cur.selection())
6833 enable = cur.paragraph().isChanged(cur.pos());
6835 // will enable if there is a change in the selection
6838 // cheap improvement for efficiency: using cached
6839 // buffer variable, if there is no change in the
6840 // document, no need to check further.
6841 if (!cur.buffer()->areChangesPresent())
6844 for (DocIterator it = cur.selectionBegin(); ; it.forwardPar()) {
6845 pos_type const beg = it.pos();
6847 bool const in_last_par = (it.pit() == cur.selectionEnd().pit() &&
6848 it.idx() == cur.selectionEnd().idx());
6850 end = cur.selectionEnd().pos();
6852 // the +1 is needed for cases, e.g., where there is a
6853 // paragraph break. See #11629.
6854 end = it.lastpos() + 1;
6855 if (beg != end && it.paragraph().isChanged(beg, end)) {
6859 if (beg != end && it.paragraph().hasChangedInsets(beg, end)) {
6869 case LFUN_OUTLINE_UP:
6870 case LFUN_OUTLINE_DOWN:
6871 case LFUN_OUTLINE_IN:
6872 case LFUN_OUTLINE_OUT:
6873 // FIXME: LyX is not ready for outlining within inset.
6874 enable = isMainText()
6875 && cur.buffer()->text().getTocLevel(cur.pit()) != Layout::NOT_IN_TOC;
6878 case LFUN_NEWLINE_INSERT:
6879 // LaTeX restrictions (labels or empty par)
6880 enable = !cur.paragraph().isPassThru()
6881 && cur.pos() > cur.paragraph().beginOfBody();
6884 case LFUN_SEPARATOR_INSERT:
6885 // Always enabled for now
6889 case LFUN_TAB_INSERT:
6890 case LFUN_TAB_DELETE:
6891 enable = cur.paragraph().isPassThru();
6894 case LFUN_GRAPHICS_SET_GROUP: {
6895 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
6899 status.setOnOff(to_utf8(cmd.argument()) == ins->getParams().groupId);
6903 case LFUN_NEWPAGE_INSERT:
6904 // not allowed in description items
6905 code = NEWPAGE_CODE;
6906 enable = !inDescriptionItem(cur);
6910 enable = !cur.paragraph().isPassThru();
6911 status.setOnOff(cmd.getArg(0) == cur.real_current_font.language()->lang());
6914 case LFUN_PARAGRAPH_BREAK:
6915 enable = inset().allowMultiPar();
6918 case LFUN_SPELLING_ADD:
6919 case LFUN_SPELLING_ADD_LOCAL:
6920 case LFUN_SPELLING_REMOVE_LOCAL:
6921 case LFUN_SPELLING_IGNORE:
6922 case LFUN_SPELLING_REMOVE:
6923 enable = theSpellChecker() != nullptr;
6924 if (enable && !cmd.getArg(1).empty()) {
6925 // validate explicitly given language
6926 Language const * const lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
6927 enable &= lang != nullptr;
6932 case LFUN_LAYOUT_TOGGLE: {
6933 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
6934 docstring const req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
6935 docstring const layout = resolveLayout(req_layout, cur);
6937 // FIXME: make this work in multicell selection case
6938 enable = !owner_->forcePlainLayout() && !layout.empty() && !cur.selIsMultiCell();
6939 status.setOnOff(!owner_->forcePlainLayout() && !cur.selIsMultiCell()
6940 && isAlreadyLayout(layout, cur));
6944 case LFUN_ENVIRONMENT_SPLIT: {
6945 if (cmd.argument() == "outer") {
6946 // check if we have an environment in our nesting hierarchy
6948 depth_type const current_depth = cur.paragraph().params().depth();
6949 pit_type pit = cur.pit();
6950 Paragraph cpar = pars_[pit];
6952 if (pit == 0 || cpar.params().depth() == 0)
6956 if (cpar.params().depth() < current_depth)
6957 res = cpar.layout().isEnvironment();
6962 else if (cmd.argument() == "previous") {
6963 // look if we have an environment in the previous par
6964 pit_type pit = cur.pit();
6965 Paragraph cpar = pars_[pit];
6969 enable = cpar.layout().isEnvironment();
6975 else if (cur.paragraph().layout().isEnvironment()) {
6976 enable = cmd.argument() == "before"
6977 || cur.pos() > 0 || !isFirstInSequence(cur.pit());
6984 case LFUN_LAYOUT_PARAGRAPH:
6985 case LFUN_PARAGRAPH_PARAMS:
6986 case LFUN_PARAGRAPH_PARAMS_APPLY:
6987 case LFUN_PARAGRAPH_UPDATE:
6988 enable = owner_->allowParagraphCustomization();
6991 // FIXME: why are accent lfuns forbidden with pass_thru layouts?
6992 // Because they insert COMBINING DIACRITICAL Unicode characters,
6993 // that cannot be handled by LaTeX but must be converted according
6994 // to the definition in lib/unicodesymbols?
6995 case LFUN_ACCENT_ACUTE:
6996 case LFUN_ACCENT_BREVE:
6997 case LFUN_ACCENT_CARON:
6998 case LFUN_ACCENT_CEDILLA:
6999 case LFUN_ACCENT_CIRCLE:
7000 case LFUN_ACCENT_CIRCUMFLEX:
7001 case LFUN_ACCENT_DOT:
7002 case LFUN_ACCENT_GRAVE:
7003 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
7004 case LFUN_ACCENT_MACRON:
7005 case LFUN_ACCENT_OGONEK:
7006 case LFUN_ACCENT_TIE:
7007 case LFUN_ACCENT_TILDE:
7008 case LFUN_ACCENT_PERISPOMENI:
7009 case LFUN_ACCENT_UMLAUT:
7010 case LFUN_ACCENT_UNDERBAR:
7011 case LFUN_ACCENT_UNDERDOT:
7012 case LFUN_FONT_FRAK:
7013 case LFUN_FONT_SIZE:
7014 case LFUN_FONT_STATE:
7015 case LFUN_FONT_UNDERLINE:
7016 case LFUN_FONT_STRIKEOUT:
7017 case LFUN_FONT_CROSSOUT:
7018 case LFUN_FONT_UNDERUNDERLINE:
7019 case LFUN_FONT_UNDERWAVE:
7020 case LFUN_FONT_NO_SPELLCHECK:
7021 case LFUN_TEXTSTYLE_UPDATE:
7022 enable = !cur.paragraph().isPassThru();
7025 case LFUN_FONT_DEFAULT: {
7026 Font font(inherit_font, ignore_language);
7027 BufferParams const & bp = cur.buffer()->masterParams();
7028 if (cur.selection()) {
7030 // Check if we have a non-default font attribute
7031 // in the selection range.
7032 DocIterator const from = cur.selectionBegin();
7033 DocIterator const to = cur.selectionEnd();
7034 for (DocIterator dit = from ; dit != to && !dit.atEnd(); ) {
7035 if (!dit.inTexted()) {
7039 Paragraph const & par = dit.paragraph();
7040 pos_type const pos = dit.pos();
7041 Font tmp = par.getFontSettings(bp, pos);
7042 if (tmp.fontInfo() != font.fontInfo()
7043 || tmp.language() != bp.language) {
7051 // Disable if all is default already.
7052 enable = (cur.current_font.fontInfo() != font.fontInfo()
7053 || cur.current_font.language() != bp.language);
7057 case LFUN_TEXTSTYLE_APPLY:
7058 enable = !freeFonts.empty();
7061 case LFUN_WORD_DELETE_FORWARD:
7062 case LFUN_WORD_DELETE_BACKWARD:
7063 case LFUN_LINE_DELETE_FORWARD:
7064 case LFUN_WORD_FORWARD:
7065 case LFUN_WORD_BACKWARD:
7066 case LFUN_WORD_RIGHT:
7067 case LFUN_WORD_LEFT:
7068 case LFUN_CHAR_FORWARD:
7069 case LFUN_CHAR_FORWARD_SELECT:
7070 case LFUN_CHAR_BACKWARD:
7071 case LFUN_CHAR_BACKWARD_SELECT:
7072 case LFUN_CHAR_LEFT:
7073 case LFUN_CHAR_LEFT_SELECT:
7074 case LFUN_CHAR_RIGHT:
7075 case LFUN_CHAR_RIGHT_SELECT:
7077 case LFUN_UP_SELECT:
7079 case LFUN_DOWN_SELECT:
7080 case LFUN_PARAGRAPH_SELECT:
7081 case LFUN_PARAGRAPH_UP_SELECT:
7082 case LFUN_PARAGRAPH_DOWN_SELECT:
7083 case LFUN_LINE_BEGIN_SELECT:
7084 case LFUN_LINE_END_SELECT:
7085 case LFUN_WORD_FORWARD_SELECT:
7086 case LFUN_WORD_BACKWARD_SELECT:
7087 case LFUN_WORD_RIGHT_SELECT:
7088 case LFUN_WORD_LEFT_SELECT:
7089 case LFUN_WORD_SELECT:
7090 case LFUN_SECTION_SELECT:
7091 case LFUN_BUFFER_BEGIN:
7092 case LFUN_BUFFER_END:
7093 case LFUN_BUFFER_BEGIN_SELECT:
7094 case LFUN_BUFFER_END_SELECT:
7095 case LFUN_INSET_BEGIN:
7096 case LFUN_INSET_END:
7097 case LFUN_INSET_BEGIN_SELECT:
7098 case LFUN_INSET_END_SELECT:
7099 case LFUN_PARAGRAPH_UP:
7100 case LFUN_PARAGRAPH_DOWN:
7101 case LFUN_LINE_BEGIN:
7103 case LFUN_CHAR_DELETE_FORWARD:
7104 case LFUN_CHAR_DELETE_BACKWARD:
7105 case LFUN_WORD_UPCASE:
7106 case LFUN_WORD_LOWCASE:
7107 case LFUN_WORD_CAPITALIZE:
7108 case LFUN_CHARS_TRANSPOSE:
7109 case LFUN_SERVER_GET_XY:
7110 case LFUN_SERVER_SET_XY:
7111 case LFUN_SERVER_GET_LAYOUT:
7112 case LFUN_SELF_INSERT:
7113 case LFUN_UNICODE_INSERT:
7114 case LFUN_THESAURUS_ENTRY:
7116 case LFUN_SERVER_GET_STATISTICS:
7117 // these are handled in our dispatch()
7121 case LFUN_INSET_INSERT: {
7122 string const type = cmd.getArg(0);
7123 if (type == "toc") {
7125 // not allowed in description items
7126 //FIXME: couldn't this be merged in Inset::insetAllowed()?
7127 enable = !inDescriptionItem(cur);
7134 case LFUN_SEARCH_IGNORE: {
7135 bool const value = cmd.getArg(1) == "true";
7136 setIgnoreFormat(cmd.getArg(0), value);
7146 || !cur.inset().insetAllowed(code)
7147 || (cur.paragraph().layout().pass_thru && !allow_in_passthru)))
7150 status.setEnabled(enable);
7155 void Text::pasteString(Cursor & cur, docstring const & clip,
7158 if (!clip.empty()) {
7161 insertStringAsParagraphs(cur, clip, cur.current_font);
7163 insertStringAsLines(cur, clip, cur.current_font);
7168 // FIXME: an item inset would make things much easier.
7169 bool Text::inDescriptionItem(Cursor const & cur) const
7171 Paragraph const & par = cur.paragraph();
7172 pos_type const pos = cur.pos();
7173 pos_type const body_pos = par.beginOfBody();
7175 if (par.layout().latextype != LATEX_LIST_ENVIRONMENT
7176 && (par.layout().latextype != LATEX_ITEM_ENVIRONMENT
7177 || par.layout().margintype != MARGIN_FIRST_DYNAMIC))
7180 return (pos < body_pos
7182 && (pos == 0 || par.getChar(pos - 1) != ' ')));
7186 std::vector<docstring> Text::getFreeFonts() const
7188 vector<docstring> ffList;
7190 for (auto const & f : freeFonts)
7191 ffList.push_back(f.first);