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 = (!isMainText())
2483 // inside insets, we call the getFont() method
2485 // outside, we access the layout font directly
2486 : owner_->getLayout().font();
2491 FontInfo font = layout.font;
2492 // Realize with the fonts of lesser depth.
2493 //font.realize(outerFont(pit));
2494 font.realize(owner_->buffer().params().getFont().fontInfo());
2500 // Note that this is supposed to return a fully realized font.
2501 FontInfo Text::labelFont(Paragraph const & par) const
2503 Buffer const & buffer = owner_->buffer();
2504 Layout const & layout = par.layout();
2506 if (!par.getDepth()) {
2507 FontInfo lf = layout.reslabelfont;
2508 // In case the default family has been customized
2509 if (layout.labelfont.family() == INHERIT_FAMILY)
2510 lf.setFamily(buffer.params().getFont().fontInfo().family());
2514 FontInfo font = layout.labelfont;
2515 // Realize with the fonts of lesser depth.
2516 font.realize(buffer.params().getFont().fontInfo());
2522 void Text::setCharFont(pit_type pit,
2523 pos_type pos, Font const & fnt, Font const & display_font)
2525 Buffer const & buffer = owner_->buffer();
2527 Layout const & layout = pars_[pit].layout();
2529 // Get concrete layout font to reduce against
2530 FontInfo layoutfont;
2532 if (pos < pars_[pit].beginOfBody())
2533 layoutfont = layout.labelfont;
2535 layoutfont = layout.font;
2537 // Realize against environment font information
2538 if (pars_[pit].getDepth()) {
2540 while (!layoutfont.resolved() &&
2541 tp != pit_type(paragraphs().size()) &&
2542 pars_[tp].getDepth()) {
2544 if (tp != pit_type(paragraphs().size()))
2545 layoutfont.realize(pars_[tp].layout().font);
2549 // Inside inset, apply the inset's font attributes if any
2552 layoutfont.realize(display_font.fontInfo());
2554 layoutfont.realize(buffer.params().getFont().fontInfo());
2556 // Now, reduce font against full layout font
2557 font.fontInfo().reduce(layoutfont);
2559 pars_[pit].setFont(pos, font);
2563 void Text::setInsetFont(BufferView const & bv, pit_type pit,
2564 pos_type pos, Font const & font)
2566 Inset * const inset = pars_[pit].getInset(pos);
2567 LASSERT(inset && inset->resetFontEdit(), return);
2569 idx_type endidx = inset->nargs();
2570 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
2571 Text * text = cs.text();
2573 // last position of the cell
2574 CursorSlice cellend = cs;
2575 cellend.pit() = cellend.lastpit();
2576 cellend.pos() = cellend.lastpos();
2577 text->setFont(bv, cs, cellend, font);
2583 void Text::setLayout(pit_type start, pit_type end,
2584 docstring const & layout)
2586 // FIXME: make this work in multicell selection case
2587 LASSERT(start != end, return);
2589 Buffer const & buffer = owner_->buffer();
2590 BufferParams const & bp = buffer.params();
2591 Layout const & lyxlayout = bp.documentClass()[layout];
2593 for (pit_type pit = start; pit != end; ++pit) {
2594 Paragraph & par = pars_[pit];
2595 // Is this a separating paragraph? If so,
2596 // this needs to be standard layout
2597 bool const is_separator = par.size() == 1
2598 && par.isEnvSeparator(0);
2599 par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
2600 if (lyxlayout.margintype == MARGIN_MANUAL)
2601 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
2604 deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
2608 // set layout over selection and make a total rebreak of those paragraphs
2609 void Text::setLayout(Cursor & cur, docstring const & layout)
2611 LBUFERR(this == cur.text());
2613 pit_type start = cur.selBegin().pit();
2614 pit_type end = cur.selEnd().pit() + 1;
2615 cur.recordUndoSelection();
2616 setLayout(start, end, layout);
2618 cur.setCurrentFont();
2619 cur.forceBufferUpdate();
2623 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
2624 Paragraph const & par, int max_depth)
2626 int const depth = par.params().depth();
2627 if (type == Text::INC_DEPTH && depth < max_depth)
2629 if (type == Text::DEC_DEPTH && depth > 0)
2635 bool Text::changeDepthAllowed(Cursor const & cur, DEPTH_CHANGE type) const
2637 LBUFERR(this == cur.text());
2638 // this happens when selecting several cells in tabular (bug 2630)
2639 if (cur.selBegin().idx() != cur.selEnd().idx())
2642 pit_type const beg = cur.selBegin().pit();
2643 pit_type const end = cur.selEnd().pit() + 1;
2644 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2646 for (pit_type pit = beg; pit != end; ++pit) {
2647 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
2649 max_depth = pars_[pit].getMaxDepthAfter();
2655 void Text::changeDepth(Cursor const & cur, DEPTH_CHANGE type)
2657 LBUFERR(this == cur.text());
2658 pit_type const beg = cur.selBegin().pit();
2659 pit_type const end = cur.selEnd().pit() + 1;
2660 cur.recordUndoSelection();
2661 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2663 for (pit_type pit = beg; pit != end; ++pit) {
2664 Paragraph & par = pars_[pit];
2665 if (lyx::changeDepthAllowed(type, par, max_depth)) {
2666 int const depth = par.params().depth();
2667 if (type == INC_DEPTH)
2668 par.params().depth(depth + 1);
2670 par.params().depth(depth - 1);
2672 max_depth = par.getMaxDepthAfter();
2674 // this handles the counter labels, and also fixes up
2675 // depth values for follow-on (child) paragraphs
2676 cur.forceBufferUpdate();
2680 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
2682 LASSERT(this == cur.text(), return);
2684 // If there is a selection, record undo before the cursor font is changed.
2685 if (cur.selection())
2686 cur.recordUndoSelection();
2688 // Set the current_font
2689 // Determine basis font
2690 FontInfo layoutfont;
2691 pit_type pit = cur.pit();
2692 if (cur.pos() < pars_[pit].beginOfBody())
2693 layoutfont = labelFont(pars_[pit]);
2695 layoutfont = layoutFont(pit);
2697 // Update current font
2698 cur.real_current_font.update(font,
2699 cur.buffer()->params().language,
2702 // Reduce to implicit settings
2703 cur.current_font = cur.real_current_font;
2704 cur.current_font.fontInfo().reduce(layoutfont);
2705 // And resolve it completely
2706 cur.real_current_font.fontInfo().realize(layoutfont);
2708 // if there is no selection that's all we need to do
2709 if (!cur.selection())
2712 // Ok, we have a selection.
2713 Font newfont = font;
2716 // Toggling behaves as follows: We check the first character of the
2717 // selection. If it's (say) got EMPH on, then we set to off; if off,
2718 // then to on. With families and the like, we set it to INHERIT, if
2719 // we already have it.
2720 CursorSlice const & sl = cur.selBegin();
2721 Text const & text = *sl.text();
2722 Paragraph const & par = text.getPar(sl.pit());
2724 // get font at the position
2725 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
2726 text.outerFont(sl.pit()));
2727 FontInfo const & oldfi = oldfont.fontInfo();
2729 FontInfo & newfi = newfont.fontInfo();
2731 FontFamily newfam = newfi.family();
2732 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
2733 newfam == oldfi.family())
2734 newfi.setFamily(INHERIT_FAMILY);
2736 FontSeries newser = newfi.series();
2737 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
2738 newfi.setSeries(INHERIT_SERIES);
2740 FontShape newshp = newfi.shape();
2741 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
2742 newshp == oldfi.shape())
2743 newfi.setShape(INHERIT_SHAPE);
2745 ColorCode newcol = newfi.color();
2746 if (newcol != Color_none && newcol != Color_inherit
2747 && newcol != Color_ignore && newcol == oldfi.color())
2748 newfi.setColor(Color_none);
2751 if (newfi.emph() == FONT_TOGGLE)
2752 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
2753 if (newfi.underbar() == FONT_TOGGLE)
2754 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
2755 if (newfi.strikeout() == FONT_TOGGLE)
2756 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
2757 if (newfi.xout() == FONT_TOGGLE)
2758 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
2759 if (newfi.uuline() == FONT_TOGGLE)
2760 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
2761 if (newfi.uwave() == FONT_TOGGLE)
2762 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
2763 if (newfi.noun() == FONT_TOGGLE)
2764 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
2765 if (newfi.number() == FONT_TOGGLE)
2766 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
2767 if (newfi.nospellcheck() == FONT_TOGGLE)
2768 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
2771 setFont(cur.bv(), cur.selectionBegin().top(),
2772 cur.selectionEnd().top(), newfont);
2776 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
2777 CursorSlice const & end, Font const & font)
2779 Buffer const & buffer = bv.buffer();
2781 // Don't use forwardChar here as ditend might have
2782 // pos() == lastpos() and forwardChar would miss it.
2783 // Can't use forwardPos either as this descends into
2785 Language const * language = buffer.params().language;
2786 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
2787 if (dit.pos() == dit.lastpos())
2789 pit_type const pit = dit.pit();
2790 pos_type const pos = dit.pos();
2791 Inset * inset = pars_[pit].getInset(pos);
2792 if (inset && inset->resetFontEdit()) {
2793 // We need to propagate the font change to all
2794 // text cells of the inset (bugs 1973, 6919).
2795 setInsetFont(bv, pit, pos, font);
2797 TextMetrics const & tm = bv.textMetrics(this);
2798 Font f = tm.displayFont(pit, pos);
2799 f.update(font, language);
2800 setCharFont(pit, pos, f, tm.font_);
2801 // font change may change language...
2802 // spell checker has to know that
2803 pars_[pit].requestSpellCheck(pos);
2808 bool Text::cursorTop(Cursor & cur)
2810 LBUFERR(this == cur.text());
2811 return setCursor(cur, 0, 0);
2815 bool Text::cursorBottom(Cursor & cur)
2817 LBUFERR(this == cur.text());
2818 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
2822 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
2824 LBUFERR(this == cur.text());
2825 // If the mask is completely neutral, tell user
2826 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
2827 // Could only happen with user style
2828 cur.message(_("No font change defined."));
2832 // Try implicit word selection
2833 // If there is a change in the language the implicit word selection
2835 CursorSlice const resetCursor = cur.top();
2836 bool const implicitSelection =
2837 font.language() == ignore_language
2838 && font.fontInfo().number() == FONT_IGNORE
2839 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
2842 setFont(cur, font, toggleall);
2844 // Implicit selections are cleared afterwards
2845 // and cursor is set to the original position.
2846 if (implicitSelection) {
2847 cur.clearSelection();
2848 cur.top() = resetCursor;
2852 // if there was no selection at all, the point was to change cursor font.
2853 // Otherwise, we want to reset it to local text font.
2854 if (cur.selection() || implicitSelection)
2855 cur.setCurrentFont();
2859 docstring Text::getStringForDialog(Cursor & cur)
2861 LBUFERR(this == cur.text());
2863 if (cur.selection())
2864 return cur.selectionAsString(false);
2866 // Try implicit word selection. If there is a change
2867 // in the language the implicit word selection is
2869 selectWordWhenUnderCursor(cur, WHOLE_WORD);
2870 docstring const & retval = cur.selectionAsString(false);
2871 cur.clearSelection();
2876 void Text::setLabelWidthStringToSequence(Cursor const & cur,
2877 docstring const & s)
2880 // Find first of same layout in sequence
2881 while (!isFirstInSequence(c.pit())) {
2882 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
2885 // now apply label width string to every par
2887 depth_type const depth = c.paragraph().getDepth();
2888 Layout const & layout = c.paragraph().layout();
2889 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
2890 while (c.paragraph().getDepth() > depth) {
2892 if (c.pit() > c.lastpit())
2895 if (c.paragraph().getDepth() < depth)
2897 if (c.paragraph().layout() != layout)
2900 c.paragraph().setLabelWidthString(s);
2905 void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
2907 LBUFERR(cur.text());
2910 string const argument = to_utf8(arg);
2911 depth_type priordepth = -1;
2914 c.setCursor(cur.selectionBegin());
2915 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
2916 Paragraph & par = c.paragraph();
2917 ParagraphParameters params = par.params();
2918 params.read(argument, merge);
2919 // Changes to label width string apply to all paragraphs
2920 // with same layout in a sequence.
2921 // Do this only once for a selected range of paragraphs
2922 // of the same layout and depth.
2924 par.params().apply(params, par.layout());
2925 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2926 setLabelWidthStringToSequence(c, params.labelWidthString());
2927 priordepth = par.getDepth();
2928 priorlayout = par.layout();
2933 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
2935 LBUFERR(cur.text());
2937 depth_type priordepth = -1;
2940 c.setCursor(cur.selectionBegin());
2941 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
2942 Paragraph & par = c.paragraph();
2943 // Changes to label width string apply to all paragraphs
2944 // with same layout in a sequence.
2945 // Do this only once for a selected range of paragraphs
2946 // of the same layout and depth.
2948 par.params().apply(p, par.layout());
2949 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2950 setLabelWidthStringToSequence(c,
2951 par.params().labelWidthString());
2952 priordepth = par.getDepth();
2953 priorlayout = par.layout();
2958 // this really should just insert the inset and not move the cursor.
2959 void Text::insertInset(Cursor & cur, Inset * inset)
2961 LBUFERR(this == cur.text());
2963 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
2964 Change(cur.buffer()->params().track_changes
2965 ? Change::INSERTED : Change::UNCHANGED));
2969 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
2970 bool setfont, bool boundary)
2972 TextMetrics const & tm = cur.bv().textMetrics(this);
2973 bool const update_needed = !tm.contains(pit);
2975 setCursorIntern(cur, pit, pos, setfont, boundary);
2976 return cur.bv().checkDepm(cur, old) || update_needed;
2980 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
2981 bool setfont, bool boundary)
2983 LBUFERR(this == cur.text());
2984 cur.boundary(boundary);
2985 cur.top().setPitPos(pit, pos);
2987 cur.setCurrentFont();
2991 bool Text::checkAndActivateInset(Cursor & cur, bool front)
2993 if (front && cur.pos() == cur.lastpos())
2995 if (!front && cur.pos() == 0)
2997 Inset * inset = front ? cur.nextInset() : cur.prevInset();
2998 if (!inset || !inset->editable())
3000 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3003 * Apparently, when entering an inset we are expected to be positioned
3004 * *before* it in the containing paragraph, regardless of the direction
3005 * from which we are entering. Otherwise, cursor placement goes awry,
3006 * and when we exit from the beginning, we'll be placed *after* the
3011 inset->edit(cur, front);
3012 cur.setCurrentFont();
3013 cur.boundary(false);
3018 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
3020 if (cur.pos() == -1)
3022 if (cur.pos() == cur.lastpos())
3024 Paragraph & par = cur.paragraph();
3025 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
3026 if (!inset || !inset->editable())
3028 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3030 inset->edit(cur, movingForward,
3031 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
3032 cur.setCurrentFont();
3033 cur.boundary(false);
3038 bool Text::cursorBackward(Cursor & cur)
3040 // Tell BufferView to test for FitCursor in any case!
3041 cur.screenUpdateFlags(Update::FitCursor);
3043 // not at paragraph start?
3044 if (cur.pos() > 0) {
3045 // if on right side of boundary (i.e. not at paragraph end, but line end)
3046 // -> skip it, i.e. set boundary to true, i.e. go only logically left
3047 // there are some exceptions to ignore this: lineseps, newlines, spaces
3049 // some effectless debug code to see the values in the debugger
3050 bool bound = cur.boundary();
3051 int rowpos = cur.textRow().pos();
3052 int pos = cur.pos();
3053 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
3054 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
3055 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
3057 if (!cur.boundary() &&
3058 cur.textRow().pos() == cur.pos() &&
3059 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
3060 !cur.paragraph().isNewline(cur.pos() - 1) &&
3061 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
3062 !cur.paragraph().isSeparator(cur.pos() - 1)) {
3063 return setCursor(cur, cur.pit(), cur.pos(), true, true);
3066 // go left and try to enter inset
3067 if (checkAndActivateInset(cur, false))
3070 // normal character left
3071 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
3074 // move to the previous paragraph or do nothing
3075 if (cur.pit() > 0) {
3076 Paragraph & par = getPar(cur.pit() - 1);
3077 pos_type lastpos = par.size();
3078 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
3079 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
3081 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
3087 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
3089 Cursor temp_cur = cur;
3090 temp_cur.posVisLeft(skip_inset);
3091 if (temp_cur.depth() > cur.depth()) {
3095 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3096 true, temp_cur.boundary());
3100 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
3102 Cursor temp_cur = cur;
3103 temp_cur.posVisRight(skip_inset);
3104 if (temp_cur.depth() > cur.depth()) {
3108 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3109 true, temp_cur.boundary());
3113 bool Text::cursorForward(Cursor & cur)
3115 // Tell BufferView to test for FitCursor in any case!
3116 cur.screenUpdateFlags(Update::FitCursor);
3118 // not at paragraph end?
3119 if (cur.pos() != cur.lastpos()) {
3120 // in front of editable inset, i.e. jump into it?
3121 if (checkAndActivateInset(cur, true))
3124 TextMetrics const & tm = cur.bv().textMetrics(this);
3125 // if left of boundary -> just jump to right side
3126 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
3127 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
3128 return setCursor(cur, cur.pit(), cur.pos(), true, false);
3130 // next position is left of boundary,
3131 // but go to next line for special cases like space, newline, linesep
3133 // some effectless debug code to see the values in the debugger
3134 int endpos = cur.textRow().endpos();
3135 int lastpos = cur.lastpos();
3136 int pos = cur.pos();
3137 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
3138 bool newline = cur.paragraph().isNewline(cur.pos());
3139 bool sep = cur.paragraph().isSeparator(cur.pos());
3140 if (cur.pos() != cur.lastpos()) {
3141 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
3142 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
3143 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
3146 if (cur.textRow().endpos() == cur.pos() + 1) {
3147 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
3148 cur.pos() + 1 == cur.lastpos() &&
3149 cur.pit() != cur.lastpit()) {
3150 // move to next paragraph
3151 return setCursor(cur, cur.pit() + 1, 0, true, false);
3152 } else if (cur.textRow().endpos() != cur.lastpos() &&
3153 !cur.paragraph().isNewline(cur.pos()) &&
3154 !cur.paragraph().isEnvSeparator(cur.pos()) &&
3155 !cur.paragraph().isLineSeparator(cur.pos()) &&
3156 !cur.paragraph().isSeparator(cur.pos())) {
3157 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3161 // in front of RTL boundary? Stay on this side of the boundary because:
3162 // ab|cDDEEFFghi -> abc|DDEEFFghi
3163 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
3164 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3167 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
3170 // move to next paragraph
3171 if (cur.pit() != cur.lastpit())
3172 return setCursor(cur, cur.pit() + 1, 0, true, false);
3177 bool Text::cursorUpParagraph(Cursor & cur)
3179 bool updated = false;
3181 updated = setCursor(cur, cur.pit(), 0);
3182 else if (cur.pit() != 0)
3183 updated = setCursor(cur, cur.pit() - 1, 0);
3188 bool Text::cursorDownParagraph(Cursor & cur)
3190 bool updated = false;
3191 if (cur.pit() != cur.lastpit())
3192 if (lyxrc.mac_like_cursor_movement)
3193 if (cur.pos() == cur.lastpos())
3194 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
3196 updated = setCursor(cur, cur.pit(), cur.lastpos());
3198 updated = setCursor(cur, cur.pit() + 1, 0);
3200 updated = setCursor(cur, cur.pit(), cur.lastpos());
3206 /** delete num_spaces characters between from and to. Return the
3207 * number of spaces that got physically deleted (not marked as
3209 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
3210 int num_spaces, bool const trackChanges)
3212 if (num_spaces <= 0)
3215 // First, delete spaces marked as inserted
3217 while (pos < to && num_spaces > 0) {
3218 Change const & change = par.lookupChange(pos);
3219 if (change.inserted() && change.currentAuthor()) {
3220 par.eraseChar(pos, trackChanges);
3227 // Then remove remaining spaces
3228 int const psize = par.size();
3229 par.eraseChars(from, from + num_spaces, trackChanges);
3230 return psize - par.size();
3236 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
3237 Cursor & old, bool & need_anchor_change)
3239 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
3241 Paragraph & oldpar = old.paragraph();
3242 bool const trackChanges = cur.buffer()->params().track_changes;
3243 bool result = false;
3245 // We do nothing if cursor did not move
3246 if (cur.top() == old.top())
3249 // We do not do anything on read-only documents
3250 if (cur.buffer()->isReadonly())
3253 // Whether a common inset is found and whether the cursor is still in
3254 // the same paragraph (possibly nested).
3255 int const depth = cur.find(&old.inset());
3256 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
3257 && old.pit() == cur[depth].pit();
3260 * (1) If the chars around the old cursor were spaces and the
3261 * paragraph is not in free spacing mode, delete some of them, but
3262 * only if the cursor has really moved.
3265 /* There are still some small problems that can lead to
3266 double spaces stored in the document file or space at
3267 the beginning of paragraphs(). This happens if you have
3268 the cursor between two spaces and then save. Or if you
3269 cut and paste and the selection has a space at the
3270 beginning and then save right after the paste. (Lgb)
3272 if (!oldpar.isFreeSpacing()) {
3273 // find range of spaces around cursors
3274 pos_type from = old.pos();
3276 && oldpar.isLineSeparator(from - 1)
3277 && !oldpar.isDeleted(from - 1))
3279 pos_type to = old.pos();
3280 while (to < old.lastpos()
3281 && oldpar.isLineSeparator(to)
3282 && !oldpar.isDeleted(to))
3285 int num_spaces = to - from;
3286 // If we are not at the start of the paragraph, keep one space
3287 if (from != to && from > 0)
3290 // If cursor is inside range, keep one additional space
3291 if (same_par && cur.pos() > from && cur.pos() < to)
3294 // Remove spaces and adapt cursor.
3295 if (num_spaces > 0) {
3298 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
3299 // correct cur position
3300 // FIXME: there can be other cursors pointing there, we should update them
3302 if (cur[depth].pos() >= to)
3303 cur[depth].pos() -= deleted;
3304 else if (cur[depth].pos() > from)
3305 cur[depth].pos() = min(from + 1, old.lastpos());
3306 need_anchor_change = true;
3313 * (2) If the paragraph where the cursor was is empty, delete it
3316 // only do our other magic if we changed paragraph
3320 // only do our magic if the paragraph is empty
3321 if (!oldpar.empty())
3324 // don't delete anything if this is the ONLY paragraph!
3325 if (old.lastpit() == 0)
3328 // Do not delete empty paragraphs with keepempty set.
3329 if (oldpar.allowEmpty())
3333 old.recordUndo(max(old.pit() - 1, pit_type(0)),
3334 min(old.pit() + 1, old.lastpit()));
3335 ParagraphList & plist = old.text()->paragraphs();
3336 bool const soa = oldpar.params().startOfAppendix();
3337 plist.erase(plist.iterator_at(old.pit()));
3338 // do not lose start of appendix marker (bug 4212)
3339 if (soa && old.pit() < pit_type(plist.size()))
3340 plist[old.pit()].params().startOfAppendix(true);
3342 // see #warning (FIXME?) above
3343 if (cur.depth() >= old.depth()) {
3344 CursorSlice & curslice = cur[old.depth() - 1];
3345 if (&curslice.inset() == &old.inset()
3346 && curslice.idx() == old.idx()
3347 && curslice.pit() > old.pit()) {
3349 // since a paragraph has been deleted, all the
3350 // insets after `old' have been copied and
3351 // their address has changed. Therefore we
3352 // need to `regenerate' cur. (JMarc)
3353 cur.updateInsets(&(cur.bottom().inset()));
3354 need_anchor_change = true;
3362 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
3364 pos_type last_pos = pars_[last].size() - 1;
3365 deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
3369 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
3370 pos_type first_pos, pos_type last_pos,
3373 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
3375 for (pit_type pit = first; pit <= last; ++pit) {
3376 Paragraph & par = pars_[pit];
3379 * (1) Delete consecutive spaces
3381 if (!par.isFreeSpacing()) {
3382 pos_type from = (pit == first) ? first_pos : 0;
3383 pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
3384 while (from < to_pos) {
3386 while (from < par.size()
3387 && (!par.isLineSeparator(from) || par.isDeleted(from)))
3389 // find string of spaces
3391 while (to < par.size()
3392 && par.isLineSeparator(to) && !par.isDeleted(to))
3394 // empty? We are done
3398 int num_spaces = to - from;
3400 // If we are not at the extremity of the paragraph, keep one space
3401 if (from != to && from > 0 && to < par.size())
3404 // Remove spaces if needed
3405 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
3406 from = to - deleted;
3411 * (2) Delete empty pragraphs
3414 // don't delete anything if this is the only remaining paragraph
3415 // within the given range. Note: Text::acceptOrRejectChanges()
3416 // sets the cursor to 'first' after calling DEPM
3420 // don't delete empty paragraphs with keepempty set
3421 if (par.allowEmpty())
3424 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
3425 pars_.erase(pars_.iterator_at(pit));
3437 typedef limited_stack<pair<docstring, Font>> FontStack;
3438 static FontStack freeFonts(15);
3439 static bool toggleall = false;
3441 void toggleAndShow(Cursor & cur, Text * text,
3442 Font const & font, bool togall = true)
3444 text->toggleFree(cur, font, togall);
3446 if (font.language() != ignore_language ||
3447 font.fontInfo().number() != FONT_IGNORE) {
3448 TextMetrics const & tm = cur.bv().textMetrics(text);
3449 if (cur.boundary() != tm.isRTLBoundary(cur.pit(), cur.pos(),
3450 cur.real_current_font))
3451 text->setCursor(cur, cur.pit(), cur.pos(),
3452 false, !cur.boundary());
3453 if (font.language() != ignore_language)
3454 // We need a buffer update if we change the language
3455 // (e.g., with info insets or if the selection contains
3457 cur.forceBufferUpdate();
3462 void moveCursor(Cursor & cur, bool selecting)
3464 if (selecting || cur.mark())
3469 void finishChange(Cursor & cur, bool selecting)
3472 moveCursor(cur, selecting);
3476 void mathDispatch(Cursor & cur, FuncRequest const & cmd)
3479 docstring sel = cur.selectionAsString(false);
3481 // It may happen that sel is empty but there is a selection
3482 replaceSelection(cur);
3484 // Is this a valid formula?
3488 #ifdef ENABLE_ASSERTIONS
3489 const int old_pos = cur.pos();
3491 cur.insert(new InsetMathHull(cur.buffer(), hullSimple));
3492 #ifdef ENABLE_ASSERTIONS
3493 LATTEST(old_pos == cur.pos());
3495 cur.nextInset()->edit(cur, true);
3496 if (cmd.action() != LFUN_MATH_MODE)
3497 // LFUN_MATH_MODE has a different meaning in math mode
3500 InsetMathHull * formula = new InsetMathHull(cur.buffer());
3501 string const selstr = to_utf8(sel);
3502 istringstream is(selstr);
3505 if (!formula->readQuiet(lex)) {
3506 // No valid formula, let's try with delims
3507 is.str("$" + selstr + "$");
3509 if (!formula->readQuiet(lex)) {
3510 // Still not valid, leave it as is
3517 cur.insert(formula);
3518 cur.nextInset()->edit(cur, true);
3519 LASSERT(cur.inMathed(), return);
3522 cur.selection(true);
3523 cur.pos() = cur.lastpos();
3524 if (cmd.action() != LFUN_MATH_MODE)
3525 // LFUN_MATH_MODE has a different meaning in math mode
3527 cur.clearSelection();
3528 cur.pos() = cur.lastpos();
3532 cur.message(from_utf8(N_("Math editor mode")));
3534 cur.message(from_utf8(N_("No valid math formula")));
3538 void regexpDispatch(Cursor & cur, FuncRequest const & cmd)
3540 LASSERT(cmd.action() == LFUN_REGEXP_MODE, return);
3541 if (cur.inRegexped()) {
3542 cur.message(_("Already in regular expression mode"));
3546 docstring sel = cur.selectionAsString(false);
3548 // It may happen that sel is empty but there is a selection
3549 replaceSelection(cur);
3551 cur.insert(new InsetMathHull(cur.buffer(), hullRegexp));
3552 cur.nextInset()->edit(cur, true);
3553 cur.niceInsert(sel);
3555 cur.message(_("Regexp editor mode"));
3559 void specialChar(Cursor & cur, InsetSpecialChar::Kind kind)
3562 cap::replaceSelection(cur);
3563 cur.insert(new InsetSpecialChar(kind));
3568 void ipaChar(Cursor & cur, InsetIPAChar::Kind kind)
3571 cap::replaceSelection(cur);
3572 cur.insert(new InsetIPAChar(kind));
3577 bool doInsertInset(Cursor & cur, Text * text,
3578 FuncRequest const & cmd, bool edit,
3579 bool pastesel, bool resetfont = false)
3581 Buffer & buffer = cur.bv().buffer();
3582 BufferParams const & bparams = buffer.params();
3583 Inset * inset = createInset(&buffer, cmd);
3587 if (InsetCollapsible * ci = inset->asInsetCollapsible())
3588 ci->setButtonLabel();
3591 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3592 bool cotextinsert = false;
3593 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3594 Layout const & lay = cur.paragraph().layout();
3595 Layout::LaTeXArgMap args = lay.args();
3596 Layout::LaTeXArgMap::const_iterator const lait = args.find(ia->name());
3597 if (lait != args.end())
3598 cotextinsert = (*lait).second.insertcotext;
3600 InsetLayout const & il = cur.inset().getLayout();
3602 Layout::LaTeXArgMap::const_iterator const ilait = args.find(ia->name());
3603 if (ilait != args.end())
3604 cotextinsert = (*ilait).second.insertcotext;
3606 // The argument requests to insert a copy of the co-text to the inset
3609 // If we have a selection within a paragraph, use this
3610 if (cur.selection() && cur.selBegin().pit() == cur.selEnd().pit())
3611 ds = cur.selectionAsString(false);
3612 // else use the whole paragraph
3614 ds = cur.paragraph().asString();
3615 text->insertInset(cur, inset);
3616 ia->init(cur.paragraph());
3618 inset->edit(cur, true);
3619 // Now put co-text into inset
3620 Font const f(inherit_font, cur.current_font.language());
3622 cur.text()->insertStringAsLines(cur, ds, f);
3623 cur.leaveInset(*inset);
3629 bool gotsel = false;
3630 bool move_layout = false;
3631 if (cur.selection()) {
3632 if (cmd.action() == LFUN_INDEX_INSERT)
3633 copySelectionToTemp(cur);
3635 cutSelectionToTemp(cur, pastesel);
3636 /* Move layout information inside the inset if the whole
3637 * paragraph and the inset allows setting layout
3638 * FIXME: this does not work as expected when change tracking is on
3639 * However, we do not really know what to do in this case.
3640 * FIXME: figure out a good test in the environment case (see #12251).
3642 if (cur.paragraph().layout().isCommand()
3643 && cur.paragraph().empty()
3644 && !inset->forcePlainLayout()) {
3645 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3649 cur.clearSelection();
3651 } else if (cmd.action() == LFUN_INDEX_INSERT) {
3652 gotsel = text->selectWordWhenUnderCursor(cur, WHOLE_WORD);
3653 copySelectionToTemp(cur);
3654 cur.clearSelection();
3656 text->insertInset(cur, inset);
3658 InsetText * inset_text = inset->asInsetText();
3660 Font const & font = inset->inheritFont()
3661 ? cur.bv().textMetrics(text).displayFont(cur.pit(), cur.pos())
3662 : bparams.getFont();
3663 inset_text->setOuterFont(cur.bv(), font.fontInfo());
3666 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3667 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3668 ia->init(cur.paragraph());
3672 inset->edit(cur, true);
3674 if (!gotsel || !pastesel)
3677 pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
3678 cur.buffer()->errors("Paste");
3679 cur.clearSelection(); // bug 393
3683 // Reset of font (not language) is requested.
3684 // Used by InsetIndex (#11961).
3685 Language const * lang = cur.getFont().language();
3686 Font font(bparams.getFont().fontInfo(), lang);
3687 cur.paragraph().resetFonts(font);
3689 inset_text->fixParagraphsFont();
3692 /* If the containing paragraph has kept its layout, reset the
3693 * layout of the first paragraph of the inset.
3696 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3697 // FIXME: what does this do?
3698 if (cmd.action() == LFUN_FLEX_INSERT)
3701 cur.leaveInset(*inset);
3702 if (cmd.action() == LFUN_PREVIEW_INSERT
3703 || cmd.action() == LFUN_IPA_INSERT)
3705 notifyCursorLeavesOrEnters(old, cur);
3707 cur.leaveInset(*inset);
3708 // reset surrounding par to default
3709 DocumentClass const & dc = bparams.documentClass();
3710 docstring const layoutname = inset->usePlainLayout()
3711 ? dc.plainLayoutName()
3712 : dc.defaultLayoutName();
3713 text->setLayout(cur, layoutname);
3719 /// the type of outline operation
3721 OutlineUp, // Move this header with text down
3722 OutlineDown, // Move this header with text up
3723 OutlineIn, // Make this header deeper
3724 OutlineOut // Make this header shallower
3728 void insertSeparator(Cursor const & cur, depth_type const depth)
3730 Buffer & buf = *cur.buffer();
3731 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
3732 DocumentClass const & tc = buf.params().documentClass();
3733 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
3734 + from_ascii("\" ignoreautonests")));
3735 // FIXME: Bibitem mess!
3736 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
3737 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
3738 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
3739 while (cur.paragraph().params().depth() > depth)
3740 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
3744 void outline(OutlineOp mode, Cursor & cur, Text * text)
3746 Buffer & buf = *cur.buffer();
3747 pit_type & pit = cur.pit();
3748 ParagraphList & pars = buf.text().paragraphs();
3749 ParagraphList::iterator const bgn = pars.begin();
3750 // The first paragraph of the area to be copied:
3751 ParagraphList::iterator start = pars.iterator_at(pit);
3752 // The final paragraph of area to be copied:
3753 ParagraphList::iterator finish = start;
3754 ParagraphList::iterator const end = pars.end();
3755 depth_type const current_depth = cur.paragraph().params().depth();
3757 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
3760 // Move out (down) from this section header
3764 // Seek the one (on same level) below
3765 for (; finish != end; ++finish) {
3766 toclevel = buf.text().getTocLevel(distance(bgn, finish));
3767 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
3773 if (start == pars.begin())
3776 ParagraphList::iterator dest = start;
3777 // Move out (up) from this header
3780 // Search previous same-level header above
3783 toclevel = buf.text().getTocLevel(distance(bgn, dest));
3785 && (toclevel == Layout::NOT_IN_TOC
3786 || toclevel > thistoclevel));
3787 // Not found; do nothing
3788 if (toclevel == Layout::NOT_IN_TOC || toclevel > thistoclevel)
3790 pit_type newpit = distance(bgn, dest);
3791 pit_type const len = distance(start, finish);
3792 pit_type const deletepit = pit + len;
3793 buf.undo().recordUndo(cur, newpit, deletepit - 1);
3794 // If we move an environment upwards, make sure it is
3795 // separated from its new neighbour below:
3796 // If an environment of the same layout follows, and the moved
3797 // paragraph sequence does not end with a separator, insert one.
3798 ParagraphList::iterator lastmoved = finish;
3800 if (start->layout().isEnvironment()
3801 && dest->layout() == start->layout()
3802 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3803 cur.pit() = distance(bgn, lastmoved);
3804 cur.pos() = cur.lastpos();
3805 insertSeparator(cur, current_depth);
3808 // Likewise, if we moved an environment upwards, make sure it
3809 // is separated from its new neighbour above.
3810 // The paragraph before the target of movement
3812 ParagraphList::iterator before = dest;
3814 // Get the parent paragraph (outer in nested context)
3815 pit_type const parent =
3816 before->params().depth() > current_depth
3817 ? text->depthHook(distance(bgn, before), current_depth)
3818 : distance(bgn, before);
3819 // If a environment with same layout preceeds the moved one in the new
3820 // position, and there is no separator yet, insert one.
3821 if (start->layout().isEnvironment()
3822 && pars[parent].layout() == start->layout()
3823 && !before->isEnvSeparator(before->beginOfBody())) {
3824 cur.pit() = distance(bgn, before);
3825 cur.pos() = cur.lastpos();
3826 insertSeparator(cur, current_depth);
3830 newpit = distance(bgn, dest);
3831 pars.splice(dest, start, finish);
3839 // Go one down from *this* header:
3840 ParagraphList::iterator dest = next(finish, 1);
3841 // Go further down to find header to insert in front of:
3842 for (; dest != end; ++dest) {
3843 toclevel = buf.text().getTocLevel(distance(bgn, dest));
3844 if (toclevel != Layout::NOT_IN_TOC
3845 && toclevel <= thistoclevel)
3848 // One such was found, so go on...
3849 // If we move an environment downwards, make sure it is
3850 // separated from its new neighbour above.
3851 pit_type newpit = distance(bgn, dest);
3852 buf.undo().recordUndo(cur, pit, newpit - 1);
3853 // The paragraph before the target of movement
3854 ParagraphList::iterator before = dest;
3856 // Get the parent paragraph (outer in nested context)
3857 pit_type const parent =
3858 before->params().depth() > current_depth
3859 ? text->depthHook(distance(bgn, before), current_depth)
3860 : distance(bgn, before);
3861 // If a environment with same layout preceeds the moved one in the new
3862 // position, and there is no separator yet, insert one.
3863 if (start->layout().isEnvironment()
3864 && pars[parent].layout() == start->layout()
3865 && !before->isEnvSeparator(before->beginOfBody())) {
3866 cur.pit() = distance(bgn, before);
3867 cur.pos() = cur.lastpos();
3868 insertSeparator(cur, current_depth);
3871 // Likewise, make sure moved environments are separated
3872 // from their new neighbour below:
3873 // If an environment of the same layout follows, and the moved
3874 // paragraph sequence does not end with a separator, insert one.
3875 ParagraphList::iterator lastmoved = finish;
3878 && start->layout().isEnvironment()
3879 && dest->layout() == start->layout()
3880 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3881 cur.pit() = distance(bgn, lastmoved);
3882 cur.pos() = cur.lastpos();
3883 insertSeparator(cur, current_depth);
3886 newpit = distance(bgn, dest);
3887 pit_type const len = distance(start, finish);
3888 pars.splice(dest, start, finish);
3889 cur.pit() = newpit - len;
3894 // We first iterate without actually doing something
3895 // in order to check whether the action flattens the structure.
3896 // If so, warn (#11178).
3897 ParagraphList::iterator cstart = start;
3898 bool strucchange = false;
3899 for (; cstart != finish; ++cstart) {
3900 toclevel = buf.text().getTocLevel(distance(bgn, cstart));
3901 if (toclevel == Layout::NOT_IN_TOC)
3904 DocumentClass const & tc = buf.params().documentClass();
3905 int const newtoclevel =
3906 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3909 for (auto const & lay : tc) {
3910 if (lay.toclevel == newtoclevel
3911 && lay.isNumHeadingLabelType()
3912 && cstart->layout().isNumHeadingLabelType()) {
3923 && frontend::Alert::prompt(_("Action flattens document structure"),
3924 _("This action will cause some headings that have been "
3925 "on different level before to be on the same level "
3926 "since there is no more lower or higher heading level. "
3929 _("&Yes, continue nonetheless"),
3930 _("&No, quit operation")) == 1)
3933 pit_type const len = distance(start, finish);
3934 buf.undo().recordUndo(cur, pit, pit + len - 1);
3935 for (; start != finish; ++start) {
3936 toclevel = buf.text().getTocLevel(distance(bgn, start));
3937 if (toclevel == Layout::NOT_IN_TOC)
3940 DocumentClass const & tc = buf.params().documentClass();
3941 int const newtoclevel =
3942 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3944 for (auto const & lay : tc) {
3945 if (lay.toclevel == newtoclevel
3946 && lay.isNumHeadingLabelType()
3947 && start->layout().isNumHeadingLabelType()) {
3948 start->setLayout(lay);
3962 void Text::number(Cursor & cur)
3964 FontInfo font = ignore_font;
3965 font.setNumber(FONT_TOGGLE);
3966 toggleAndShow(cur, this, Font(font, ignore_language));
3970 bool Text::isRTL(pit_type const pit) const
3972 Buffer const & buffer = owner_->buffer();
3973 return pars_[pit].isRTL(buffer.params());
3979 Language const * getLanguage(Cursor const & cur, string const & lang)
3981 return lang.empty() ? cur.getFont().language() : languages.getLanguage(lang);
3985 docstring resolveLayout(docstring layout, DocIterator const & dit)
3987 Paragraph const & par = dit.paragraph();
3988 DocumentClass const & tclass = dit.buffer()->params().documentClass();
3991 layout = tclass.defaultLayoutName();
3993 if (dit.inset().forcePlainLayout(dit.idx()))
3994 // in this case only the empty layout is allowed
3995 layout = tclass.plainLayoutName();
3996 else if (par.usePlainLayout()) {
3997 // in this case, default layout maps to empty layout
3998 if (layout == tclass.defaultLayoutName())
3999 layout = tclass.plainLayoutName();
4001 // otherwise, the empty layout maps to the default
4002 if (layout == tclass.plainLayoutName())
4003 layout = tclass.defaultLayoutName();
4006 // If the entry is obsolete, use the new one instead.
4007 if (tclass.hasLayout(layout)) {
4008 docstring const & obs = tclass[layout].obsoleted_by();
4012 if (!tclass.hasLayout(layout))
4018 bool isAlreadyLayout(docstring const & layout, CursorData const & cur)
4020 ParagraphList const & pars = cur.text()->paragraphs();
4022 pit_type pit = cur.selBegin().pit();
4023 pit_type const epit = cur.selEnd().pit() + 1;
4024 for ( ; pit != epit; ++pit)
4025 if (pars[pit].layout().name() != layout)
4035 void Text::dispatch(Cursor & cur, FuncRequest & cmd)
4037 LYXERR(Debug::ACTION, "Text::dispatch: cmd: " << cmd);
4039 // Dispatch if the cursor is inside the text. It is not the
4040 // case for context menus (bug 5797).
4041 if (cur.text() != this) {
4046 BufferView * bv = &cur.bv();
4047 TextMetrics * tm = &bv->textMetrics(this);
4048 if (!tm->contains(cur.pit())) {
4049 lyx::dispatch(FuncRequest(LFUN_SCREEN_SHOW_CURSOR));
4050 tm = &bv->textMetrics(this);
4053 // FIXME: We use the update flag to indicates wether a singlePar or a
4054 // full screen update is needed. We reset it here but shall we restore it
4056 cur.noScreenUpdate();
4058 LBUFERR(this == cur.text());
4060 // NOTE: This should NOT be a reference. See commit 94a5481a.
4061 CursorSlice const oldTopSlice = cur.top();
4062 bool const oldBoundary = cur.boundary();
4063 bool const oldSelection = cur.selection();
4064 // Signals that, even if needsUpdate == false, an update of the
4065 // cursor paragraph is required
4066 bool singleParUpdate = lyxaction.funcHasFlag(cmd.action(),
4067 LyXAction::SingleParUpdate);
4068 // Signals that a full-screen update is required
4069 bool needsUpdate = !(lyxaction.funcHasFlag(cmd.action(),
4070 LyXAction::NoUpdate) || singleParUpdate);
4071 bool const last_misspelled = lyxrc.spellcheck_continuously
4072 && cur.paragraph().isMisspelled(cur.pos(), true);
4074 FuncCode const act = cmd.action();
4077 case LFUN_PARAGRAPH_MOVE_DOWN: {
4078 pit_type const pit = cur.pit();
4079 cur.recordUndo(pit, pit + 1);
4080 pars_.swap(pit, pit + 1);
4082 cur.forceBufferUpdate();
4087 case LFUN_PARAGRAPH_MOVE_UP: {
4088 pit_type const pit = cur.pit();
4089 cur.recordUndo(pit - 1, pit);
4091 pars_.swap(pit, pit - 1);
4094 cur.forceBufferUpdate();
4098 case LFUN_APPENDIX: {
4099 Paragraph & par = cur.paragraph();
4100 bool start = !par.params().startOfAppendix();
4102 // FIXME: The code below only makes sense at top level.
4103 // Should LFUN_APPENDIX be restricted to top-level paragraphs?
4104 // ensure that we have only one start_of_appendix in this document
4105 // FIXME: this don't work for multipart document!
4106 for (pit_type tmp = 0, end = pars_.size(); tmp != end; ++tmp) {
4107 if (pars_[tmp].params().startOfAppendix()) {
4108 cur.recordUndo(tmp, tmp);
4109 pars_[tmp].params().startOfAppendix(false);
4115 par.params().startOfAppendix(start);
4117 // we can set the refreshing parameters now
4118 cur.forceBufferUpdate();
4122 case LFUN_WORD_DELETE_FORWARD:
4123 if (cur.selection())
4124 cutSelection(cur, false);
4126 deleteWordForward(cur, cmd.getArg(0) != "confirm");
4127 finishChange(cur, false);
4130 case LFUN_WORD_DELETE_BACKWARD:
4131 if (cur.selection())
4132 cutSelection(cur, false);
4134 deleteWordBackward(cur, cmd.getArg(0) != "confirm");
4135 finishChange(cur, false);
4138 case LFUN_LINE_DELETE_FORWARD:
4139 if (cur.selection())
4140 cutSelection(cur, false);
4142 tm->deleteLineForward(cur);
4143 finishChange(cur, false);
4146 case LFUN_BUFFER_BEGIN:
4147 case LFUN_BUFFER_BEGIN_SELECT:
4148 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_BEGIN_SELECT);
4149 if (cur.depth() == 1)
4150 needsUpdate |= cursorTop(cur);
4153 cur.screenUpdateFlags(Update::FitCursor);
4156 case LFUN_BUFFER_END:
4157 case LFUN_BUFFER_END_SELECT:
4158 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_END_SELECT);
4159 if (cur.depth() == 1)
4160 needsUpdate |= cursorBottom(cur);
4163 cur.screenUpdateFlags(Update::FitCursor);
4166 case LFUN_INSET_BEGIN:
4167 case LFUN_INSET_BEGIN_SELECT:
4168 needsUpdate |= cur.selHandle(act == LFUN_INSET_BEGIN_SELECT);
4169 if (cur.depth() == 1 || !cur.top().at_begin())
4170 needsUpdate |= cursorTop(cur);
4173 cur.screenUpdateFlags(Update::FitCursor);
4176 case LFUN_INSET_END:
4177 case LFUN_INSET_END_SELECT:
4178 needsUpdate |= cur.selHandle(act == LFUN_INSET_END_SELECT);
4179 if (cur.depth() == 1 || !cur.top().at_end())
4180 needsUpdate |= cursorBottom(cur);
4183 cur.screenUpdateFlags(Update::FitCursor);
4186 case LFUN_CHAR_FORWARD:
4187 case LFUN_CHAR_FORWARD_SELECT: {
4188 //LYXERR0(" LFUN_CHAR_FORWARD[SEL]:\n" << cur);
4189 needsUpdate |= cur.selHandle(act == LFUN_CHAR_FORWARD_SELECT);
4190 bool const cur_moved = cursorForward(cur);
4191 needsUpdate |= cur_moved;
4193 if (!cur_moved && cur.depth() > 1
4194 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4196 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4198 // we will be moving out the inset, so we should execute
4199 // the depm-mechanism.
4200 // The cursor hasn't changed yet. To give the DEPM the
4201 // possibility of doing something we must provide it with
4202 // two different cursors.
4204 dummy.pos() = dummy.pit() = 0;
4205 if (cur.bv().checkDepm(dummy, cur))
4206 cur.forceBufferUpdate();
4211 case LFUN_CHAR_BACKWARD:
4212 case LFUN_CHAR_BACKWARD_SELECT: {
4213 //lyxerr << "handle LFUN_CHAR_BACKWARD[_SELECT]:\n" << cur << endl;
4214 needsUpdate |= cur.selHandle(act == LFUN_CHAR_BACKWARD_SELECT);
4215 bool const cur_moved = cursorBackward(cur);
4216 needsUpdate |= cur_moved;
4218 if (!cur_moved && cur.depth() > 1
4219 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4221 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4223 // we will be moving out the inset, so we should execute
4224 // the depm-mechanism.
4225 // The cursor hasn't changed yet. To give the DEPM the
4226 // possibility of doing something we must provide it with
4227 // two different cursors.
4229 dummy.pos() = cur.lastpos();
4230 dummy.pit() = cur.lastpit();
4231 if (cur.bv().checkDepm(dummy, cur))
4232 cur.forceBufferUpdate();
4237 case LFUN_CHAR_LEFT:
4238 case LFUN_CHAR_LEFT_SELECT:
4239 if (lyxrc.visual_cursor) {
4240 needsUpdate |= cur.selHandle(act == LFUN_CHAR_LEFT_SELECT);
4241 bool const cur_moved = cursorVisLeft(cur);
4242 needsUpdate |= cur_moved;
4243 if (!cur_moved && cur.depth() > 1
4244 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4246 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4249 if (cur.reverseDirectionNeeded()) {
4250 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4251 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4253 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4254 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4261 case LFUN_CHAR_RIGHT:
4262 case LFUN_CHAR_RIGHT_SELECT:
4263 if (lyxrc.visual_cursor) {
4264 needsUpdate |= cur.selHandle(cmd.action() == LFUN_CHAR_RIGHT_SELECT);
4265 bool const cur_moved = cursorVisRight(cur);
4266 needsUpdate |= cur_moved;
4267 if (!cur_moved && cur.depth() > 1
4268 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4270 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4273 if (cur.reverseDirectionNeeded()) {
4274 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4275 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4277 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4278 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4286 case LFUN_UP_SELECT:
4287 case LFUN_DOWN_SELECT:
4290 // stop/start the selection
4291 bool select = cmd.action() == LFUN_DOWN_SELECT ||
4292 cmd.action() == LFUN_UP_SELECT;
4294 // move cursor up/down
4295 bool up = cmd.action() == LFUN_UP_SELECT || cmd.action() == LFUN_UP;
4296 bool const atFirstOrLastRow = cur.atFirstOrLastRow(up);
4298 if (!atFirstOrLastRow) {
4299 needsUpdate |= cur.selHandle(select);
4300 cur.upDownInText(up, needsUpdate);
4301 needsUpdate |= cur.beforeDispatchCursor().inMathed();
4303 pos_type newpos = up ? 0 : cur.lastpos();
4304 if (lyxrc.mac_like_cursor_movement && cur.pos() != newpos) {
4305 needsUpdate |= cur.selHandle(select);
4306 // we do not reset the targetx of the cursor
4308 needsUpdate |= bv->checkDepm(cur, bv->cursor());
4309 cur.updateTextTargetOffset();
4311 cur.forceBufferUpdate();
4315 // if the cursor cannot be moved up or down do not remove
4316 // the selection right now, but wait for the next dispatch.
4318 needsUpdate |= cur.selHandle(select);
4319 cur.upDownInText(up, needsUpdate);
4326 case LFUN_PARAGRAPH_SELECT:
4328 needsUpdate |= setCursor(cur, cur.pit(), 0);
4329 needsUpdate |= cur.selHandle(true);
4330 if (cur.pos() < cur.lastpos())
4331 needsUpdate |= setCursor(cur, cur.pit(), cur.lastpos());
4334 case LFUN_PARAGRAPH_UP:
4335 case LFUN_PARAGRAPH_UP_SELECT:
4336 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_UP_SELECT);
4337 needsUpdate |= cursorUpParagraph(cur);
4340 case LFUN_PARAGRAPH_DOWN:
4341 case LFUN_PARAGRAPH_DOWN_SELECT:
4342 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_DOWN_SELECT);
4343 needsUpdate |= cursorDownParagraph(cur);
4346 case LFUN_LINE_BEGIN:
4347 case LFUN_LINE_BEGIN_SELECT:
4348 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_BEGIN_SELECT);
4349 needsUpdate |= tm->cursorHome(cur);
4353 case LFUN_LINE_END_SELECT:
4354 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_END_SELECT);
4355 needsUpdate |= tm->cursorEnd(cur);
4358 case LFUN_SECTION_SELECT: {
4359 Buffer const & buf = *cur.buffer();
4360 pit_type const pit = cur.pit();
4361 ParagraphList & pars = buf.text().paragraphs();
4362 ParagraphList::iterator bgn = pars.begin();
4363 // The first paragraph of the area to be selected:
4364 ParagraphList::iterator start = pars.iterator_at(pit);
4365 // The final paragraph of area to be selected:
4366 ParagraphList::iterator finish = start;
4367 ParagraphList::iterator end = pars.end();
4369 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
4370 if (thistoclevel == Layout::NOT_IN_TOC)
4374 Cursor const old_cur = cur;
4375 needsUpdate |= cur.selHandle(true);
4377 // Move out (down) from this section header
4381 // Seek the one (on same level) below
4382 for (; finish != end; ++finish, ++cur.pit()) {
4383 int const toclevel = buf.text().getTocLevel(distance(bgn, finish));
4384 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
4387 cur.pos() = cur.lastpos();
4388 cur.boundary(false);
4389 cur.setCurrentFont();
4391 needsUpdate |= cur != old_cur;
4395 case LFUN_WORD_RIGHT:
4396 case LFUN_WORD_RIGHT_SELECT:
4397 if (lyxrc.visual_cursor) {
4398 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_RIGHT_SELECT);
4399 bool const cur_moved = cursorVisRightOneWord(cur);
4400 needsUpdate |= cur_moved;
4401 if (!cur_moved && cur.depth() > 1
4402 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4404 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4407 if (cur.reverseDirectionNeeded()) {
4408 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4409 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4411 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4412 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4419 case LFUN_WORD_FORWARD:
4420 case LFUN_WORD_FORWARD_SELECT: {
4421 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_FORWARD_SELECT);
4422 bool const cur_moved = cursorForwardOneWord(cur);
4423 needsUpdate |= cur_moved;
4425 if (!cur_moved && cur.depth() > 1
4426 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4428 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4430 // we will be moving out the inset, so we should execute
4431 // the depm-mechanism.
4432 // The cursor hasn't changed yet. To give the DEPM the
4433 // possibility of doing something we must provide it with
4434 // two different cursors.
4436 dummy.pos() = dummy.pit() = 0;
4437 if (cur.bv().checkDepm(dummy, cur))
4438 cur.forceBufferUpdate();
4443 case LFUN_WORD_LEFT:
4444 case LFUN_WORD_LEFT_SELECT:
4445 if (lyxrc.visual_cursor) {
4446 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_LEFT_SELECT);
4447 bool const cur_moved = cursorVisLeftOneWord(cur);
4448 needsUpdate |= cur_moved;
4449 if (!cur_moved && cur.depth() > 1
4450 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4452 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4455 if (cur.reverseDirectionNeeded()) {
4456 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4457 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4459 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4460 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4467 case LFUN_WORD_BACKWARD:
4468 case LFUN_WORD_BACKWARD_SELECT: {
4469 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_BACKWARD_SELECT);
4470 bool const cur_moved = cursorBackwardOneWord(cur);
4471 needsUpdate |= cur_moved;
4473 if (!cur_moved && cur.depth() > 1
4474 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4476 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4478 // we will be moving out the inset, so we should execute
4479 // the depm-mechanism.
4480 // The cursor hasn't changed yet. To give the DEPM the
4481 // possibility of doing something we must provide it with
4482 // two different cursors.
4484 dummy.pos() = cur.lastpos();
4485 dummy.pit() = cur.lastpit();
4486 if (cur.bv().checkDepm(dummy, cur))
4487 cur.forceBufferUpdate();
4492 case LFUN_WORD_SELECT: {
4493 selectWord(cur, WHOLE_WORD);
4494 finishChange(cur, true);
4498 case LFUN_NEWLINE_INSERT: {
4499 InsetNewlineParams inp;
4500 docstring const & arg = cmd.argument();
4501 if (arg == "linebreak")
4502 inp.kind = InsetNewlineParams::LINEBREAK;
4504 inp.kind = InsetNewlineParams::NEWLINE;
4505 cap::replaceSelection(cur);
4507 cur.insert(new InsetNewline(inp));
4509 moveCursor(cur, false);
4513 case LFUN_TAB_INSERT: {
4514 bool const multi_par_selection = cur.selection() &&
4515 cur.selBegin().pit() != cur.selEnd().pit();
4516 if (multi_par_selection) {
4517 // If there is a multi-paragraph selection, a tab is inserted
4518 // at the beginning of each paragraph.
4519 cur.recordUndoSelection();
4520 pit_type const pit_end = cur.selEnd().pit();
4521 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4522 pars_[pit].insertChar(0, '\t',
4523 bv->buffer().params().track_changes);
4524 // Update the selection pos to make sure the selection does not
4525 // change as the inserted tab will increase the logical pos.
4526 if (cur.realAnchor().pit() == pit)
4527 cur.realAnchor().forwardPos();
4528 if (cur.pit() == pit)
4533 // Maybe we shouldn't allow tabs within a line, because they
4534 // are not (yet) aligned as one might do expect.
4535 FuncRequest ncmd(LFUN_SELF_INSERT, from_ascii("\t"));
4536 dispatch(cur, ncmd);
4541 case LFUN_TAB_DELETE: {
4542 bool const tc = bv->buffer().params().track_changes;
4543 if (cur.selection()) {
4544 // If there is a selection, a tab (if present) is removed from
4545 // the beginning of each paragraph.
4546 cur.recordUndoSelection();
4547 pit_type const pit_end = cur.selEnd().pit();
4548 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4549 Paragraph & par = paragraphs()[pit];
4552 char_type const c = par.getChar(0);
4553 if (c == '\t' || c == ' ') {
4554 // remove either 1 tab or 4 spaces.
4555 int const n = (c == ' ' ? 4 : 1);
4556 for (int i = 0; i < n
4557 && !par.empty() && par.getChar(0) == c; ++i) {
4558 if (cur.pit() == pit)
4560 if (cur.realAnchor().pit() == pit
4561 && cur.realAnchor().pos() > 0 )
4562 cur.realAnchor().backwardPos();
4563 par.eraseChar(0, tc);
4569 // If there is no selection, try to remove a tab or some spaces
4570 // before the position of the cursor.
4571 Paragraph & par = paragraphs()[cur.pit()];
4572 pos_type const pos = cur.pos();
4577 char_type const c = par.getChar(pos - 1);
4581 par.eraseChar(cur.pos(), tc);
4583 for (int n_spaces = 0;
4585 && par.getChar(cur.pos() - 1) == ' '
4589 par.eraseChar(cur.pos(), tc);
4596 case LFUN_CHAR_DELETE_FORWARD:
4597 if (!cur.selection()) {
4598 if (cur.pos() == cur.paragraph().size())
4599 // Par boundary, force full-screen update
4600 singleParUpdate = false;
4601 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion()) {
4603 cur.selection(true);
4608 needsUpdate |= erase(cur);
4611 cutSelection(cur, false);
4612 cur.setCurrentFont();
4613 singleParUpdate = false;
4615 moveCursor(cur, false);
4618 case LFUN_CHAR_DELETE_BACKWARD:
4619 if (!cur.selection()) {
4620 if (bv->getIntl().getTransManager().backspace()) {
4621 bool par_boundary = cur.pos() == 0;
4622 bool first_par = cur.pit() == 0;
4623 // Par boundary, full-screen update
4625 singleParUpdate = false;
4626 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion(true)) {
4628 cur.selection(true);
4633 needsUpdate |= backspace(cur);
4635 if (par_boundary && !first_par && cur.pos() > 0
4636 && cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
4637 needsUpdate |= backspace(cur);
4642 DocIterator const dit = cur.selectionBegin();
4643 cutSelection(cur, false);
4644 if (cur.buffer()->params().track_changes)
4645 // since we're doing backwards deletion,
4646 // and the selection is not really cut,
4647 // move cursor before selection (#11630)
4649 cur.setCurrentFont();
4650 singleParUpdate = false;
4654 case LFUN_PARAGRAPH_BREAK: {
4655 cap::replaceSelection(cur);
4656 pit_type pit = cur.pit();
4657 Paragraph const & par = pars_[pit];
4658 bool lastpar = (pit == pit_type(pars_.size() - 1));
4659 Paragraph const & nextpar = lastpar ? par : pars_[pit + 1];
4660 pit_type prev = pit > 0 ? depthHook(pit, par.getDepth()) : pit;
4661 if (prev < pit && cur.pos() == par.beginOfBody()
4662 && par.empty() && !par.isEnvSeparator(cur.pos())
4663 && !par.layout().keepempty
4664 && !par.layout().isCommand()
4665 && pars_[prev].layout() != par.layout()
4666 && pars_[prev].layout().isEnvironment()
4667 && !nextpar.isEnvSeparator(nextpar.beginOfBody())) {
4668 if (par.layout().isEnvironment()
4669 && pars_[prev].getDepth() == par.getDepth()) {
4670 docstring const layout = par.layout().name();
4671 DocumentClass const & tc = bv->buffer().params().documentClass();
4672 lyx::dispatch(FuncRequest(LFUN_LAYOUT, tc.plainLayout().name()));
4673 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4674 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
4675 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
4677 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4678 breakParagraph(cur);
4680 Font const f(inherit_font, cur.current_font.language());
4681 pars_[cur.pit() - 1].resetFonts(f);
4683 if (par.isEnvSeparator(cur.pos()) && cmd.getArg(1) != "ignoresep")
4685 breakParagraph(cur, cmd.getArg(0) == "inverse");
4688 // If we have a list and autoinsert item insets,
4690 Layout::LaTeXArgMap args = par.layout().args();
4691 for (auto const & thearg : args) {
4692 Layout::latexarg arg = thearg.second;
4693 if (arg.autoinsert && prefixIs(thearg.first, "item:")) {
4694 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, thearg.first);
4695 lyx::dispatch(cmd2);
4701 case LFUN_INSET_INSERT: {
4704 // We have to avoid triggering InstantPreview loading
4705 // before inserting into the document. See bug #5626.
4706 bool loaded = bv->buffer().isFullyLoaded();
4707 bv->buffer().setFullyLoaded(false);
4708 Inset * inset = createInset(&bv->buffer(), cmd);
4709 bv->buffer().setFullyLoaded(loaded);
4712 // FIXME (Abdel 01/02/2006):
4713 // What follows would be a partial fix for bug 2154:
4714 // http://www.lyx.org/trac/ticket/2154
4715 // This automatically put the label inset _after_ a
4716 // numbered section. It should be possible to extend the mechanism
4717 // to any kind of LateX environement.
4718 // The correct way to fix that bug would be at LateX generation.
4719 // I'll let the code here for reference as it could be used for some
4720 // other feature like "automatic labelling".
4722 Paragraph & par = pars_[cur.pit()];
4723 if (inset->lyxCode() == LABEL_CODE
4724 && !par.layout().counter.empty()) {
4725 // Go to the end of the paragraph
4726 // Warning: Because of Change-Tracking, the last
4727 // position is 'size()' and not 'size()-1':
4728 cur.pos() = par.size();
4729 // Insert a new paragraph
4730 FuncRequest fr(LFUN_PARAGRAPH_BREAK);
4734 if (cur.selection())
4735 cutSelection(cur, false);
4737 cur.forceBufferUpdate();
4738 if (inset->editable() && inset->asInsetText())
4739 inset->edit(cur, true);
4743 // trigger InstantPreview now
4744 if (inset->lyxCode() == EXTERNAL_CODE) {
4745 InsetExternal & ins =
4746 static_cast<InsetExternal &>(*inset);
4747 ins.updatePreview();
4754 case LFUN_INSET_DISSOLVE: {
4755 if (dissolveInset(cur)) {
4757 cur.forceBufferUpdate();
4762 case LFUN_INSET_SPLIT: {
4763 if (splitInset(cur)) {
4765 cur.forceBufferUpdate();
4770 case LFUN_GRAPHICS_SET_GROUP: {
4771 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
4777 string id = to_utf8(cmd.argument());
4778 string grp = graphics::getGroupParams(bv->buffer(), id);
4779 InsetGraphicsParams tmp, inspar = ins->getParams();
4782 inspar.groupId = to_utf8(cmd.argument());
4784 InsetGraphics::string2params(grp, bv->buffer(), tmp);
4785 tmp.filename = inspar.filename;
4789 ins->setParams(inspar);
4793 case LFUN_SPACE_INSERT:
4794 if (cur.paragraph().layout().free_spacing)
4795 insertChar(cur, ' ');
4797 doInsertInset(cur, this, cmd, false, false);
4800 moveCursor(cur, false);
4803 case LFUN_SPECIALCHAR_INSERT: {
4804 string const name = to_utf8(cmd.argument());
4805 if (name == "hyphenation")
4806 specialChar(cur, InsetSpecialChar::HYPHENATION);
4807 else if (name == "allowbreak")
4808 specialChar(cur, InsetSpecialChar::ALLOWBREAK);
4809 else if (name == "ligature-break")
4810 specialChar(cur, InsetSpecialChar::LIGATURE_BREAK);
4811 else if (name == "slash")
4812 specialChar(cur, InsetSpecialChar::SLASH);
4813 else if (name == "nobreakdash")
4814 specialChar(cur, InsetSpecialChar::NOBREAKDASH);
4815 else if (name == "dots")
4816 specialChar(cur, InsetSpecialChar::LDOTS);
4817 else if (name == "end-of-sentence")
4818 specialChar(cur, InsetSpecialChar::END_OF_SENTENCE);
4819 else if (name == "menu-separator")
4820 specialChar(cur, InsetSpecialChar::MENU_SEPARATOR);
4821 else if (name == "lyx")
4822 specialChar(cur, InsetSpecialChar::PHRASE_LYX);
4823 else if (name == "tex")
4824 specialChar(cur, InsetSpecialChar::PHRASE_TEX);
4825 else if (name == "latex")
4826 specialChar(cur, InsetSpecialChar::PHRASE_LATEX);
4827 else if (name == "latex2e")
4828 specialChar(cur, InsetSpecialChar::PHRASE_LATEX2E);
4829 else if (name.empty())
4830 lyxerr << "LyX function 'specialchar-insert' needs an argument." << endl;
4832 lyxerr << "Wrong argument for LyX function 'specialchar-insert'." << endl;
4836 case LFUN_IPAMACRO_INSERT: {
4837 string const arg = cmd.getArg(0);
4838 if (arg == "deco") {
4839 // Open the inset, and move the current selection
4841 doInsertInset(cur, this, cmd, true, true);
4843 // Some insets are numbered, others are shown in the outline pane so
4844 // let's update the labels and the toc backend.
4845 cur.forceBufferUpdate();
4848 if (arg == "tone-falling")
4849 ipaChar(cur, InsetIPAChar::TONE_FALLING);
4850 else if (arg == "tone-rising")
4851 ipaChar(cur, InsetIPAChar::TONE_RISING);
4852 else if (arg == "tone-high-rising")
4853 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING);
4854 else if (arg == "tone-low-rising")
4855 ipaChar(cur, InsetIPAChar::TONE_LOW_RISING);
4856 else if (arg == "tone-high-rising-falling")
4857 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING_FALLING);
4858 else if (arg.empty())
4859 lyxerr << "LyX function 'ipamacro-insert' needs an argument." << endl;
4861 lyxerr << "Wrong argument for LyX function 'ipamacro-insert'." << endl;
4865 case LFUN_WORD_UPCASE:
4866 changeCase(cur, text_uppercase, cmd.getArg(0) == "partial");
4869 case LFUN_WORD_LOWCASE:
4870 changeCase(cur, text_lowercase, cmd.getArg(0) == "partial");
4873 case LFUN_WORD_CAPITALIZE:
4874 changeCase(cur, text_capitalization, cmd.getArg(0) == "partial");
4877 case LFUN_CHARS_TRANSPOSE:
4878 charsTranspose(cur);
4882 cur.message(_("Paste"));
4883 LASSERT(cur.selBegin().idx() == cur.selEnd().idx(), break);
4884 cap::replaceSelection(cur);
4886 // without argument?
4887 string const arg = to_utf8(cmd.argument());
4889 bool tryGraphics = true;
4890 if (theClipboard().isInternal())
4891 pasteFromStack(cur, bv->buffer().errorList("Paste"), 0);
4892 else if (theClipboard().hasTextContents()) {
4893 if (pasteClipboardText(cur, bv->buffer().errorList("Paste"),
4894 !cur.paragraph().parbreakIsNewline(),
4895 Clipboard::AnyTextType))
4896 tryGraphics = false;
4898 if (tryGraphics && theClipboard().hasGraphicsContents())
4899 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"));
4900 } else if (isStrUnsignedInt(arg)) {
4901 // we have a numerical argument
4902 pasteFromStack(cur, bv->buffer().errorList("Paste"),
4903 convert<unsigned int>(arg));
4904 } else if (arg == "html" || arg == "latex") {
4905 Clipboard::TextType type = (arg == "html") ?
4906 Clipboard::HtmlTextType : Clipboard::LaTeXTextType;
4907 pasteClipboardText(cur, bv->buffer().errorList("Paste"), true, type);
4909 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
4911 type = Clipboard::PdfGraphicsType;
4912 else if (arg == "png")
4913 type = Clipboard::PngGraphicsType;
4914 else if (arg == "jpeg")
4915 type = Clipboard::JpegGraphicsType;
4916 else if (arg == "linkback")
4917 type = Clipboard::LinkBackGraphicsType;
4918 else if (arg == "emf")
4919 type = Clipboard::EmfGraphicsType;
4920 else if (arg == "wmf")
4921 type = Clipboard::WmfGraphicsType;
4923 // we also check in getStatus()
4924 LYXERR0("Unrecognized graphics type: " << arg);
4926 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"), type);
4929 bv->buffer().errors("Paste");
4930 bv->buffer().updatePreviews(); // bug 11619
4931 cur.clearSelection(); // bug 393
4937 cutSelection(cur, true);
4938 cur.message(_("Cut"));
4941 case LFUN_SERVER_GET_XY:
4942 cur.message(from_utf8(
4943 convert<string>(tm->cursorX(cur.top(), cur.boundary()))
4944 + ' ' + convert<string>(tm->cursorY(cur.top(), cur.boundary()))));
4947 case LFUN_SERVER_SET_XY: {
4950 istringstream is(to_utf8(cmd.argument()));
4953 lyxerr << "SETXY: Could not parse coordinates in '"
4954 << to_utf8(cmd.argument()) << endl;
4956 tm->setCursorFromCoordinates(cur, x, y);
4960 case LFUN_SERVER_GET_LAYOUT:
4961 cur.message(cur.paragraph().layout().name());
4965 case LFUN_LAYOUT_TOGGLE: {
4966 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
4967 docstring req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
4968 LYXERR(Debug::INFO, "LFUN_LAYOUT: (arg) " << to_utf8(req_layout));
4970 docstring layout = resolveLayout(req_layout, cur);
4971 if (layout.empty()) {
4972 cur.errorMessage(from_utf8(N_("Layout ")) + req_layout +
4973 from_utf8(N_(" not known")));
4977 docstring const old_layout = cur.paragraph().layout().name();
4978 bool change_layout = !isAlreadyLayout(layout, cur);
4980 if (cmd.action() == LFUN_LAYOUT_TOGGLE && !change_layout) {
4981 change_layout = true;
4982 layout = resolveLayout(docstring(), cur);
4985 if (change_layout) {
4986 setLayout(cur, layout);
4987 if (cur.pit() > 0 && !ignoreautonests) {
4988 pit_type prev_pit = cur.pit() - 1;
4989 depth_type const cur_depth = pars_[cur.pit()].getDepth();
4990 // Scan for the previous par on same nesting level
4991 while (prev_pit > 0 && pars_[prev_pit].getDepth() > cur_depth)
4993 set<docstring> const & autonests =
4994 pars_[prev_pit].layout().autonests();
4995 set<docstring> const & autonested =
4996 pars_[cur.pit()].layout().isAutonestedBy();
4997 if (autonests.find(layout) != autonests.end()
4998 || autonested.find(old_layout) != autonested.end())
4999 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5003 DocumentClass const & tclass = bv->buffer().params().documentClass();
5004 bool inautoarg = false;
5005 for (auto const & la_pair : tclass[layout].args()) {
5006 Layout::latexarg const & arg = la_pair.second;
5007 if (arg.autoinsert) {
5008 // If we had already inserted an arg automatically,
5009 // leave this now in order to insert the next one.
5011 cur.leaveInset(cur.inset());
5014 FuncRequest const cmd2(LFUN_ARGUMENT_INSERT, la_pair.first);
5015 lyx::dispatch(cmd2);
5023 case LFUN_ENVIRONMENT_SPLIT: {
5024 bool const outer = cmd.argument() == "outer";
5025 bool const previous = cmd.argument() == "previous";
5026 bool const before = cmd.argument() == "before";
5027 bool const normal = cmd.argument().empty();
5028 Paragraph const & para = cur.paragraph();
5030 if (para.layout().isEnvironment())
5031 layout = para.layout().name();
5032 depth_type split_depth = cur.paragraph().params().depth();
5033 vector<depth_type> nextpars_depth;
5034 if (outer || previous) {
5035 // check if we have an environment in our scope
5036 pit_type pit = cur.pit();
5037 Paragraph cpar = pars_[pit];
5043 if (layout.empty() && previous
5044 && cpar.layout().isEnvironment()
5045 && cpar.params().depth() <= split_depth)
5046 layout = cpar.layout().name();
5047 if (cpar.params().depth() < split_depth
5048 && cpar.layout().isEnvironment()) {
5050 layout = cpar.layout().name();
5051 split_depth = cpar.params().depth();
5053 if (cpar.params().depth() == 0)
5057 if ((outer || normal) && cur.pit() < cur.lastpit()) {
5058 // save nesting of following paragraphs if they are deeper
5060 pit_type offset = 1;
5061 depth_type cur_depth = pars_[cur.pit()].params().depth();
5062 while (cur.pit() + offset <= cur.lastpit()) {
5063 Paragraph cpar = pars_[cur.pit() + offset];
5064 depth_type nextpar_depth = cpar.params().depth();
5065 if (cur_depth <= nextpar_depth && nextpar_depth > 0) {
5066 nextpars_depth.push_back(nextpar_depth);
5067 cur_depth = nextpar_depth;
5074 cur.top().setPitPos(cur.pit(), 0);
5075 if (before || cur.pos() > 0)
5076 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
5077 else if (previous && cur.nextInset() && cur.nextInset()->lyxCode() == SEPARATOR_CODE)
5078 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5080 while (cur.paragraph().params().depth() > split_depth)
5081 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
5083 DocumentClass const & tc = bv->buffer().params().documentClass();
5084 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
5085 + from_ascii("\" ignoreautonests")));
5086 // FIXME: Bibitem mess!
5087 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
5088 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
5089 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
5092 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5093 while (cur.paragraph().params().depth() < split_depth)
5094 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5097 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
5098 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
5099 if ((outer || normal) && !nextpars_depth.empty()) {
5100 // restore nesting of following paragraphs
5101 DocIterator scur = cur;
5102 depth_type max_depth = cur.paragraph().params().depth() + 1;
5103 for (auto nextpar_depth : nextpars_depth) {
5105 while (cur.paragraph().params().depth() < min(nextpar_depth, max_depth)) {
5106 depth_type const olddepth = cur.paragraph().params().depth();
5107 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5108 if (olddepth == cur.paragraph().params().depth())
5109 // leave loop if no incrementation happens
5112 max_depth = cur.paragraph().params().depth() + 1;
5114 cur.setCursor(scur);
5120 case LFUN_CLIPBOARD_PASTE:
5121 cap::replaceSelection(cur);
5122 pasteClipboardText(cur, bv->buffer().errorList("Paste"),
5123 cmd.argument() == "paragraph");
5124 bv->buffer().errors("Paste");
5127 case LFUN_CLIPBOARD_PASTE_SIMPLE:
5128 cap::replaceSelection(cur);
5129 pasteSimpleText(cur, cmd.argument() == "paragraph");
5132 case LFUN_PRIMARY_SELECTION_PASTE:
5133 cap::replaceSelection(cur);
5134 pasteString(cur, theSelection().get(),
5135 cmd.argument() == "paragraph");
5138 case LFUN_SELECTION_PASTE:
5139 // Copy the selection buffer to the clipboard stack,
5140 // because we want it to appear in the "Edit->Paste
5142 cap::replaceSelection(cur);
5143 cap::copySelectionToStack();
5144 cap::pasteSelection(bv->cursor(), bv->buffer().errorList("Paste"));
5145 bv->buffer().errors("Paste");
5148 case LFUN_QUOTE_INSERT: {
5149 cap::replaceSelection(cur);
5152 Paragraph const & par = cur.paragraph();
5153 pos_type pos = cur.pos();
5154 // Ignore deleted text before cursor
5155 while (pos > 0 && par.isDeleted(pos - 1))
5158 bool const inner = (cmd.getArg(0) == "single" || cmd.getArg(0) == "inner");
5160 // Guess quote side.
5161 // A space triggers an opening quote. This is passed if the preceding
5162 // char/inset is a space or at paragraph start.
5164 if (pos > 0 && !par.isSpace(pos - 1)) {
5165 if (cur.prevInset() && cur.prevInset()->lyxCode() == QUOTE_CODE) {
5166 // If an opening double quotation mark precedes, and this
5167 // is a single quote, make it opening as well
5169 static_cast<InsetQuotes &>(*cur.prevInset());
5170 string const type = ins.getType();
5171 if (!suffixIs(type, "ld") || !inner)
5172 c = par.getChar(pos - 1);
5174 else if (!cur.prevInset()
5175 || (cur.prevInset() && cur.prevInset()->isChar()))
5176 // If a char precedes, pass that and let InsetQuote decide
5177 c = par.getChar(pos - 1);
5180 if (par.getInset(pos - 1)
5181 && !par.getInset(pos - 1)->isPartOfTextSequence()) {
5182 // skip "invisible" insets
5186 c = par.getChar(pos - 1);
5191 QuoteLevel const quote_level = inner
5192 ? QuoteLevel::Secondary : QuoteLevel::Primary;
5193 cur.insert(new InsetQuotes(cur.buffer(), c, quote_level, cmd.getArg(1), cmd.getArg(2)));
5194 cur.buffer()->updateBuffer();
5199 case LFUN_MOUSE_TRIPLE:
5200 if (cmd.button() == mouse_button::button1) {
5202 setCursor(cur, cur.pit(), 0);
5205 if (cur.pos() < cur.lastpos())
5206 setCursor(cur, cur.pit(), cur.lastpos());
5212 case LFUN_MOUSE_DOUBLE:
5213 if (cmd.button() == mouse_button::button1) {
5214 selectWord(cur, WHOLE_WORD);
5219 // Single-click on work area
5220 case LFUN_MOUSE_PRESS: {
5221 // We are not marking a selection with the keyboard in any case.
5222 Cursor & bvcur = cur.bv().cursor();
5223 bvcur.setMark(false);
5224 switch (cmd.button()) {
5225 case mouse_button::button1:
5226 if (!bvcur.selection())
5228 bvcur.resetAnchor();
5229 if (!bv->mouseSetCursor(cur, cmd.modifier() == ShiftModifier))
5230 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5231 // FIXME: move this to mouseSetCursor?
5232 if (bvcur.wordSelection() && bvcur.inTexted())
5233 expandWordSel(bvcur);
5236 case mouse_button::button2:
5237 if (lyxrc.mouse_middlebutton_paste) {
5238 // Middle mouse pasting.
5239 bv->mouseSetCursor(cur);
5241 FuncRequest(LFUN_COMMAND_ALTERNATIVES,
5242 "selection-paste ; primary-selection-paste paragraph"));
5244 cur.noScreenUpdate();
5247 case mouse_button::button3: {
5248 // Don't do anything if we right-click a
5249 // selection, a context menu will popup.
5250 if (bvcur.selection() && cur >= bvcur.selectionBegin()
5251 && cur <= bvcur.selectionEnd()) {
5252 cur.noScreenUpdate();
5255 if (!bv->mouseSetCursor(cur, false))
5256 cur.screenUpdateFlags(Update::FitCursor);
5262 } // switch (cmd.button())
5265 case LFUN_MOUSE_MOTION: {
5266 // Mouse motion with right or middle mouse do nothing for now.
5267 if (cmd.button() != mouse_button::button1) {
5268 cur.noScreenUpdate();
5271 // ignore motions deeper nested than the real anchor
5272 Cursor & bvcur = cur.bv().cursor();
5273 if (!bvcur.realAnchor().hasPart(cur)) {
5277 CursorSlice old = bvcur.top();
5279 int const wh = bv->workHeight();
5280 int const y = max(0, min(wh - 1, cmd.y()));
5282 tm->setCursorFromCoordinates(cur, cmd.x(), y);
5283 cur.setTargetX(cmd.x());
5284 // Don't allow selecting a separator inset
5285 if (cur.pos() && cur.paragraph().isEnvSeparator(cur.pos() - 1))
5288 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5289 else if (cmd.y() < 0)
5290 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5291 // This is to allow jumping over large insets
5292 if (cur.top() == old) {
5294 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5295 else if (cmd.y() < 0)
5296 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5298 // We continue with our existing selection or start a new one, so don't
5299 // reset the anchor.
5300 bvcur.setCursor(cur);
5301 if (bvcur.wordSelection() && bvcur.inTexted())
5302 expandWordSel(bvcur);
5303 bvcur.selection(true);
5304 bvcur.setCurrentFont();
5305 if (cur.top() == old) {
5306 // We didn't move one iota, so no need to update the screen.
5307 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5308 //cur.noScreenUpdate();
5314 case LFUN_MOUSE_RELEASE:
5315 switch (cmd.button()) {
5316 case mouse_button::button1:
5317 // Cursor was set at LFUN_MOUSE_PRESS or LFUN_MOUSE_MOTION time.
5318 // If there is a new selection, update persistent selection;
5319 // otherwise, single click does not clear persistent selection
5321 if (cur.selection()) {
5322 // Finish selection. If double click,
5323 // cur is moved to the end of word by
5324 // selectWord but bvcur is current
5326 cur.bv().cursor().setSelection();
5327 // We might have removed an empty but drawn selection
5328 // (probably a margin)
5329 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5331 cur.noScreenUpdate();
5332 // FIXME: We could try to handle drag and drop of selection here.
5335 case mouse_button::button2:
5336 // Middle mouse pasting is handled at mouse press time,
5337 // see LFUN_MOUSE_PRESS.
5338 cur.noScreenUpdate();
5341 case mouse_button::button3:
5342 // Cursor was set at LFUN_MOUSE_PRESS time.
5343 // FIXME: If there is a selection we could try to handle a special
5344 // drag & drop context menu.
5345 cur.noScreenUpdate();
5348 case mouse_button::none:
5349 case mouse_button::button4:
5350 case mouse_button::button5:
5352 } // switch (cmd.button())
5356 case LFUN_SELF_INSERT: {
5357 if (cmd.argument().empty())
5360 // Automatically delete the currently selected
5361 // text and replace it with what is being
5362 // typed in now. Depends on lyxrc settings
5363 // "auto_region_delete", which defaults to
5366 if (lyxrc.auto_region_delete && cur.selection()) {
5367 cutSelection(cur, false);
5368 cur.setCurrentFont();
5370 cur.clearSelection();
5372 for (char_type c : cmd.argument())
5373 bv->translateAndInsert(c, this, cur);
5376 moveCursor(cur, false);
5377 cur.markNewWordPosition();
5378 bv->bookmarkEditPosition();
5382 case LFUN_HREF_INSERT: {
5383 docstring content = cmd.argument();
5384 if (content.empty() && cur.selection())
5385 content = cur.selectionAsString(false);
5387 InsetCommandParams p(HYPERLINK_CODE);
5388 if (!content.empty()){
5389 // if it looks like a link, we'll put it as target,
5390 // otherwise as name (bug #8792).
5393 // regex_match(to_utf8(content), matches, link_re)
5394 // because smatch stores pointers to the substrings rather
5395 // than making copies of them. And those pointers become
5396 // invalid after regex_match returns, since it is then
5397 // being given a temporary object. (Thanks to Georg for
5398 // figuring that out.)
5399 regex const link_re("^([a-z]+):.*");
5401 string const c = to_utf8(lowercase(content));
5403 if (c.substr(0,7) == "mailto:") {
5404 p["target"] = content;
5405 p["type"] = from_ascii("mailto:");
5406 } else if (regex_match(c, matches, link_re)) {
5407 p["target"] = content;
5408 string protocol = matches.str(1);
5409 if (protocol == "file")
5410 p["type"] = from_ascii("file:");
5412 p["name"] = content;
5414 string const data = InsetCommand::params2string(p);
5416 // we need to have a target. if we already have one, then
5417 // that gets used at the default for the name, too, which
5418 // is probably what is wanted.
5419 if (p["target"].empty()) {
5420 bv->showDialog("href", data);
5422 FuncRequest fr(LFUN_INSET_INSERT, data);
5428 case LFUN_LABEL_INSERT: {
5429 InsetCommandParams p(LABEL_CODE);
5430 // Try to generate a valid label
5431 p["name"] = (cmd.argument().empty()) ?
5432 cur.getPossibleLabel() :
5434 string const data = InsetCommand::params2string(p);
5436 if (cmd.argument().empty()) {
5437 bv->showDialog("label", data);
5439 FuncRequest fr(LFUN_INSET_INSERT, data);
5445 case LFUN_INFO_INSERT: {
5446 if (cmd.argument().empty()) {
5447 bv->showDialog("info", cur.current_font.language()->lang());
5450 inset = createInset(cur.buffer(), cmd);
5454 insertInset(cur, inset);
5455 cur.forceBufferUpdate();
5460 case LFUN_CAPTION_INSERT:
5461 case LFUN_FOOTNOTE_INSERT:
5462 case LFUN_NOTE_INSERT:
5463 case LFUN_BOX_INSERT:
5464 case LFUN_BRANCH_INSERT:
5465 case LFUN_PHANTOM_INSERT:
5466 case LFUN_ERT_INSERT:
5467 case LFUN_INDEXMACRO_INSERT:
5468 case LFUN_LISTING_INSERT:
5469 case LFUN_MARGINALNOTE_INSERT:
5470 case LFUN_ARGUMENT_INSERT:
5471 case LFUN_INDEX_INSERT:
5472 case LFUN_PREVIEW_INSERT:
5473 case LFUN_SCRIPT_INSERT:
5474 case LFUN_IPA_INSERT: {
5475 // Indexes reset font formatting (#11961)
5476 bool const resetfont = cmd.action() == LFUN_INDEX_INSERT;
5477 // Open the inset, and move the current selection
5479 doInsertInset(cur, this, cmd, true, true, resetfont);
5481 cur.setCurrentFont();
5482 // Some insets are numbered, others are shown in the outline pane so
5483 // let's update the labels and the toc backend.
5484 cur.forceBufferUpdate();
5488 case LFUN_FLEX_INSERT: {
5489 // Open the inset, and move the current selection
5491 bool const sel = cur.selection();
5492 doInsertInset(cur, this, cmd, true, true);
5493 // Insert auto-insert arguments
5494 bool autoargs = false, inautoarg = false;
5495 Layout::LaTeXArgMap args = cur.inset().getLayout().args();
5496 for (auto const & argt : args) {
5497 Layout::latexarg arg = argt.second;
5498 if (!inautoarg && arg.insertonnewline && cur.pos() > 0) {
5499 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5500 lyx::dispatch(cmd2);
5502 if (arg.autoinsert) {
5503 // The cursor might have been invalidated by the replaceSelection.
5504 cur.buffer()->changed(true);
5505 // If we had already inserted an arg automatically,
5506 // leave this now in order to insert the next one.
5508 cur.leaveInset(cur.inset());
5509 cur.setCurrentFont();
5511 if (arg.insertonnewline && cur.pos() > 0) {
5512 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5513 lyx::dispatch(cmd2);
5516 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, argt.first);
5517 lyx::dispatch(cmd2);
5524 cur.leaveInset(cur.inset());
5527 // Some insets are numbered, others are shown in the outline pane so
5528 // let's update the labels and the toc backend.
5529 cur.forceBufferUpdate();
5533 case LFUN_TABULAR_INSERT: {
5534 // if there were no arguments, just open the dialog
5535 if (cmd.argument().empty()) {
5536 bv->showDialog("tabularcreate");
5538 } else if (cur.buffer()->masterParams().tablestyle != "default"
5539 || bv->buffer().params().documentClass().tablestyle() != "default") {
5540 string tabstyle = cur.buffer()->masterParams().tablestyle;
5541 if (tabstyle == "default")
5542 tabstyle = bv->buffer().params().documentClass().tablestyle();
5543 if (!libFileSearch("tabletemplates", tabstyle + ".lyx").empty()) {
5544 FuncRequest fr(LFUN_TABULAR_STYLE_INSERT,
5545 tabstyle + " " + to_ascii(cmd.argument()));
5549 // Unknown style. Report and fall back to default.
5550 cur.errorMessage(from_utf8(N_("Table Style ")) + from_utf8(tabstyle) +
5551 from_utf8(N_(" not known")));
5553 if (doInsertInset(cur, this, cmd, false, true))
5558 case LFUN_TABULAR_STYLE_INSERT: {
5559 string const style = cmd.getArg(0);
5560 string const rows = cmd.getArg(1);
5561 string const cols = cmd.getArg(2);
5562 if (cols.empty() || !isStrInt(cols)
5563 || rows.empty() || !isStrInt(rows))
5565 int const r = convert<int>(rows);
5566 int const c = convert<int>(cols);
5573 FileName const tabstyle = libFileSearch("tabletemplates",
5574 style + suffix + ".lyx", "lyx");
5575 if (tabstyle.empty())
5577 UndoGroupHelper ugh(cur.buffer());
5579 FuncRequest cmd2(LFUN_FILE_INSERT, tabstyle.absFileName() + " ignorelang");
5580 lyx::dispatch(cmd2);
5584 // move one cell up to middle cell
5586 // add the missing rows
5587 int const addrows = r - 3;
5588 for (int i = 0 ; i < addrows ; ++i) {
5589 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-row");
5593 // add the missing columns
5594 int const addcols = c - 1;
5595 for (int i = 0 ; i < addcols ; ++i) {
5596 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-column");
5605 case LFUN_FLOAT_INSERT:
5606 case LFUN_FLOAT_WIDE_INSERT:
5607 case LFUN_WRAP_INSERT: {
5608 // will some content be moved into the inset?
5609 bool const content = cur.selection();
5610 // does the content consist of multiple paragraphs?
5611 bool const singlepar = (cur.selBegin().pit() == cur.selEnd().pit());
5613 doInsertInset(cur, this, cmd, true, true);
5616 // If some single-par content is moved into the inset,
5617 // doInsertInset puts the cursor outside the inset.
5618 // To insert the caption we put it back into the inset.
5619 // FIXME cleanup doInsertInset to avoid such dances!
5620 if (content && singlepar)
5623 ParagraphList & pars = cur.text()->paragraphs();
5625 DocumentClass const & tclass = bv->buffer().params().documentClass();
5627 // add a separate paragraph for the caption inset
5628 pars.push_back(Paragraph());
5629 pars.back().setInsetOwner(&cur.text()->inset());
5630 pars.back().setPlainOrDefaultLayout(tclass);
5631 int cap_pit = pars.size() - 1;
5633 // if an empty inset was created, we create an additional empty
5634 // paragraph at the bottom so that the user can choose where to put
5635 // the graphics (or table).
5637 pars.push_back(Paragraph());
5638 pars.back().setInsetOwner(&cur.text()->inset());
5639 pars.back().setPlainOrDefaultLayout(tclass);
5642 // reposition the cursor to the caption
5643 cur.pit() = cap_pit;
5645 // FIXME: This Text/Cursor dispatch handling is a mess!
5646 // We cannot use Cursor::dispatch here it needs access to up to
5648 FuncRequest cmd_caption(LFUN_CAPTION_INSERT);
5649 doInsertInset(cur, cur.text(), cmd_caption, true, false);
5650 cur.forceBufferUpdate();
5651 cur.screenUpdateFlags(Update::Force);
5652 // FIXME: When leaving the Float (or Wrap) inset we should
5653 // delete any empty paragraph left above or below the
5658 case LFUN_NOMENCL_INSERT: {
5659 InsetCommandParams p(NOMENCL_CODE);
5660 if (cmd.argument().empty()) {
5662 bv->cursor().innerText()->getStringForDialog(bv->cursor());
5663 cur.clearSelection();
5665 p["symbol"] = cmd.argument();
5666 string const data = InsetCommand::params2string(p);
5667 bv->showDialog("nomenclature", data);
5671 case LFUN_INDEX_PRINT: {
5672 InsetCommandParams p(INDEX_PRINT_CODE);
5673 if (cmd.argument().empty())
5674 p["type"] = from_ascii("idx");
5676 p["type"] = cmd.argument();
5677 string const data = InsetCommand::params2string(p);
5678 FuncRequest fr(LFUN_INSET_INSERT, data);
5683 case LFUN_NOMENCL_PRINT:
5684 case LFUN_NEWPAGE_INSERT:
5686 doInsertInset(cur, this, cmd, false, false);
5690 case LFUN_SEPARATOR_INSERT: {
5691 doInsertInset(cur, this, cmd, false, false);
5693 // remove a following space
5694 Paragraph & par = cur.paragraph();
5695 if (cur.pos() != cur.lastpos() && par.isLineSeparator(cur.pos()))
5696 par.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
5700 case LFUN_DEPTH_DECREMENT:
5701 changeDepth(cur, DEC_DEPTH);
5704 case LFUN_DEPTH_INCREMENT:
5705 changeDepth(cur, INC_DEPTH);
5708 case LFUN_REGEXP_MODE:
5709 regexpDispatch(cur, cmd);
5712 case LFUN_MATH_MODE: {
5713 if (cmd.argument() == "on" || cmd.argument() == "") {
5714 // don't pass "on" as argument
5715 // (it would appear literally in the first cell)
5716 docstring sel = cur.selectionAsString(false);
5717 InsetMathMacroTemplate * macro = new InsetMathMacroTemplate(cur.buffer());
5718 // create a macro template if we see "\\newcommand" somewhere, and
5719 // an ordinary formula otherwise
5721 && (sel.find(from_ascii("\\newcommand")) != string::npos
5722 || sel.find(from_ascii("\\newlyxcommand")) != string::npos
5723 || sel.find(from_ascii("\\def")) != string::npos)
5724 && macro->fromString(sel)) {
5726 replaceSelection(cur);
5729 // no meaningful macro template was found
5731 mathDispatch(cur,FuncRequest(LFUN_MATH_MODE));
5734 // The argument is meaningful
5735 // We replace cmd with LFUN_MATH_INSERT because LFUN_MATH_MODE
5736 // has a different meaning in math mode
5737 mathDispatch(cur, FuncRequest(LFUN_MATH_INSERT,cmd.argument()));
5741 case LFUN_MATH_MACRO:
5742 if (cmd.argument().empty())
5743 cur.errorMessage(from_utf8(N_("Missing argument")));
5746 string s = to_utf8(cmd.argument());
5747 string const s1 = token(s, ' ', 1);
5748 int const nargs = s1.empty() ? 0 : convert<int>(s1);
5749 string const s2 = token(s, ' ', 2);
5750 MacroType type = MacroTypeNewcommand;
5752 type = MacroTypeDef;
5753 InsetMathMacroTemplate * inset = new InsetMathMacroTemplate(cur.buffer(),
5754 from_utf8(token(s, ' ', 0)), nargs, false, type);
5755 inset->setBuffer(bv->buffer());
5756 insertInset(cur, inset);
5758 // enter macro inset and select the name
5760 cur.top().pos() = cur.top().lastpos();
5762 cur.selection(true);
5763 cur.top().pos() = 0;
5767 case LFUN_MATH_DISPLAY:
5768 case LFUN_MATH_SUBSCRIPT:
5769 case LFUN_MATH_SUPERSCRIPT:
5770 case LFUN_MATH_INSERT:
5771 case LFUN_MATH_AMS_MATRIX:
5772 case LFUN_MATH_MATRIX:
5773 case LFUN_MATH_DELIM:
5774 case LFUN_MATH_BIGDELIM:
5775 mathDispatch(cur, cmd);
5778 case LFUN_FONT_EMPH: {
5779 Font font(ignore_font, ignore_language);
5780 font.fontInfo().setEmph(FONT_TOGGLE);
5781 toggleAndShow(cur, this, font);
5785 case LFUN_FONT_ITAL: {
5786 Font font(ignore_font, ignore_language);
5787 font.fontInfo().setShape(ITALIC_SHAPE);
5788 toggleAndShow(cur, this, font);
5792 case LFUN_FONT_BOLD:
5793 case LFUN_FONT_BOLDSYMBOL: {
5794 Font font(ignore_font, ignore_language);
5795 font.fontInfo().setSeries(BOLD_SERIES);
5796 toggleAndShow(cur, this, font);
5800 case LFUN_FONT_NOUN: {
5801 Font font(ignore_font, ignore_language);
5802 font.fontInfo().setNoun(FONT_TOGGLE);
5803 toggleAndShow(cur, this, font);
5807 case LFUN_FONT_TYPEWRITER: {
5808 Font font(ignore_font, ignore_language);
5809 font.fontInfo().setFamily(TYPEWRITER_FAMILY); // no good
5810 toggleAndShow(cur, this, font);
5814 case LFUN_FONT_SANS: {
5815 Font font(ignore_font, ignore_language);
5816 font.fontInfo().setFamily(SANS_FAMILY);
5817 toggleAndShow(cur, this, font);
5821 case LFUN_FONT_ROMAN: {
5822 Font font(ignore_font, ignore_language);
5823 font.fontInfo().setFamily(ROMAN_FAMILY);
5824 toggleAndShow(cur, this, font);
5828 case LFUN_FONT_DEFAULT: {
5829 Font font(inherit_font, ignore_language);
5830 toggleAndShow(cur, this, font);
5834 case LFUN_FONT_STRIKEOUT: {
5835 Font font(ignore_font, ignore_language);
5836 font.fontInfo().setStrikeout(FONT_TOGGLE);
5837 toggleAndShow(cur, this, font);
5841 case LFUN_FONT_CROSSOUT: {
5842 Font font(ignore_font, ignore_language);
5843 font.fontInfo().setXout(FONT_TOGGLE);
5844 toggleAndShow(cur, this, font);
5848 case LFUN_FONT_UNDERUNDERLINE: {
5849 Font font(ignore_font, ignore_language);
5850 font.fontInfo().setUuline(FONT_TOGGLE);
5851 toggleAndShow(cur, this, font);
5855 case LFUN_FONT_UNDERWAVE: {
5856 Font font(ignore_font, ignore_language);
5857 font.fontInfo().setUwave(FONT_TOGGLE);
5858 toggleAndShow(cur, this, font);
5862 case LFUN_FONT_UNDERLINE: {
5863 Font font(ignore_font, ignore_language);
5864 font.fontInfo().setUnderbar(FONT_TOGGLE);
5865 toggleAndShow(cur, this, font);
5869 case LFUN_FONT_NO_SPELLCHECK: {
5870 Font font(ignore_font, ignore_language);
5871 font.fontInfo().setNoSpellcheck(FONT_TOGGLE);
5872 toggleAndShow(cur, this, font);
5876 case LFUN_FONT_SIZE: {
5877 Font font(ignore_font, ignore_language);
5878 setLyXSize(to_utf8(cmd.argument()), font.fontInfo());
5879 toggleAndShow(cur, this, font);
5883 case LFUN_LANGUAGE: {
5884 string const lang_arg = cmd.getArg(0);
5885 bool const reset = (lang_arg.empty() || lang_arg == "reset");
5886 Language const * lang =
5887 reset ? cur.bv().buffer().params().language
5888 : languages.getLanguage(lang_arg);
5889 // we allow reset_language, which is 0, but only if it
5890 // was requested via empty or "reset" arg.
5891 if (!lang && !reset)
5893 bool const toggle = (cmd.getArg(1) != "set");
5894 selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
5895 Font font(ignore_font, lang);
5896 toggleAndShow(cur, this, font, toggle);
5900 case LFUN_TEXTSTYLE_APPLY: {
5901 unsigned int num = 0;
5902 string const arg = to_utf8(cmd.argument());
5905 if (isStrUnsignedInt(arg)) {
5906 num = convert<uint>(arg);
5907 if (num >= freeFonts.size()) {
5908 cur.message(_("Invalid argument (number exceeds stack size)!"));
5912 cur.message(_("Invalid argument (must be a non-negative number)!"));
5916 toggleAndShow(cur, this, freeFonts[num].second, toggleall);
5917 cur.message(bformat(_("Text properties applied: %1$s"), freeFonts[num].first));
5921 // Set the freefont using the contents of \param data dispatched from
5922 // the frontends and apply it at the current cursor location.
5923 case LFUN_TEXTSTYLE_UPDATE: {
5924 Font font(ignore_font, ignore_language);
5926 if (font.fromString(to_utf8(cmd.argument()), toggle)) {
5927 docstring const props = font.stateText(&bv->buffer().params(), true);
5928 freeFonts.push(make_pair(props, font));
5930 toggleAndShow(cur, this, font, toggleall);
5931 cur.message(bformat(_("Text properties applied: %1$s"), props));
5933 LYXERR0("Invalid argument of textstyle-update");
5937 case LFUN_FINISHED_LEFT:
5938 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_LEFT:\n" << cur);
5939 // We're leaving an inset, going left. If the inset is LTR, we're
5940 // leaving from the front, so we should not move (remain at --- but
5941 // not in --- the inset). If the inset is RTL, move left, without
5942 // entering the inset itself; i.e., move to after the inset.
5943 if (cur.paragraph().getFontSettings(
5944 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5945 cursorVisLeft(cur, true);
5948 case LFUN_FINISHED_RIGHT:
5949 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_RIGHT:\n" << cur);
5950 // We're leaving an inset, going right. If the inset is RTL, we're
5951 // leaving from the front, so we should not move (remain at --- but
5952 // not in --- the inset). If the inset is LTR, move right, without
5953 // entering the inset itself; i.e., move to after the inset.
5954 if (!cur.paragraph().getFontSettings(
5955 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5956 cursorVisRight(cur, true);
5959 case LFUN_FINISHED_BACKWARD:
5960 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_BACKWARD:\n" << cur);
5961 cur.setCurrentFont();
5964 case LFUN_FINISHED_FORWARD:
5965 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_FORWARD:\n" << cur);
5967 cur.setCurrentFont();
5970 case LFUN_LAYOUT_PARAGRAPH: {
5972 params2string(cur.paragraph(), data);
5973 data = "show\n" + data;
5974 bv->showDialog("paragraph", data);
5978 case LFUN_PARAGRAPH_UPDATE: {
5980 params2string(cur.paragraph(), data);
5982 // Will the paragraph accept changes from the dialog?
5984 cur.inset().allowParagraphCustomization(cur.idx());
5986 data = "update " + convert<string>(accept) + '\n' + data;
5987 bv->updateDialog("paragraph", data);
5991 case LFUN_ACCENT_UMLAUT:
5992 case LFUN_ACCENT_CIRCUMFLEX:
5993 case LFUN_ACCENT_GRAVE:
5994 case LFUN_ACCENT_ACUTE:
5995 case LFUN_ACCENT_TILDE:
5996 case LFUN_ACCENT_PERISPOMENI:
5997 case LFUN_ACCENT_CEDILLA:
5998 case LFUN_ACCENT_MACRON:
5999 case LFUN_ACCENT_DOT:
6000 case LFUN_ACCENT_UNDERDOT:
6001 case LFUN_ACCENT_UNDERBAR:
6002 case LFUN_ACCENT_CARON:
6003 case LFUN_ACCENT_BREVE:
6004 case LFUN_ACCENT_TIE:
6005 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
6006 case LFUN_ACCENT_CIRCLE:
6007 case LFUN_ACCENT_OGONEK:
6008 theApp()->handleKeyFunc(cmd.action());
6009 if (!cmd.argument().empty())
6010 // FIXME: Are all these characters encoded in one byte in utf8?
6011 bv->translateAndInsert(cmd.argument()[0], this, cur);
6012 cur.screenUpdateFlags(Update::FitCursor);
6015 case LFUN_FLOAT_LIST_INSERT: {
6016 DocumentClass const & tclass = bv->buffer().params().documentClass();
6017 if (tclass.floats().typeExist(to_utf8(cmd.argument()))) {
6019 if (cur.selection())
6020 cutSelection(cur, false);
6021 breakParagraph(cur);
6023 if (cur.lastpos() != 0) {
6024 cursorBackward(cur);
6025 breakParagraph(cur);
6028 docstring const laystr = cur.inset().usePlainLayout() ?
6029 tclass.plainLayoutName() :
6030 tclass.defaultLayoutName();
6031 setLayout(cur, laystr);
6032 ParagraphParameters p;
6033 // FIXME If this call were replaced with one to clearParagraphParams(),
6034 // then we could get rid of this method altogether.
6035 setParagraphs(cur, p);
6036 // FIXME This should be simplified when InsetFloatList takes a
6037 // Buffer in its constructor.
6038 InsetFloatList * ifl = new InsetFloatList(cur.buffer(), to_utf8(cmd.argument()));
6039 ifl->setBuffer(bv->buffer());
6040 insertInset(cur, ifl);
6043 lyxerr << "Non-existent float type: "
6044 << to_utf8(cmd.argument()) << endl;
6049 case LFUN_CHANGE_ACCEPT: {
6050 acceptOrRejectChanges(cur, ACCEPT);
6054 case LFUN_CHANGE_REJECT: {
6055 acceptOrRejectChanges(cur, REJECT);
6059 case LFUN_THESAURUS_ENTRY: {
6060 Language const * language = cur.getFont().language();
6061 docstring arg = cmd.argument();
6063 arg = cur.selectionAsString(false);
6064 // Too large. We unselect if needed and try to get
6065 // the first word in selection or under cursor
6066 if (arg.size() > 100 || arg.empty()) {
6067 if (cur.selection()) {
6068 DocIterator selbeg = cur.selectionBegin();
6069 cur.clearSelection();
6070 setCursorIntern(cur, selbeg.pit(), selbeg.pos());
6071 cur.screenUpdateFlags(Update::Force);
6073 // Get word or selection
6074 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6075 arg = cur.selectionAsString(false);
6076 arg += " lang=" + from_ascii(language->lang());
6079 string lang = cmd.getArg(1);
6080 // This duplicates the code in GuiThesaurus::initialiseParams
6081 if (prefixIs(lang, "lang=")) {
6082 language = languages.getLanguage(lang.substr(5));
6084 language = cur.getFont().language();
6087 string lang = language->code();
6088 if (lyxrc.thesaurusdir_path.empty() && !thesaurus.thesaurusInstalled(from_ascii(lang))) {
6089 LYXERR(Debug::ACTION, "Command " << cmd << ". Thesaurus not found for language " << lang);
6090 frontend::Alert::warning(_("Path to thesaurus directory not set!"),
6091 _("The path to the thesaurus directory has not been specified.\n"
6092 "The thesaurus is not functional.\n"
6093 "Please refer to sec. 6.15.1 of the User's Guide for setup\n"
6096 bv->showDialog("thesaurus", to_utf8(arg));
6100 case LFUN_SPELLING_ADD: {
6101 Language const * language = getLanguage(cur, cmd.getArg(1));
6102 docstring word = from_utf8(cmd.getArg(0));
6104 word = cur.selectionAsString(false);
6106 if (word.size() > 100 || word.empty()) {
6107 // Get word or selection
6108 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6109 word = cur.selectionAsString(false);
6112 WordLangTuple wl(word, language);
6113 theSpellChecker()->insert(wl);
6117 case LFUN_SPELLING_ADD_LOCAL: {
6118 Language const * language = getLanguage(cur, cmd.getArg(1));
6119 docstring word = from_utf8(cmd.getArg(0));
6121 word = cur.selectionAsString(false);
6122 if (word.size() > 100)
6125 // Get word or selection
6126 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6127 word = cur.selectionAsString(false);
6130 WordLangTuple wl(word, language);
6131 if (!bv->buffer().params().spellignored(wl)) {
6132 cur.recordUndoBufferParams();
6133 bv->buffer().params().spellignore().push_back(wl);
6135 // trigger re-check of whole buffer
6136 bv->buffer().requestSpellcheck();
6141 case LFUN_SPELLING_REMOVE_LOCAL: {
6142 Language const * language = getLanguage(cur, cmd.getArg(1));
6143 docstring word = from_utf8(cmd.getArg(0));
6145 word = cur.selectionAsString(false);
6146 if (word.size() > 100)
6149 // Get word or selection
6150 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6151 word = cur.selectionAsString(false);
6154 WordLangTuple wl(word, language);
6155 bool has_item = false;
6156 vector<WordLangTuple>::const_iterator it = bv->buffer().params().spellignore().begin();
6157 for (; it != bv->buffer().params().spellignore().end(); ++it) {
6158 if (it->lang()->code() != wl.lang()->code())
6160 if (it->word() == wl.word()) {
6166 cur.recordUndoBufferParams();
6167 bv->buffer().params().spellignore().erase(it);
6169 // trigger re-check of whole buffer
6170 bv->buffer().requestSpellcheck();
6176 case LFUN_SPELLING_IGNORE: {
6177 Language const * language = getLanguage(cur, cmd.getArg(1));
6178 docstring word = from_utf8(cmd.getArg(0));
6180 word = cur.selectionAsString(false);
6182 if (word.size() > 100 || word.empty()) {
6183 // Get word or selection
6184 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6185 word = cur.selectionAsString(false);
6188 WordLangTuple wl(word, language);
6189 theSpellChecker()->accept(wl);
6193 case LFUN_SPELLING_REMOVE: {
6194 Language const * language = getLanguage(cur, cmd.getArg(1));
6195 docstring word = from_utf8(cmd.getArg(0));
6197 word = cur.selectionAsString(false);
6199 if (word.size() > 100 || word.empty()) {
6200 // Get word or selection
6201 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6202 word = cur.selectionAsString(false);
6205 WordLangTuple wl(word, language);
6206 theSpellChecker()->remove(wl);
6210 case LFUN_PARAGRAPH_PARAMS_APPLY: {
6211 // Given data, an encoding of the ParagraphParameters
6212 // generated in the Paragraph dialog, this function sets
6213 // the current paragraph, or currently selected paragraphs,
6215 // NOTE: This function overrides all existing settings.
6216 setParagraphs(cur, cmd.argument());
6217 cur.message(_("Paragraph layout set"));
6221 case LFUN_PARAGRAPH_PARAMS: {
6222 // Given data, an encoding of the ParagraphParameters as we'd
6223 // find them in a LyX file, this function modifies the current paragraph,
6224 // or currently selected paragraphs.
6225 // NOTE: This function only modifies, and does not override, existing
6227 setParagraphs(cur, cmd.argument(), true);
6228 cur.message(_("Paragraph layout set"));
6233 if (cur.selection()) {
6234 cur.selection(false);
6237 // This used to be LFUN_FINISHED_RIGHT, I think FORWARD is more
6238 // correct, but I'm not 100% sure -- dov, 071019
6239 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
6243 case LFUN_OUTLINE_UP: {
6244 pos_type const opos = cur.pos();
6245 outline(OutlineUp, cur, this);
6246 setCursor(cur, cur.pit(), opos);
6247 cur.forceBufferUpdate();
6252 case LFUN_OUTLINE_DOWN: {
6253 pos_type const opos = cur.pos();
6254 outline(OutlineDown, cur, this);
6255 setCursor(cur, cur.pit(), opos);
6256 cur.forceBufferUpdate();
6261 case LFUN_OUTLINE_IN:
6262 outline(OutlineIn, cur, this);
6263 cur.forceBufferUpdate();
6267 case LFUN_OUTLINE_OUT:
6268 outline(OutlineOut, cur, this);
6269 cur.forceBufferUpdate();
6273 case LFUN_SERVER_GET_STATISTICS: {
6274 DocIterator from, to;
6275 if (cur.selection()) {
6276 from = cur.selectionBegin();
6277 to = cur.selectionEnd();
6279 from = doc_iterator_begin(cur.buffer());
6280 to = doc_iterator_end(cur.buffer());
6283 cur.buffer()->updateStatistics(from, to);
6284 string const arg0 = cmd.getArg(0);
6285 if (arg0 == "words") {
6286 cur.message(convert<docstring>(cur.buffer()->wordCount()));
6287 } else if (arg0 == "chars") {
6288 cur.message(convert<docstring>(cur.buffer()->charCount(false)));
6289 } else if (arg0 == "chars-space") {
6290 cur.message(convert<docstring>(cur.buffer()->charCount(true)));
6292 cur.message(convert<docstring>(cur.buffer()->wordCount()) + " "
6293 + convert<docstring>(cur.buffer()->charCount(false)) + " "
6294 + convert<docstring>(cur.buffer()->charCount(true)));
6300 LYXERR(Debug::ACTION, "Command " << cmd << " not DISPATCHED by Text");
6305 needsUpdate |= (cur.pos() != cur.lastpos()) && cur.selection();
6307 if (lyxrc.spellcheck_continuously && !needsUpdate) {
6308 // Check for misspelled text
6309 // The redraw is useful because of the painting of
6310 // misspelled markers depends on the cursor position.
6311 // Trigger a redraw for cursor moves inside misspelled text.
6312 if (!cur.inTexted()) {
6313 // move from regular text to math
6314 needsUpdate = last_misspelled;
6315 } else if (oldTopSlice != cur.top() || oldBoundary != cur.boundary()) {
6316 // move inside regular text
6317 needsUpdate = last_misspelled
6318 || cur.paragraph().isMisspelled(cur.pos(), true);
6322 // FIXME: The cursor flag is reset two lines below
6323 // so we need to check here if some of the LFUN did touch that.
6324 // for now only Text::erase() and Text::backspace() do that.
6325 // The plan is to verify all the LFUNs and then to remove this
6326 // singleParUpdate boolean altogether.
6327 if (cur.result().screenUpdate() & Update::Force) {
6328 singleParUpdate = false;
6332 // FIXME: the following code should go in favor of fine grained
6333 // update flag treatment.
6334 if (singleParUpdate) {
6335 // Inserting characters does not change par height in general. So, try
6336 // to update _only_ this paragraph. BufferView will detect if a full
6337 // metrics update is needed anyway.
6338 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
6342 && &oldTopSlice.inset() == &cur.inset()
6343 && oldTopSlice.idx() == cur.idx()
6344 && !oldSelection // oldSelection is a backup of cur.selection() at the beginning of the function.
6345 && !cur.selection())
6346 // FIXME: it would be better if we could just do this
6348 //if (cur.result().update() != Update::FitCursor)
6349 // cur.noScreenUpdate();
6351 // But some LFUNs do not set Update::FitCursor when needed, so we
6352 // do it for all. This is not very harmfull as FitCursor will provoke
6353 // a full redraw only if needed but still, a proper review of all LFUN
6354 // should be done and this needsUpdate boolean can then be removed.
6355 cur.screenUpdateFlags(Update::FitCursor);
6357 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
6361 bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
6362 FuncStatus & status) const
6364 LBUFERR(this == cur.text());
6366 FontInfo const & fontinfo = cur.real_current_font.fontInfo();
6368 bool allow_in_passthru = false;
6369 InsetCode code = NO_CODE;
6371 switch (cmd.action()) {
6373 case LFUN_DEPTH_DECREMENT:
6374 enable = changeDepthAllowed(cur, DEC_DEPTH);
6377 case LFUN_DEPTH_INCREMENT:
6378 enable = changeDepthAllowed(cur, INC_DEPTH);
6382 // FIXME We really should not allow this to be put, e.g.,
6383 // in a footnote, or in ERT. But it would make sense in a
6384 // branch, so I'm not sure what to do.
6385 status.setOnOff(cur.paragraph().params().startOfAppendix());
6388 case LFUN_DIALOG_SHOW_NEW_INSET:
6389 if (cmd.argument() == "bibitem")
6390 code = BIBITEM_CODE;
6391 else if (cmd.argument() == "bibtex") {
6393 // not allowed in description items
6394 enable = !inDescriptionItem(cur);
6396 else if (cmd.argument() == "box")
6398 else if (cmd.argument() == "branch")
6400 else if (cmd.argument() == "citation")
6402 else if (cmd.argument() == "counter")
6403 code = COUNTER_CODE;
6404 else if (cmd.argument() == "ert")
6406 else if (cmd.argument() == "external")
6407 code = EXTERNAL_CODE;
6408 else if (cmd.argument() == "float")
6410 else if (cmd.argument() == "graphics")
6411 code = GRAPHICS_CODE;
6412 else if (cmd.argument() == "href")
6413 code = HYPERLINK_CODE;
6414 else if (cmd.argument() == "include")
6415 code = INCLUDE_CODE;
6416 else if (cmd.argument() == "index")
6418 else if (cmd.argument() == "index_print")
6419 code = INDEX_PRINT_CODE;
6420 else if (cmd.argument() == "listings")
6421 code = LISTINGS_CODE;
6422 else if (cmd.argument() == "mathspace")
6423 code = MATH_HULL_CODE;
6424 else if (cmd.argument() == "nomenclature")
6425 code = NOMENCL_CODE;
6426 else if (cmd.argument() == "nomencl_print")
6427 code = NOMENCL_PRINT_CODE;
6428 else if (cmd.argument() == "label")
6430 else if (cmd.argument() == "line")
6432 else if (cmd.argument() == "note")
6434 else if (cmd.argument() == "phantom")
6435 code = PHANTOM_CODE;
6436 else if (cmd.argument() == "ref")
6438 else if (cmd.argument() == "space")
6440 else if (cmd.argument() == "toc")
6442 else if (cmd.argument() == "vspace")
6444 else if (cmd.argument() == "wrap")
6448 case LFUN_ERT_INSERT:
6451 case LFUN_LISTING_INSERT:
6452 code = LISTINGS_CODE;
6453 // not allowed in description items
6454 enable = !inDescriptionItem(cur);
6456 case LFUN_FOOTNOTE_INSERT:
6459 case LFUN_TABULAR_INSERT:
6460 code = TABULAR_CODE;
6462 case LFUN_TABULAR_STYLE_INSERT:
6463 code = TABULAR_CODE;
6465 case LFUN_MARGINALNOTE_INSERT:
6468 case LFUN_FLOAT_INSERT:
6469 case LFUN_FLOAT_WIDE_INSERT:
6470 // FIXME: If there is a selection, we should check whether there
6471 // are floats in the selection, but this has performance issues, see
6472 // LFUN_CHANGE_ACCEPT/REJECT.
6474 if (inDescriptionItem(cur))
6475 // not allowed in description items
6478 InsetCode const inset_code = cur.inset().lyxCode();
6480 // algorithm floats cannot be put in another float
6481 if (to_utf8(cmd.argument()) == "algorithm") {
6482 enable = inset_code != WRAP_CODE && inset_code != FLOAT_CODE;
6486 // for figures and tables: only allow in another
6487 // float or wrap if it is of the same type and
6488 // not a subfloat already
6489 if(cur.inset().lyxCode() == code) {
6490 InsetFloat const & ins =
6491 static_cast<InsetFloat const &>(cur.inset());
6492 enable = ins.params().type == to_utf8(cmd.argument())
6493 && !ins.params().subfloat;
6494 } else if(cur.inset().lyxCode() == WRAP_CODE) {
6495 InsetWrap const & ins =
6496 static_cast<InsetWrap const &>(cur.inset());
6497 enable = ins.params().type == to_utf8(cmd.argument());
6501 case LFUN_WRAP_INSERT:
6503 // not allowed in description items
6504 enable = !inDescriptionItem(cur);
6506 case LFUN_FLOAT_LIST_INSERT: {
6507 code = FLOAT_LIST_CODE;
6508 // not allowed in description items
6509 enable = !inDescriptionItem(cur);
6511 FloatList const & floats = cur.buffer()->params().documentClass().floats();
6512 FloatList::const_iterator cit = floats[to_ascii(cmd.argument())];
6513 // make sure we know about such floats
6514 if (cit == floats.end() ||
6515 // and that we know how to generate a list of them
6516 (!cit->second.usesFloatPkg() && cit->second.listCommand().empty())) {
6517 status.setUnknown(true);
6518 // probably not necessary, but...
6524 case LFUN_CAPTION_INSERT: {
6525 code = CAPTION_CODE;
6526 string arg = cmd.getArg(0);
6527 bool varia = arg != "Unnumbered"
6528 && cur.inset().allowsCaptionVariation(arg);
6529 // not allowed in description items,
6530 // and in specific insets
6531 enable = !inDescriptionItem(cur)
6532 && (varia || arg.empty() || arg == "Standard");
6535 case LFUN_NOTE_INSERT:
6538 case LFUN_FLEX_INSERT: {
6540 docstring s = from_utf8(cmd.getArg(0));
6541 // Prepend "Flex:" prefix if not there
6542 if (!prefixIs(s, from_ascii("Flex:")))
6543 s = from_ascii("Flex:") + s;
6544 if (!cur.buffer()->params().documentClass().hasInsetLayout(s))
6548 cur.buffer()->params().documentClass().insetLayout(s).lyxtype();
6549 if (ilt != InsetLyXType::CHARSTYLE
6550 && ilt != InsetLyXType::CUSTOM
6551 && ilt != InsetLyXType::STANDARD)
6556 case LFUN_BOX_INSERT:
6559 case LFUN_BRANCH_INSERT:
6561 if (cur.buffer()->masterBuffer()->params().branchlist().empty()
6562 && cur.buffer()->params().branchlist().empty())
6565 case LFUN_IPA_INSERT:
6568 case LFUN_PHANTOM_INSERT:
6569 code = PHANTOM_CODE;
6571 case LFUN_LABEL_INSERT:
6574 case LFUN_INFO_INSERT:
6576 enable = cmd.argument().empty()
6577 || infoparams.validateArgument(cur.buffer(), cmd.argument(), true);
6579 case LFUN_ARGUMENT_INSERT: {
6581 allow_in_passthru = true;
6582 string const arg = cmd.getArg(0);
6587 Layout const & lay = cur.paragraph().layout();
6588 Layout::LaTeXArgMap args = lay.args();
6589 Layout::LaTeXArgMap::const_iterator const lait =
6591 if (lait != args.end()) {
6593 pit_type pit = cur.pit();
6594 pit_type lastpit = cur.pit();
6595 if (lay.isEnvironment() && !prefixIs(arg, "item:")) {
6596 // In a sequence of "merged" environment layouts, we only allow
6597 // non-item arguments once.
6598 lastpit = cur.lastpit();
6599 // get the first paragraph in sequence with this layout
6600 depth_type const current_depth = cur.paragraph().params().depth();
6604 Paragraph cpar = pars_[pit - 1];
6605 if (cpar.layout() == lay && cpar.params().depth() == current_depth)
6611 for (; pit <= lastpit; ++pit) {
6612 if (pars_[pit].layout() != lay)
6614 for (auto const & table : pars_[pit].insetList())
6615 if (InsetArgument const * ins = table.inset->asInsetArgument())
6616 if (ins->name() == arg) {
6617 // we have this already
6626 case LFUN_INDEX_INSERT:
6629 case LFUN_INDEX_PRINT:
6630 code = INDEX_PRINT_CODE;
6631 // not allowed in description items
6632 enable = !inDescriptionItem(cur);
6634 case LFUN_NOMENCL_INSERT:
6635 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6639 code = NOMENCL_CODE;
6641 case LFUN_NOMENCL_PRINT:
6642 code = NOMENCL_PRINT_CODE;
6643 // not allowed in description items
6644 enable = !inDescriptionItem(cur);
6646 case LFUN_HREF_INSERT:
6647 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6651 code = HYPERLINK_CODE;
6653 case LFUN_INDEXMACRO_INSERT: {
6654 string const arg = cmd.getArg(0);
6655 if (arg == "sortkey")
6656 code = INDEXMACRO_SORTKEY_CODE;
6658 code = INDEXMACRO_CODE;
6661 case LFUN_IPAMACRO_INSERT: {
6662 string const arg = cmd.getArg(0);
6664 code = IPADECO_CODE;
6666 code = IPACHAR_CODE;
6669 case LFUN_QUOTE_INSERT:
6670 // always allow this, since we will inset a raw quote
6671 // if an inset is not allowed.
6672 allow_in_passthru = true;
6674 case LFUN_SPECIALCHAR_INSERT:
6675 code = SPECIALCHAR_CODE;
6677 case LFUN_SPACE_INSERT:
6678 // slight hack: we know this is allowed in math mode
6682 case LFUN_PREVIEW_INSERT:
6683 code = PREVIEW_CODE;
6685 case LFUN_SCRIPT_INSERT:
6689 case LFUN_MATH_INSERT:
6690 case LFUN_MATH_AMS_MATRIX:
6691 case LFUN_MATH_MATRIX:
6692 case LFUN_MATH_DELIM:
6693 case LFUN_MATH_BIGDELIM:
6694 case LFUN_MATH_DISPLAY:
6695 case LFUN_MATH_MODE:
6696 case LFUN_MATH_MACRO:
6697 case LFUN_MATH_SUBSCRIPT:
6698 case LFUN_MATH_SUPERSCRIPT:
6699 code = MATH_HULL_CODE;
6702 case LFUN_REGEXP_MODE:
6703 code = MATH_HULL_CODE;
6704 enable = cur.buffer()->isInternal() && !cur.inRegexped();
6707 case LFUN_INSET_MODIFY:
6708 // We need to disable this, because we may get called for a
6710 // InsetTabular::getStatus() -> InsetText::getStatus()
6711 // and we don't handle LFUN_INSET_MODIFY.
6715 case LFUN_FONT_EMPH:
6716 status.setOnOff(fontinfo.emph() == FONT_ON);
6717 enable = !cur.paragraph().isPassThru();
6720 case LFUN_FONT_ITAL:
6721 status.setOnOff(fontinfo.shape() == ITALIC_SHAPE);
6722 enable = !cur.paragraph().isPassThru();
6725 case LFUN_FONT_NOUN:
6726 status.setOnOff(fontinfo.noun() == FONT_ON);
6727 enable = !cur.paragraph().isPassThru();
6730 case LFUN_FONT_BOLD:
6731 case LFUN_FONT_BOLDSYMBOL:
6732 status.setOnOff(fontinfo.series() == BOLD_SERIES);
6733 enable = !cur.paragraph().isPassThru();
6736 case LFUN_FONT_SANS:
6737 status.setOnOff(fontinfo.family() == SANS_FAMILY);
6738 enable = !cur.paragraph().isPassThru();
6741 case LFUN_FONT_ROMAN:
6742 status.setOnOff(fontinfo.family() == ROMAN_FAMILY);
6743 enable = !cur.paragraph().isPassThru();
6746 case LFUN_FONT_TYPEWRITER:
6747 status.setOnOff(fontinfo.family() == TYPEWRITER_FAMILY);
6748 enable = !cur.paragraph().isPassThru();
6752 enable = cur.selection();
6756 if (cmd.argument().empty()) {
6757 if (theClipboard().isInternal())
6758 enable = cap::numberOfSelections() > 0;
6760 enable = !theClipboard().empty();
6764 // we have an argument
6765 string const arg = to_utf8(cmd.argument());
6766 if (isStrUnsignedInt(arg)) {
6767 // it's a number and therefore means the internal stack
6768 unsigned int n = convert<unsigned int>(arg);
6769 enable = cap::numberOfSelections() > n;
6773 // explicit text type?
6774 if (arg == "html") {
6775 // Do not enable for PlainTextType, since some tidying in the
6776 // frontend is needed for HTML, which is too unsafe for plain text.
6777 enable = theClipboard().hasTextContents(Clipboard::HtmlTextType);
6779 } else if (arg == "latex") {
6780 // LaTeX is usually not available on the clipboard with
6781 // the correct MIME type, but in plain text.
6782 enable = theClipboard().hasTextContents(Clipboard::PlainTextType) ||
6783 theClipboard().hasTextContents(Clipboard::LaTeXTextType);
6787 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
6789 type = Clipboard::PdfGraphicsType;
6790 else if (arg == "png")
6791 type = Clipboard::PngGraphicsType;
6792 else if (arg == "jpeg")
6793 type = Clipboard::JpegGraphicsType;
6794 else if (arg == "linkback")
6795 type = Clipboard::LinkBackGraphicsType;
6796 else if (arg == "emf")
6797 type = Clipboard::EmfGraphicsType;
6798 else if (arg == "wmf")
6799 type = Clipboard::WmfGraphicsType;
6802 LYXERR0("Unrecognized graphics type: " << arg);
6803 // we don't want to assert if the user just mistyped the LFUN
6804 LATTEST(cmd.origin() != FuncRequest::INTERNAL);
6808 enable = theClipboard().hasGraphicsContents(type);
6812 case LFUN_CLIPBOARD_PASTE:
6813 case LFUN_CLIPBOARD_PASTE_SIMPLE:
6814 enable = !theClipboard().empty();
6817 case LFUN_PRIMARY_SELECTION_PASTE:
6818 status.setUnknown(!theSelection().supported());
6819 enable = cur.selection() || !theSelection().empty();
6822 case LFUN_SELECTION_PASTE:
6823 enable = cap::selection();
6826 case LFUN_PARAGRAPH_MOVE_UP:
6827 enable = cur.pit() > 0 && !cur.selection();
6830 case LFUN_PARAGRAPH_MOVE_DOWN:
6831 enable = cur.pit() < cur.lastpit() && !cur.selection();
6834 case LFUN_CHANGE_ACCEPT:
6835 case LFUN_CHANGE_REJECT:
6836 if (!cur.selection())
6837 enable = cur.paragraph().isChanged(cur.pos());
6839 // will enable if there is a change in the selection
6842 // cheap improvement for efficiency: using cached
6843 // buffer variable, if there is no change in the
6844 // document, no need to check further.
6845 if (!cur.buffer()->areChangesPresent())
6848 for (DocIterator it = cur.selectionBegin(); ; it.forwardPar()) {
6849 pos_type const beg = it.pos();
6851 bool const in_last_par = (it.pit() == cur.selectionEnd().pit() &&
6852 it.idx() == cur.selectionEnd().idx());
6854 end = cur.selectionEnd().pos();
6856 // the +1 is needed for cases, e.g., where there is a
6857 // paragraph break. See #11629.
6858 end = it.lastpos() + 1;
6859 if (beg != end && it.paragraph().isChanged(beg, end)) {
6863 if (beg != end && it.paragraph().hasChangedInsets(beg, end)) {
6873 case LFUN_OUTLINE_UP:
6874 case LFUN_OUTLINE_DOWN:
6875 case LFUN_OUTLINE_IN:
6876 case LFUN_OUTLINE_OUT:
6877 // FIXME: LyX is not ready for outlining within inset.
6878 enable = isMainText()
6879 && cur.buffer()->text().getTocLevel(cur.pit()) != Layout::NOT_IN_TOC;
6882 case LFUN_NEWLINE_INSERT:
6883 // LaTeX restrictions (labels or empty par)
6884 enable = !cur.paragraph().isPassThru()
6885 && cur.pos() > cur.paragraph().beginOfBody();
6888 case LFUN_SEPARATOR_INSERT:
6889 // Always enabled for now
6893 case LFUN_TAB_INSERT:
6894 case LFUN_TAB_DELETE:
6895 enable = cur.paragraph().isPassThru();
6898 case LFUN_GRAPHICS_SET_GROUP: {
6899 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
6903 status.setOnOff(to_utf8(cmd.argument()) == ins->getParams().groupId);
6907 case LFUN_NEWPAGE_INSERT:
6908 // not allowed in description items
6909 code = NEWPAGE_CODE;
6910 enable = !inDescriptionItem(cur);
6914 enable = !cur.paragraph().isPassThru();
6915 status.setOnOff(cmd.getArg(0) == cur.real_current_font.language()->lang());
6918 case LFUN_PARAGRAPH_BREAK:
6919 enable = inset().allowMultiPar();
6922 case LFUN_SPELLING_ADD:
6923 case LFUN_SPELLING_ADD_LOCAL:
6924 case LFUN_SPELLING_REMOVE_LOCAL:
6925 case LFUN_SPELLING_IGNORE:
6926 case LFUN_SPELLING_REMOVE:
6927 enable = theSpellChecker() != nullptr;
6928 if (enable && !cmd.getArg(1).empty()) {
6929 // validate explicitly given language
6930 Language const * const lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
6931 enable &= lang != nullptr;
6936 case LFUN_LAYOUT_TOGGLE: {
6937 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
6938 docstring const req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
6939 docstring const layout = resolveLayout(req_layout, cur);
6941 // FIXME: make this work in multicell selection case
6942 enable = !owner_->forcePlainLayout() && !layout.empty() && !cur.selIsMultiCell();
6943 status.setOnOff(!owner_->forcePlainLayout() && !cur.selIsMultiCell()
6944 && isAlreadyLayout(layout, cur));
6948 case LFUN_ENVIRONMENT_SPLIT: {
6949 if (cmd.argument() == "outer") {
6950 // check if we have an environment in our nesting hierarchy
6952 depth_type const current_depth = cur.paragraph().params().depth();
6953 pit_type pit = cur.pit();
6954 Paragraph cpar = pars_[pit];
6956 if (pit == 0 || cpar.params().depth() == 0)
6960 if (cpar.params().depth() < current_depth)
6961 res = cpar.layout().isEnvironment();
6966 else if (cmd.argument() == "previous") {
6967 // look if we have an environment in the previous par
6968 pit_type pit = cur.pit();
6969 Paragraph cpar = pars_[pit];
6973 enable = cpar.layout().isEnvironment();
6979 else if (cur.paragraph().layout().isEnvironment()) {
6980 enable = cmd.argument() == "before"
6981 || cur.pos() > 0 || !isFirstInSequence(cur.pit());
6988 case LFUN_LAYOUT_PARAGRAPH:
6989 case LFUN_PARAGRAPH_PARAMS:
6990 case LFUN_PARAGRAPH_PARAMS_APPLY:
6991 case LFUN_PARAGRAPH_UPDATE:
6992 enable = owner_->allowParagraphCustomization();
6995 // FIXME: why are accent lfuns forbidden with pass_thru layouts?
6996 // Because they insert COMBINING DIACRITICAL Unicode characters,
6997 // that cannot be handled by LaTeX but must be converted according
6998 // to the definition in lib/unicodesymbols?
6999 case LFUN_ACCENT_ACUTE:
7000 case LFUN_ACCENT_BREVE:
7001 case LFUN_ACCENT_CARON:
7002 case LFUN_ACCENT_CEDILLA:
7003 case LFUN_ACCENT_CIRCLE:
7004 case LFUN_ACCENT_CIRCUMFLEX:
7005 case LFUN_ACCENT_DOT:
7006 case LFUN_ACCENT_GRAVE:
7007 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
7008 case LFUN_ACCENT_MACRON:
7009 case LFUN_ACCENT_OGONEK:
7010 case LFUN_ACCENT_TIE:
7011 case LFUN_ACCENT_TILDE:
7012 case LFUN_ACCENT_PERISPOMENI:
7013 case LFUN_ACCENT_UMLAUT:
7014 case LFUN_ACCENT_UNDERBAR:
7015 case LFUN_ACCENT_UNDERDOT:
7016 case LFUN_FONT_FRAK:
7017 case LFUN_FONT_SIZE:
7018 case LFUN_FONT_STATE:
7019 case LFUN_FONT_UNDERLINE:
7020 case LFUN_FONT_STRIKEOUT:
7021 case LFUN_FONT_CROSSOUT:
7022 case LFUN_FONT_UNDERUNDERLINE:
7023 case LFUN_FONT_UNDERWAVE:
7024 case LFUN_FONT_NO_SPELLCHECK:
7025 case LFUN_TEXTSTYLE_UPDATE:
7026 enable = !cur.paragraph().isPassThru();
7029 case LFUN_FONT_DEFAULT: {
7030 Font font(inherit_font, ignore_language);
7031 BufferParams const & bp = cur.buffer()->masterParams();
7032 if (cur.selection()) {
7034 // Check if we have a non-default font attribute
7035 // in the selection range.
7036 DocIterator const from = cur.selectionBegin();
7037 DocIterator const to = cur.selectionEnd();
7038 for (DocIterator dit = from ; dit != to && !dit.atEnd(); ) {
7039 if (!dit.inTexted()) {
7043 Paragraph const & par = dit.paragraph();
7044 pos_type const pos = dit.pos();
7045 Font tmp = par.getFontSettings(bp, pos);
7046 if (tmp.fontInfo() != font.fontInfo()
7047 || tmp.language() != bp.language) {
7055 // Disable if all is default already.
7056 enable = (cur.current_font.fontInfo() != font.fontInfo()
7057 || cur.current_font.language() != bp.language);
7061 case LFUN_TEXTSTYLE_APPLY:
7062 enable = !freeFonts.empty();
7065 case LFUN_WORD_DELETE_FORWARD:
7066 case LFUN_WORD_DELETE_BACKWARD:
7067 case LFUN_LINE_DELETE_FORWARD:
7068 case LFUN_WORD_FORWARD:
7069 case LFUN_WORD_BACKWARD:
7070 case LFUN_WORD_RIGHT:
7071 case LFUN_WORD_LEFT:
7072 case LFUN_CHAR_FORWARD:
7073 case LFUN_CHAR_FORWARD_SELECT:
7074 case LFUN_CHAR_BACKWARD:
7075 case LFUN_CHAR_BACKWARD_SELECT:
7076 case LFUN_CHAR_LEFT:
7077 case LFUN_CHAR_LEFT_SELECT:
7078 case LFUN_CHAR_RIGHT:
7079 case LFUN_CHAR_RIGHT_SELECT:
7081 case LFUN_UP_SELECT:
7083 case LFUN_DOWN_SELECT:
7084 case LFUN_PARAGRAPH_SELECT:
7085 case LFUN_PARAGRAPH_UP_SELECT:
7086 case LFUN_PARAGRAPH_DOWN_SELECT:
7087 case LFUN_LINE_BEGIN_SELECT:
7088 case LFUN_LINE_END_SELECT:
7089 case LFUN_WORD_FORWARD_SELECT:
7090 case LFUN_WORD_BACKWARD_SELECT:
7091 case LFUN_WORD_RIGHT_SELECT:
7092 case LFUN_WORD_LEFT_SELECT:
7093 case LFUN_WORD_SELECT:
7094 case LFUN_SECTION_SELECT:
7095 case LFUN_BUFFER_BEGIN:
7096 case LFUN_BUFFER_END:
7097 case LFUN_BUFFER_BEGIN_SELECT:
7098 case LFUN_BUFFER_END_SELECT:
7099 case LFUN_INSET_BEGIN:
7100 case LFUN_INSET_END:
7101 case LFUN_INSET_BEGIN_SELECT:
7102 case LFUN_INSET_END_SELECT:
7103 case LFUN_PARAGRAPH_UP:
7104 case LFUN_PARAGRAPH_DOWN:
7105 case LFUN_LINE_BEGIN:
7107 case LFUN_CHAR_DELETE_FORWARD:
7108 case LFUN_CHAR_DELETE_BACKWARD:
7109 case LFUN_WORD_UPCASE:
7110 case LFUN_WORD_LOWCASE:
7111 case LFUN_WORD_CAPITALIZE:
7112 case LFUN_CHARS_TRANSPOSE:
7113 case LFUN_SERVER_GET_XY:
7114 case LFUN_SERVER_SET_XY:
7115 case LFUN_SERVER_GET_LAYOUT:
7116 case LFUN_SELF_INSERT:
7117 case LFUN_UNICODE_INSERT:
7118 case LFUN_THESAURUS_ENTRY:
7120 case LFUN_SERVER_GET_STATISTICS:
7121 // these are handled in our dispatch()
7125 case LFUN_INSET_INSERT: {
7126 string const type = cmd.getArg(0);
7127 if (type == "toc") {
7129 // not allowed in description items
7130 //FIXME: couldn't this be merged in Inset::insetAllowed()?
7131 enable = !inDescriptionItem(cur);
7138 case LFUN_SEARCH_IGNORE: {
7139 bool const value = cmd.getArg(1) == "true";
7140 setIgnoreFormat(cmd.getArg(0), value);
7150 || !cur.inset().insetAllowed(code)
7151 || (cur.paragraph().layout().pass_thru && !allow_in_passthru)))
7154 status.setEnabled(enable);
7159 void Text::pasteString(Cursor & cur, docstring const & clip,
7162 if (!clip.empty()) {
7165 insertStringAsParagraphs(cur, clip, cur.current_font);
7167 insertStringAsLines(cur, clip, cur.current_font);
7172 // FIXME: an item inset would make things much easier.
7173 bool Text::inDescriptionItem(Cursor const & cur) const
7175 Paragraph const & par = cur.paragraph();
7176 pos_type const pos = cur.pos();
7177 pos_type const body_pos = par.beginOfBody();
7179 if (par.layout().latextype != LATEX_LIST_ENVIRONMENT
7180 && (par.layout().latextype != LATEX_ITEM_ENVIRONMENT
7181 || par.layout().margintype != MARGIN_FIRST_DYNAMIC))
7184 return (pos < body_pos
7186 && (pos == 0 || par.getChar(pos - 1) != ' ')));
7190 std::vector<docstring> Text::getFreeFonts() const
7192 vector<docstring> ffList;
7194 for (auto const & f : freeFonts)
7195 ffList.push_back(f.first);