3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Alfredo Braunstein
9 * \author Angus Leeming
10 * \author Asger Alstrup
12 * \author Dov Feldstern
13 * \author Jean-Marc Lasgouttes
15 * \author Jürgen Vigna
16 * \author Lars Gullik Bjønnes
17 * \author Stefan Schimanski
19 * Full author contact details are available in file CREDITS.
27 #include "BranchList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
32 #include "CompletionList.h"
34 #include "CursorSlice.h"
35 #include "CutAndPaste.h"
36 #include "DispatchResult.h"
38 #include "ErrorList.h"
40 #include "FloatList.h"
42 #include "FuncRequest.h"
43 #include "FuncStatus.h"
44 #include "InsetList.h"
50 #include "LyXAction.h"
53 #include "Paragraph.h"
54 #include "ParagraphParameters.h"
55 #include "SpellChecker.h"
56 #include "TextClass.h"
57 #include "TextMetrics.h"
58 #include "Thesaurus.h"
59 #include "WordLangTuple.h"
62 #include "frontends/alert.h"
63 #include "frontends/Application.h"
64 #include "frontends/Clipboard.h"
65 #include "frontends/Selection.h"
67 #include "mathed/InsetMathHull.h"
68 #include "mathed/InsetMathMacroTemplate.h"
70 #include "insets/Inset.h"
71 #include "insets/InsetArgument.h"
72 #include "insets/InsetCaption.h"
73 #include "insets/InsetCollapsible.h"
74 #include "insets/InsetCommand.h"
75 #include "insets/InsetExternal.h"
76 #include "insets/InsetFloat.h"
77 #include "insets/InsetFloatList.h"
78 #include "insets/InsetGraphics.h"
79 #include "insets/InsetGraphicsParams.h"
80 #include "insets/InsetIndexMacro.h"
81 #include "insets/InsetInfo.h"
82 #include "insets/InsetIPAMacro.h"
83 #include "insets/InsetNewline.h"
84 #include "insets/InsetQuotes.h"
85 #include "insets/InsetSpecialChar.h"
86 #include "insets/InsetTabular.h"
87 #include "insets/InsetText.h"
88 #include "insets/InsetWrap.h"
90 #include "support/convert.h"
91 #include "support/debug.h"
92 #include "support/docstream.h"
93 #include "support/docstring.h"
94 #include "support/docstring_list.h"
95 #include "support/filetools.h"
96 #include "support/gettext.h"
97 #include "support/lassert.h"
98 #include "support/limited_stack.h"
99 #include "support/lstrings.h"
100 #include "support/lyxtime.h"
101 #include "support/textutils.h"
102 #include "support/unique_ptr.h"
111 using namespace support;
113 using frontend::Clipboard;
118 bool moveItem(Paragraph & fromPar, pos_type fromPos,
119 Paragraph & toPar, pos_type toPos, BufferParams const & params)
121 // Note: moveItem() does not honour change tracking!
122 // Therefore, it should only be used for breaking and merging paragraphs
124 // We need a copy here because the character at fromPos is going to be erased.
125 Font const tmpFont = fromPar.getFontSettings(params, fromPos);
126 Change const tmpChange = fromPar.lookupChange(fromPos);
128 if (Inset * tmpInset = fromPar.getInset(fromPos)) {
129 fromPar.releaseInset(fromPos);
130 // The inset is not in fromPar any more.
131 if (!toPar.insertInset(toPos, tmpInset, tmpFont, tmpChange)) {
138 char_type const tmpChar = fromPar.getChar(fromPos);
139 fromPar.eraseChar(fromPos, false);
140 toPar.insertChar(toPos, tmpChar, tmpFont, tmpChange);
148 void breakParagraphConservative(BufferParams const & bparams,
149 ParagraphList & pars, pit_type pit, pos_type pos)
151 // create a new paragraph
152 Paragraph & tmp = *pars.insert(pars.iterator_at(pit + 1), Paragraph());
153 Paragraph & par = pars[pit];
155 tmp.setInsetOwner(&par.inInset());
156 tmp.makeSameLayout(par);
158 LASSERT(pos <= par.size(), return);
160 if (pos < par.size()) {
161 // move everything behind the break position to the new paragraph
162 pos_type pos_end = par.size() - 1;
164 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
165 if (moveItem(par, pos, tmp, j, bparams)) {
169 // Move over the end-of-par change information
170 tmp.setChange(tmp.size(), par.lookupChange(par.size()));
171 par.setChange(par.size(), Change(bparams.track_changes ?
172 Change::INSERTED : Change::UNCHANGED));
177 void mergeParagraph(BufferParams const & bparams,
178 ParagraphList & pars, pit_type par_offset)
180 Paragraph & next = pars[par_offset + 1];
181 Paragraph & par = pars[par_offset];
183 pos_type pos_end = next.size() - 1;
184 pos_type pos_insert = par.size();
186 // the imaginary end-of-paragraph character (at par.size()) has to be
187 // marked as unmodified. Otherwise, its change is adopted by the first
188 // character of the next paragraph.
189 if (par.isChanged(par.size())) {
190 LYXERR(Debug::CHANGES,
191 "merging par with inserted/deleted end-of-par character");
192 par.setChange(par.size(), Change(Change::UNCHANGED));
195 Change change = next.lookupChange(next.size());
197 // move the content of the second paragraph to the end of the first one
198 for (pos_type i = 0, j = pos_insert; i <= pos_end; ++i) {
199 if (moveItem(next, 0, par, j, bparams)) {
204 // move the change of the end-of-paragraph character
205 par.setChange(par.size(), change);
207 pars.erase(pars.iterator_at(par_offset + 1));
211 Text::Text(InsetText * owner, bool use_default_layout)
214 pars_.push_back(Paragraph());
215 Paragraph & par = pars_.back();
216 par.setInsetOwner(owner);
217 DocumentClass const & dc = owner->buffer().params().documentClass();
218 if (use_default_layout)
219 par.setDefaultLayout(dc);
221 par.setPlainLayout(dc);
225 Text::Text(InsetText * owner, Text const & text)
226 : owner_(owner), pars_(text.pars_)
228 for (auto & p : pars_)
229 p.setInsetOwner(owner);
233 pit_type Text::depthHook(pit_type pit, depth_type depth) const
235 pit_type newpit = pit;
240 while (newpit != 0 && pars_[newpit].getDepth() > depth)
243 if (pars_[newpit].getDepth() > depth)
250 pit_type Text::outerHook(pit_type par_offset) const
252 Paragraph const & par = pars_[par_offset];
254 if (par.getDepth() == 0)
256 return depthHook(par_offset, par.getDepth() - 1);
260 bool Text::isFirstInSequence(pit_type par_offset) const
262 Paragraph const & par = pars_[par_offset];
264 pit_type dhook_offset = depthHook(par_offset, par.getDepth());
266 if (dhook_offset == par_offset)
269 Paragraph const & dhook = pars_[dhook_offset];
271 return dhook.layout() != par.layout()
272 || dhook.getDepth() != par.getDepth();
276 pit_type Text::lastInSequence(pit_type pit) const
278 depth_type const depth = pars_[pit].getDepth();
279 pit_type newpit = pit;
281 while (size_t(newpit + 1) < pars_.size() &&
282 (pars_[newpit + 1].getDepth() > depth ||
283 (pars_[newpit + 1].getDepth() == depth &&
284 pars_[newpit + 1].layout() == pars_[pit].layout())))
291 int Text::getTocLevel(pit_type par_offset) const
293 Paragraph const & par = pars_[par_offset];
295 if (par.layout().isEnvironment() && !isFirstInSequence(par_offset))
296 return Layout::NOT_IN_TOC;
298 return par.layout().toclevel;
302 Font const Text::outerFont(pit_type par_offset) const
304 depth_type par_depth = pars_[par_offset].getDepth();
305 FontInfo tmpfont = inherit_font;
306 depth_type prev_par_depth = 0;
307 // Resolve against environment font information
308 while (par_offset != pit_type(pars_.size())
309 && par_depth != prev_par_depth
311 && !tmpfont.resolved()) {
312 prev_par_depth = par_depth;
313 par_offset = outerHook(par_offset);
314 if (par_offset != pit_type(pars_.size())) {
315 tmpfont.realize(pars_[par_offset].layout().font);
316 par_depth = pars_[par_offset].getDepth();
320 return Font(tmpfont);
324 int Text::getEndLabel(pit_type p) const
327 depth_type par_depth = pars_[p].getDepth();
328 while (pit != pit_type(pars_.size())) {
329 Layout const & layout = pars_[pit].layout();
330 int const endlabeltype = layout.endlabeltype;
332 if (endlabeltype != END_LABEL_NO_LABEL) {
333 if (p + 1 == pit_type(pars_.size()))
336 depth_type const next_depth =
337 pars_[p + 1].getDepth();
338 if (par_depth > next_depth ||
339 (par_depth == next_depth && layout != pars_[p + 1].layout()))
345 pit = outerHook(pit);
346 if (pit != pit_type(pars_.size()))
347 par_depth = pars_[pit].getDepth();
349 return END_LABEL_NO_LABEL;
353 static void acceptOrRejectChanges(ParagraphList & pars,
354 BufferParams const & bparams, Text::ChangeOp op)
356 pit_type pars_size = static_cast<pit_type>(pars.size());
358 // first, accept or reject changes within each individual
359 // paragraph (do not consider end-of-par)
360 for (pit_type pit = 0; pit < pars_size; ++pit) {
361 // prevent assertion failure
362 if (!pars[pit].empty()) {
363 if (op == Text::ACCEPT)
364 pars[pit].acceptChanges(0, pars[pit].size());
366 pars[pit].rejectChanges(0, pars[pit].size());
370 // next, accept or reject imaginary end-of-par characters
371 for (pit_type pit = 0; pit < pars_size; ++pit) {
372 pos_type pos = pars[pit].size();
373 if (pars[pit].isChanged(pos)) {
374 // keep the end-of-par char if it is inserted and accepted
375 // or when it is deleted and rejected.
376 if (pars[pit].isInserted(pos) == (op == Text::ACCEPT)) {
377 pars[pit].setChange(pos, Change(Change::UNCHANGED));
379 if (pit == pars_size - 1) {
380 // we cannot remove a par break at the end of the last
381 // paragraph; instead, we mark it unchanged
382 pars[pit].setChange(pos, Change(Change::UNCHANGED));
384 mergeParagraph(bparams, pars, pit);
394 void acceptChanges(ParagraphList & pars, BufferParams const & bparams)
396 acceptOrRejectChanges(pars, bparams, Text::ACCEPT);
400 void rejectChanges(ParagraphList & pars, BufferParams const & bparams)
402 acceptOrRejectChanges(pars, bparams, Text::REJECT);
406 InsetText const & Text::inset() const
413 void Text::readParToken(Paragraph & par, Lexer & lex,
414 string const & token, Font & font, Change & change, ErrorList & errorList)
416 Buffer * buf = &owner_->buffer();
417 BufferParams & bp = buf->params();
419 if (token[0] != '\\') {
420 docstring dstr = lex.getDocString();
421 par.appendString(dstr, font, change);
423 } else if (token == "\\begin_layout") {
425 docstring layoutname = lex.getDocString();
427 font = Font(inherit_font, bp.language);
428 change = Change(Change::UNCHANGED);
430 DocumentClass const & tclass = bp.documentClass();
432 if (layoutname.empty())
433 layoutname = tclass.defaultLayoutName();
435 if (owner_->forcePlainLayout()) {
436 // in this case only the empty layout is allowed
437 layoutname = tclass.plainLayoutName();
438 } else if (par.usePlainLayout()) {
439 // in this case, default layout maps to empty layout
440 if (layoutname == tclass.defaultLayoutName())
441 layoutname = tclass.plainLayoutName();
443 // otherwise, the empty layout maps to the default
444 if (layoutname == tclass.plainLayoutName())
445 layoutname = tclass.defaultLayoutName();
448 // When we apply an unknown layout to a document, we add this layout to the textclass
449 // of this document. For example, when you apply class article to a beamer document,
450 // all unknown layouts such as frame will be added to document class article so that
451 // these layouts can keep their original names.
452 bool const added_one = tclass.addLayoutIfNeeded(layoutname);
455 docstring const s = bformat(_("Layout `%1$s' was not found."), layoutname);
456 errorList.push_back(ErrorItem(_("Layout Not Found"), s,
457 {par.id(), 0}, {par.id(), -1}));
460 par.setLayout(bp.documentClass()[layoutname]);
462 // Test whether the layout is obsolete.
463 Layout const & layout = par.layout();
464 if (!layout.obsoleted_by().empty())
465 par.setLayout(bp.documentClass()[layout.obsoleted_by()]);
467 par.params().read(lex);
469 } else if (token == "\\end_layout") {
470 LYXERR0("Solitary \\end_layout in line " << lex.lineNumber() << "\n"
471 << "Missing \\begin_layout ?");
472 } else if (token == "\\end_inset") {
473 LYXERR0("Solitary \\end_inset in line " << lex.lineNumber() << "\n"
474 << "Missing \\begin_inset ?");
475 } else if (token == "\\begin_inset") {
476 Inset * inset = readInset(lex, buf);
478 par.insertInset(par.size(), inset, font, change);
481 docstring line = lex.getDocString();
482 errorList.push_back(ErrorItem(_("Unknown Inset"), line,
483 {par.id(), 0}, {par.id(), -1}));
485 } else if (token == "\\family") {
487 setLyXFamily(lex.getString(), font.fontInfo());
488 } else if (token == "\\series") {
490 setLyXSeries(lex.getString(), font.fontInfo());
491 } else if (token == "\\shape") {
493 setLyXShape(lex.getString(), font.fontInfo());
494 } else if (token == "\\size") {
496 setLyXSize(lex.getString(), font.fontInfo());
497 } else if (token == "\\lang") {
499 string const tok = lex.getString();
500 Language const * lang = languages.getLanguage(tok);
502 font.setLanguage(lang);
504 font.setLanguage(bp.language);
505 lex.printError("Unknown language `$$Token'");
507 } else if (token == "\\numeric") {
509 font.fontInfo().setNumber(setLyXMisc(lex.getString()));
510 } else if (token == "\\nospellcheck") {
512 font.fontInfo().setNoSpellcheck(setLyXMisc(lex.getString()));
513 } else if (token == "\\emph") {
515 font.fontInfo().setEmph(setLyXMisc(lex.getString()));
516 } else if (token == "\\bar") {
518 string const tok = lex.getString();
521 font.fontInfo().setUnderbar(FONT_ON);
522 else if (tok == "no")
523 font.fontInfo().setUnderbar(FONT_OFF);
524 else if (tok == "default")
525 font.fontInfo().setUnderbar(FONT_INHERIT);
527 lex.printError("Unknown bar font flag "
529 } else if (token == "\\strikeout") {
531 font.fontInfo().setStrikeout(setLyXMisc(lex.getString()));
532 } else if (token == "\\xout") {
534 font.fontInfo().setXout(setLyXMisc(lex.getString()));
535 } else if (token == "\\uuline") {
537 font.fontInfo().setUuline(setLyXMisc(lex.getString()));
538 } else if (token == "\\uwave") {
540 font.fontInfo().setUwave(setLyXMisc(lex.getString()));
541 } else if (token == "\\noun") {
543 font.fontInfo().setNoun(setLyXMisc(lex.getString()));
544 } else if (token == "\\color") {
546 setLyXColor(lex.getString(), font.fontInfo());
547 } else if (token == "\\SpecialChar" ||
548 (token == "\\SpecialCharNoPassThru" &&
549 !par.layout().pass_thru && !inset().isPassThru())) {
550 auto inset = make_unique<InsetSpecialChar>();
552 inset->setBuffer(*buf);
553 par.insertInset(par.size(), inset.release(), font, change);
554 } else if (token == "\\SpecialCharNoPassThru") {
556 docstring const s = ltrim(lex.getDocString(), "\\");
557 par.insert(par.size(), s, font, change);
558 } else if (token == "\\IPAChar") {
559 auto inset = make_unique<InsetIPAChar>();
561 inset->setBuffer(*buf);
562 par.insertInset(par.size(), inset.release(), font, change);
563 } else if (token == "\\twohyphens" || token == "\\threehyphens") {
564 // Ideally, this should be done by lyx2lyx, but lyx2lyx does not know the
565 // running font and does not know anything about layouts (and CopyStyle).
566 Layout const & layout(par.layout());
567 FontInfo info = font.fontInfo();
568 info.realize(layout.resfont);
569 if (layout.pass_thru || inset().isPassThru() ||
570 info.family() == TYPEWRITER_FAMILY) {
571 if (token == "\\twohyphens")
572 par.insert(par.size(), from_ascii("--"), font, change);
574 par.insert(par.size(), from_ascii("---"), font, change);
576 if (token == "\\twohyphens")
577 par.insertChar(par.size(), 0x2013, font, change);
579 par.insertChar(par.size(), 0x2014, font, change);
581 } else if (token == "\\backslash") {
582 par.appendChar('\\', font, change);
583 } else if (token == "\\LyXTable") {
584 auto inset = make_unique<InsetTabular>(buf);
586 par.insertInset(par.size(), inset.release(), font, change);
587 } else if (token == "\\change_unchanged") {
588 change = Change(Change::UNCHANGED);
589 } else if (token == "\\change_inserted" || token == "\\change_deleted") {
591 istringstream is(lex.getString());
595 BufferParams::AuthorMap const & am = bp.author_map_;
596 if (am.find(aid) == am.end()) {
597 errorList.push_back(ErrorItem(
598 _("Change tracking author index missing"),
599 bformat(_("A change tracking author information for index "
600 "%1$d is missing. This can happen after a wrong "
601 "merge by a version control system. In this case, "
602 "either fix the merge, or have this information "
603 "missing until the corresponding tracked changes "
604 "are merged or this user edits the file again.\n"),
606 {par.id(), par.size()}, {par.id(), par.size() + 1}));
607 bp.addAuthor(Author(aid));
609 if (token == "\\change_inserted")
610 change = Change(Change::INSERTED, am.find(aid)->second, ct);
612 change = Change(Change::DELETED, am.find(aid)->second, ct);
615 errorList.push_back(ErrorItem(_("Unknown token"),
616 bformat(_("Unknown token: %1$s %2$s\n"),
619 {par.id(), 0}, {par.id(), -1}));
624 void Text::readParagraph(Paragraph & par, Lexer & lex,
625 ErrorList & errorList)
628 string token = lex.getString();
630 Change change(Change::UNCHANGED);
633 readParToken(par, lex, token, font, change, errorList);
636 token = lex.getString();
641 if (token == "\\end_layout") {
642 //Ok, paragraph finished
646 LYXERR(Debug::PARSER, "Handling paragraph token: `" << token << '\'');
647 if (token == "\\begin_layout" || token == "\\end_document"
648 || token == "\\end_inset" || token == "\\begin_deeper"
649 || token == "\\end_deeper") {
650 lex.pushToken(token);
651 lyxerr << "Paragraph ended in line "
652 << lex.lineNumber() << "\n"
653 << "Missing \\end_layout.\n";
657 // Final change goes to paragraph break:
658 if (inset().allowMultiPar())
659 par.setChange(par.size(), change);
661 // Initialize begin_of_body_ on load; redoParagraph maintains
662 par.setBeginOfBody();
664 // mark paragraph for spell checking on load
665 // par.requestSpellCheck();
669 class TextCompletionList : public CompletionList
673 TextCompletionList(Cursor const & cur, WordList const & list)
674 : buffer_(cur.buffer()), list_(list)
677 virtual ~TextCompletionList() {}
680 bool sorted() const override { return true; }
682 size_t size() const override
687 docstring const & data(size_t idx) const override
689 return list_.word(idx);
694 Buffer const * buffer_;
696 WordList const & list_;
700 bool Text::empty() const
702 return pars_.empty() || (pars_.size() == 1 && pars_[0].empty()
703 // FIXME: Should we consider the labeled type as empty too?
704 && pars_[0].layout().labeltype == LABEL_NO_LABEL);
708 double Text::spacing(Paragraph const & par) const
710 if (par.params().spacing().isDefault())
711 return owner_->buffer().params().spacing().getValue();
712 return par.params().spacing().getValue();
717 * This breaks a paragraph at the specified position.
718 * The new paragraph will:
719 * - change layout to default layout when keep_layout == false
720 * - keep layout when keep_layout == true
722 static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
725 BufferParams const & bparams = text.inset().buffer().params();
726 ParagraphList & pars = text.paragraphs();
727 // create a new paragraph, and insert into the list
728 ParagraphList::iterator tmp =
729 pars.insert(pars.iterator_at(par_offset + 1), Paragraph());
731 Paragraph & par = pars[par_offset];
733 // remember to set the inset_owner
734 tmp->setInsetOwner(&par.inInset());
735 tmp->params().depth(par.params().depth());
738 tmp->setLayout(par.layout());
739 tmp->setLabelWidthString(par.params().labelWidthString());
741 tmp->setPlainOrDefaultLayout(bparams.documentClass());
743 bool const isempty = (par.allowEmpty() && par.empty());
745 if (!isempty && (par.size() > pos || par.empty())) {
746 tmp->setLayout(par.layout());
747 tmp->params().align(par.params().align());
748 tmp->setLabelWidthString(par.params().labelWidthString());
750 tmp->params().depth(par.params().depth());
751 tmp->params().noindent(par.params().noindent());
752 tmp->params().spacing(par.params().spacing());
754 // move everything behind the break position
755 // to the new paragraph
757 /* Note: if !keepempty, empty() == true, then we reach
758 * here with size() == 0. So pos_end becomes - 1. This
759 * doesn't cause problems because both loops below
760 * enforce pos <= pos_end and 0 <= pos
762 pos_type pos_end = par.size() - 1;
764 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
765 if (moveItem(par, pos, *tmp, j, bparams)) {
771 // Move over the end-of-par change information
772 tmp->setChange(tmp->size(), par.lookupChange(par.size()));
773 par.setChange(par.size(), Change(bparams.track_changes ?
774 Change::INSERTED : Change::UNCHANGED));
777 // Make sure that we keep the language when
778 // breaking paragraph.
780 Font changed = tmp->getFirstFontSettings(bparams);
781 Font const & old = par.getFontSettings(bparams, par.size());
782 changed.setLanguage(old.language());
783 tmp->setFont(0, changed);
790 bool const soa = par.params().startOfAppendix();
791 par.params().clear();
792 // do not lose start of appendix marker (bug 4212)
793 par.params().startOfAppendix(soa);
794 par.setPlainOrDefaultLayout(bparams.documentClass());
798 par.setLayout(tmp->layout());
799 par.setLabelWidthString(tmp->params().labelWidthString());
800 par.params().depth(tmp->params().depth());
805 void Text::breakParagraph(Cursor & cur, bool inverse_logic)
807 LBUFERR(this == cur.text());
809 Paragraph & cpar = cur.paragraph();
810 pit_type cpit = cur.pit();
812 DocumentClass const & tclass = cur.buffer()->params().documentClass();
813 Layout const & layout = cpar.layout();
815 if (cur.lastpos() == 0 && !cpar.allowEmpty()) {
816 if (changeDepthAllowed(cur, DEC_DEPTH)) {
817 changeDepth(cur, DEC_DEPTH);
818 pit_type const prev = depthHook(cpit, cpar.getDepth());
819 docstring const & lay = pars_[prev].layout().name();
820 if (lay != layout.name())
823 docstring const & lay = cur.paragraph().usePlainLayout()
824 ? tclass.plainLayoutName() : tclass.defaultLayoutName();
825 if (lay != layout.name())
833 // Always break behind a space
834 // It is better to erase the space (Dekel)
835 if (cur.pos() != cur.lastpos() && cpar.isLineSeparator(cur.pos()))
836 cpar.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
838 // What should the layout for the new paragraph be?
839 bool keep_layout = layout.isEnvironment()
840 || (layout.isParagraph() && layout.parbreak_is_newline);
842 keep_layout = !keep_layout;
844 // We need to remember this before we break the paragraph, because
845 // that invalidates the layout variable
846 bool sensitive = layout.labeltype == LABEL_SENSITIVE;
848 // we need to set this before we insert the paragraph.
849 bool const isempty = cpar.allowEmpty() && cpar.empty();
851 lyx::breakParagraph(*this, cpit, cur.pos(), keep_layout);
853 // After this, neither paragraph contains any rows!
856 pit_type next_par = cpit + 1;
858 // well this is the caption hack since one caption is really enough
861 // set to standard-layout
862 //FIXME Check if this should be plainLayout() in some cases
863 pars_[cpit].applyLayout(tclass.defaultLayout());
865 // set to standard-layout
866 //FIXME Check if this should be plainLayout() in some cases
867 pars_[next_par].applyLayout(tclass.defaultLayout());
870 while (!pars_[next_par].empty() && pars_[next_par].isNewline(0)) {
871 if (!pars_[next_par].eraseChar(0, cur.buffer()->params().track_changes))
872 break; // the character couldn't be deleted physically due to change tracking
875 // A singlePar update is not enough in this case.
876 cur.screenUpdateFlags(Update::Force);
877 cur.forceBufferUpdate();
879 // This check is necessary. Otherwise the new empty paragraph will
880 // be deleted automatically. And it is more friendly for the user!
881 if (cur.pos() != 0 || isempty)
882 setCursor(cur, cur.pit() + 1, 0);
884 setCursor(cur, cur.pit(), 0);
888 // needed to insert the selection
889 void Text::insertStringAsLines(Cursor & cur, docstring const & str,
892 BufferParams const & bparams = owner_->buffer().params();
893 pit_type pit = cur.pit();
894 pos_type pos = cur.pos();
896 // The special chars we handle
897 static map<wchar_t, InsetSpecialChar::Kind> specialchars = {
898 { 0x200c, InsetSpecialChar::LIGATURE_BREAK },
899 { 0x200b, InsetSpecialChar::ALLOWBREAK },
900 { 0x2026, InsetSpecialChar::LDOTS },
901 { 0x2011, InsetSpecialChar::NOBREAKDASH }
904 // insert the string, don't insert doublespace
905 bool space_inserted = true;
906 for (auto const & ch : str) {
907 Paragraph & par = pars_[pit];
909 if (inset().allowMultiPar() && (!par.empty() || par.allowEmpty())) {
910 lyx::breakParagraph(*this, pit, pos,
911 par.layout().isEnvironment());
914 space_inserted = true;
918 // do not insert consecutive spaces if !free_spacing
919 } else if ((ch == ' ' || ch == '\t') &&
920 space_inserted && !par.isFreeSpacing()) {
922 } else if (ch == '\t') {
923 if (!par.isFreeSpacing()) {
924 // tabs are like spaces here
925 par.insertChar(pos, ' ', font, bparams.track_changes);
927 space_inserted = true;
929 par.insertChar(pos, ch, font, bparams.track_changes);
931 space_inserted = true;
933 } else if (specialchars.find(ch) != specialchars.end()
934 && (par.insertInset(pos, new InsetSpecialChar(specialchars.find(ch)->second),
935 font, bparams.track_changes
936 ? Change(Change::INSERTED)
937 : Change(Change::UNCHANGED)))) {
939 space_inserted = false;
940 } else if (!isPrintable(ch)) {
941 // Ignore (other) unprintables
944 // just insert the character
945 par.insertChar(pos, ch, font, bparams.track_changes);
947 space_inserted = (ch == ' ');
950 setCursor(cur, pit, pos);
954 // turn double CR to single CR, others are converted into one
955 // blank. Then insertStringAsLines is called
956 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str,
959 docstring linestr = str;
960 bool newline_inserted = false;
962 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
963 if (linestr[i] == '\n') {
964 if (newline_inserted) {
965 // we know that \r will be ignored by
966 // insertStringAsLines. Of course, it is a dirty
967 // trick, but it works...
968 linestr[i - 1] = '\r';
972 newline_inserted = true;
974 } else if (isPrintable(linestr[i])) {
975 newline_inserted = false;
978 insertStringAsLines(cur, linestr, font);
984 bool canInsertChar(Cursor const & cur, char_type c)
986 Paragraph const & par = cur.paragraph();
987 // If not in free spacing mode, check if there will be two blanks together or a blank at
988 // the beginning of a paragraph.
989 if (!par.isFreeSpacing() && isLineSeparatorChar(c)) {
990 if (cur.pos() == 0) {
992 "You cannot insert a space at the "
993 "beginning of a paragraph. Please read the Tutorial."));
996 // If something is wrong, ignore this character.
997 LASSERT(cur.pos() > 0, return false);
998 if ((par.isLineSeparator(cur.pos() - 1) || par.isNewline(cur.pos() - 1))
999 && !par.isDeleted(cur.pos() - 1)) {
1001 "You cannot type two spaces this way. "
1002 "Please read the Tutorial."));
1007 // Prevent to insert uncodable characters in verbatim and ERT.
1008 // The encoding is inherited from the context here.
1009 if (par.isPassThru() && cur.getEncoding()) {
1010 Encoding const * e = cur.getEncoding();
1011 if (!e->encodable(c)) {
1012 cur.message(_("Character is uncodable in this verbatim context."));
1022 // insert a character, moves all the following breaks in the
1023 // same Paragraph one to the right and make a rebreak
1024 void Text::insertChar(Cursor & cur, char_type c)
1026 LBUFERR(this == cur.text());
1028 if (!canInsertChar(cur,c))
1031 cur.recordUndo(INSERT_UNDO);
1033 TextMetrics const & tm = cur.bv().textMetrics(this);
1034 Buffer const & buffer = *cur.buffer();
1035 Paragraph & par = cur.paragraph();
1036 // try to remove this
1037 pit_type const pit = cur.pit();
1039 if (lyxrc.auto_number) {
1040 static docstring const number_operators = from_ascii("+-/*");
1041 static docstring const number_unary_operators = from_ascii("+-");
1043 // Common Number Separators: comma, dot etc.
1044 // European Number Terminators: percent, permille, degree, euro etc.
1045 if (cur.current_font.fontInfo().number() == FONT_ON) {
1046 if (!isDigitASCII(c) && !contains(number_operators, c) &&
1047 !(isCommonNumberSeparator(c) &&
1049 cur.pos() != cur.lastpos() &&
1050 tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1051 tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON) &&
1052 !(isEuropeanNumberTerminator(c) &&
1054 tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1055 tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON)
1057 number(cur); // Set current_font.number to OFF
1058 } else if (isDigitASCII(c) &&
1059 cur.real_current_font.isVisibleRightToLeft()) {
1060 number(cur); // Set current_font.number to ON
1062 if (cur.pos() != 0) {
1063 char_type const ch = par.getChar(cur.pos() - 1);
1064 if (contains(number_unary_operators, ch) &&
1066 || par.isSeparator(cur.pos() - 2)
1067 || par.isEnvSeparator(cur.pos() - 2)
1068 || par.isNewline(cur.pos() - 2))
1070 setCharFont(pit, cur.pos() - 1, cur.current_font,
1072 } else if (isCommonNumberSeparator(ch)
1074 && tm.displayFont(pit, cur.pos() - 2).fontInfo().number() == FONT_ON) {
1075 setCharFont(pit, cur.pos() - 1, cur.current_font,
1082 // In Bidi text, we want spaces to be treated in a special way: spaces
1083 // which are between words in different languages should get the
1084 // paragraph's language; otherwise, spaces should keep the language
1085 // they were originally typed in. This is only in effect while typing;
1086 // after the text is already typed in, the user can always go back and
1087 // explicitly set the language of a space as desired. But 99.9% of the
1088 // time, what we're doing here is what the user actually meant.
1090 // The following cases are the ones in which the language of the space
1091 // should be changed to match that of the containing paragraph. In the
1092 // depictions, lowercase is LTR, uppercase is RTL, underscore (_)
1093 // represents a space, pipe (|) represents the cursor position (so the
1094 // character before it is the one just typed in). The different cases
1095 // are depicted logically (not visually), from left to right:
1100 // Theoretically, there are other situations that we should, perhaps, deal
1101 // with (e.g.: a|_A, A|_a). In practice, though, there really isn't any
1102 // point (to understand why, just try to create this situation...).
1104 if ((cur.pos() >= 2) && (par.isLineSeparator(cur.pos() - 1))) {
1105 // get font in front and behind the space in question. But do NOT
1106 // use getFont(cur.pos()) because the character c is not inserted yet
1107 Font const pre_space_font = tm.displayFont(cur.pit(), cur.pos() - 2);
1108 Font const & post_space_font = cur.real_current_font;
1109 bool pre_space_rtl = pre_space_font.isVisibleRightToLeft();
1110 bool post_space_rtl = post_space_font.isVisibleRightToLeft();
1112 if (pre_space_rtl != post_space_rtl) {
1113 // Set the space's language to match the language of the
1114 // adjacent character whose direction is the paragraph's
1115 // direction; don't touch other properties of the font
1116 Language const * lang =
1117 (pre_space_rtl == par.isRTL(buffer.params())) ?
1118 pre_space_font.language() : post_space_font.language();
1120 Font space_font = tm.displayFont(cur.pit(), cur.pos() - 1);
1121 space_font.setLanguage(lang);
1122 par.setFont(cur.pos() - 1, space_font);
1126 pos_type pos = cur.pos();
1127 if (!cur.paragraph().isPassThru() && owner_->lyxCode() != IPA_CODE &&
1128 cur.real_current_font.fontInfo().family() != TYPEWRITER_FAMILY &&
1129 c == '-' && pos > 0) {
1130 if (par.getChar(pos - 1) == '-') {
1131 // convert "--" to endash
1132 par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1135 } else if (par.getChar(pos - 1) == 0x2013) {
1136 // convert "---" to emdash
1137 par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1143 par.insertChar(pos, c, cur.current_font,
1144 cur.buffer()->params().track_changes);
1145 cur.checkBufferStructure();
1147 // cur.screenUpdateFlags(Update::Force);
1148 bool boundary = cur.boundary()
1149 || tm.isRTLBoundary(cur.pit(), pos + 1);
1150 setCursor(cur, cur.pit(), pos + 1, false, boundary);
1155 void Text::charInserted(Cursor & cur)
1157 Paragraph & par = cur.paragraph();
1159 // register word if a non-letter was entered
1161 && !par.isWordSeparator(cur.pos() - 2)
1162 && par.isWordSeparator(cur.pos() - 1)) {
1163 // get the word in front of cursor
1164 LBUFERR(this == cur.text());
1170 // the cursor set functions have a special mechanism. When they
1171 // realize, that you left an empty paragraph, they will delete it.
1173 bool Text::cursorForwardOneWord(Cursor & cur)
1175 LBUFERR(this == cur.text());
1177 if (lyxrc.mac_like_cursor_movement) {
1178 DocIterator dit(cur);
1179 DocIterator prv(cur);
1180 bool inword = false;
1181 bool intext = dit.inTexted();
1182 while (!dit.atEnd()) {
1183 if (dit.inTexted()) { // no paragraphs in mathed
1184 Paragraph const & par = dit.paragraph();
1185 pos_type const pos = dit.pos();
1187 if (!par.isDeleted(pos)) {
1188 bool wordsep = par.isWordSeparator(pos);
1189 if (inword && wordsep)
1190 break; // stop at word end
1191 else if (!inword && !wordsep)
1195 } else if (intext) {
1196 // move to end of math
1197 while (!dit.inTexted() && !dit.atEnd()) dit.forwardPos();
1201 dit.forwardPosIgnoreCollapsed();
1203 if (dit.atEnd()) dit = prv;
1204 if (dit == cur) return false; // we didn't move
1207 // see comment above
1208 cur.bv().checkDepm(cur, orig);
1211 pos_type const lastpos = cur.lastpos();
1212 pit_type pit = cur.pit();
1213 pos_type pos = cur.pos();
1214 Paragraph const & par = cur.paragraph();
1216 // Paragraph boundary is a word boundary
1217 if (pos == lastpos || (pos + 1 == lastpos && par.isEnvSeparator(pos))) {
1218 if (pit != cur.lastpit())
1219 return setCursor(cur, pit + 1, 0);
1224 LASSERT(pos < lastpos, return false); // see above
1225 if (!par.isWordSeparator(pos))
1226 while (pos != lastpos && !par.isWordSeparator(pos))
1228 else if (par.isChar(pos))
1229 while (pos != lastpos && par.isChar(pos))
1231 else if (!par.isSpace(pos)) // non-char inset
1234 // Skip over white space
1235 while (pos != lastpos && par.isSpace(pos))
1238 // Don't skip a separator inset at the end of a paragraph
1239 if (pos == lastpos && pos && par.isEnvSeparator(pos - 1))
1242 return setCursor(cur, pit, pos);
1247 bool Text::cursorBackwardOneWord(Cursor & cur)
1249 LBUFERR(this == cur.text());
1251 if (lyxrc.mac_like_cursor_movement) {
1252 DocIterator dit(cur);
1253 bool inword = false;
1254 bool intext = dit.inTexted();
1255 while (!dit.atBegin()) {
1256 DocIterator prv(dit);
1257 dit.backwardPosIgnoreCollapsed();
1258 if (dit.inTexted()) { // no paragraphs in mathed
1259 Paragraph const & par = dit.paragraph();
1260 pos_type pos = dit.pos();
1262 if (!par.isDeleted(pos)) {
1263 bool wordsep = par.isWordSeparator(pos);
1264 if (inword && wordsep) {
1266 break; // stop at word begin
1267 } else if (!inword && !wordsep)
1271 } else if (intext) {
1272 // move to begin of math
1273 while (!dit.inTexted() && !dit.atBegin()) dit.backwardPos();
1277 if (dit == cur) return false; // we didn't move
1280 // see comment above cursorForwardOneWord
1281 cur.bv().checkDepm(cur, orig);
1284 Paragraph const & par = cur.paragraph();
1285 pit_type const pit = cur.pit();
1286 pos_type pos = cur.pos();
1288 // Paragraph boundary is a word boundary
1289 if (pos == 0 && pit != 0) {
1290 Paragraph & prevpar = getPar(pit - 1);
1291 pos = prevpar.size();
1292 // Don't stop after an environment separator
1293 if (pos && prevpar.isEnvSeparator(pos - 1))
1295 return setCursor(cur, pit - 1, pos);
1297 // Skip over white space
1298 while (pos != 0 && par.isSpace(pos - 1))
1301 if (pos != 0 && !par.isWordSeparator(pos - 1))
1302 while (pos != 0 && !par.isWordSeparator(pos - 1))
1304 else if (pos != 0 && par.isChar(pos - 1))
1305 while (pos != 0 && par.isChar(pos - 1))
1307 else if (pos != 0 && !par.isSpace(pos - 1)) // non-char inset
1310 return setCursor(cur, pit, pos);
1315 bool Text::cursorVisLeftOneWord(Cursor & cur)
1317 LBUFERR(this == cur.text());
1319 pos_type left_pos, right_pos;
1321 Cursor temp_cur = cur;
1323 // always try to move at least once...
1324 while (temp_cur.posVisLeft(true /* skip_inset */)) {
1326 // collect some information about current cursor position
1327 temp_cur.getSurroundingPos(left_pos, right_pos);
1328 bool left_is_letter =
1329 (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1330 bool right_is_letter =
1331 (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1333 // if we're not at a letter/non-letter boundary, continue moving
1334 if (left_is_letter == right_is_letter)
1337 // we should stop when we have an LTR word on our right or an RTL word
1339 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1340 temp_cur.buffer()->params(), left_pos).isRightToLeft())
1341 || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1342 temp_cur.buffer()->params(), right_pos).isRightToLeft()))
1346 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1347 true, temp_cur.boundary());
1351 bool Text::cursorVisRightOneWord(Cursor & cur)
1353 LBUFERR(this == cur.text());
1355 pos_type left_pos, right_pos;
1357 Cursor temp_cur = cur;
1359 // always try to move at least once...
1360 while (temp_cur.posVisRight(true /* skip_inset */)) {
1362 // collect some information about current cursor position
1363 temp_cur.getSurroundingPos(left_pos, right_pos);
1364 bool left_is_letter =
1365 (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1366 bool right_is_letter =
1367 (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1369 // if we're not at a letter/non-letter boundary, continue moving
1370 if (left_is_letter == right_is_letter)
1373 // we should stop when we have an LTR word on our right or an RTL word
1375 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1376 temp_cur.buffer()->params(),
1377 left_pos).isRightToLeft())
1378 || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1379 temp_cur.buffer()->params(),
1380 right_pos).isRightToLeft()))
1384 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1385 true, temp_cur.boundary());
1389 void Text::selectWord(Cursor & cur, word_location loc)
1391 LBUFERR(this == cur.text());
1392 CursorSlice from = cur.top();
1394 getWord(from, to, loc);
1395 if (cur.top() != from)
1396 setCursor(cur, from.pit(), from.pos());
1399 if (!cur.selection())
1401 setCursor(cur, to.pit(), to.pos());
1403 cur.setWordSelection(true);
1407 void Text::expandWordSel(Cursor & cur)
1409 // get selection of word around cur
1412 c.text()->selectWord(c, WHOLE_WORD);
1413 // use the correct word boundary, depending on selection direction
1414 if (cur.top() > cur.normalAnchor())
1415 cur.pos() = c.selEnd().pos();
1417 cur.pos() = c.selBegin().pos();
1421 void Text::selectAll(Cursor & cur)
1423 LBUFERR(this == cur.text());
1424 if (cur.lastpos() == 0 && cur.lastpit() == 0)
1426 // If the cursor is at the beginning, make sure the cursor ends there
1427 if (cur.pit() == 0 && cur.pos() == 0) {
1428 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1430 setCursor(cur, 0, 0);
1432 setCursor(cur, 0, 0);
1434 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1440 // Select the word currently under the cursor when no
1441 // selection is currently set
1442 bool Text::selectWordWhenUnderCursor(Cursor & cur, word_location loc)
1444 LBUFERR(this == cur.text());
1445 if (cur.selection())
1447 selectWord(cur, loc);
1448 return cur.selection();
1452 void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
1454 LBUFERR(this == cur.text());
1456 if (!cur.selection()) {
1457 if (!selectChange(cur))
1461 cur.recordUndoSelection();
1463 pit_type begPit = cur.selectionBegin().pit();
1464 pit_type endPit = cur.selectionEnd().pit();
1466 pos_type begPos = cur.selectionBegin().pos();
1467 pos_type endPos = cur.selectionEnd().pos();
1469 // keep selection info, because endPos becomes invalid after the first loop
1470 bool const endsBeforeEndOfPar = (endPos < pars_[endPit].size());
1472 // first, accept/reject changes within each individual paragraph (do not consider end-of-par)
1473 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1474 pos_type parSize = pars_[pit].size();
1476 // ignore empty paragraphs; otherwise, an assertion will fail for
1477 // acceptChanges(bparams, 0, 0) or rejectChanges(bparams, 0, 0)
1481 // do not consider first paragraph if the cursor starts at pos size()
1482 if (pit == begPit && begPos == parSize)
1485 // do not consider last paragraph if the cursor ends at pos 0
1486 if (pit == endPit && endPos == 0)
1487 break; // last iteration anyway
1489 pos_type const left = (pit == begPit ? begPos : 0);
1490 pos_type const right = (pit == endPit ? endPos : parSize);
1493 // there is no change here
1497 pars_[pit].acceptChanges(left, right);
1499 pars_[pit].rejectChanges(left, right);
1503 // next, accept/reject imaginary end-of-par characters
1505 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1506 pos_type pos = pars_[pit].size();
1508 // skip if the selection ends before the end-of-par
1509 if (pit == endPit && endsBeforeEndOfPar)
1510 break; // last iteration anyway
1512 // skip if this is not the last paragraph of the document
1513 // note: the user should be able to accept/reject the par break of the last par!
1514 if (pit == endPit && pit + 1 != int(pars_.size()))
1515 break; // last iteration anway
1518 if (pars_[pit].isInserted(pos)) {
1519 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1520 } else if (pars_[pit].isDeleted(pos)) {
1521 if (pit + 1 == int(pars_.size())) {
1522 // we cannot remove a par break at the end of the last paragraph;
1523 // instead, we mark it unchanged
1524 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1526 mergeParagraph(cur.buffer()->params(), pars_, pit);
1532 if (pars_[pit].isDeleted(pos)) {
1533 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1534 } else if (pars_[pit].isInserted(pos)) {
1535 if (pit + 1 == int(pars_.size())) {
1536 // we mark the par break at the end of the last paragraph unchanged
1537 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1539 mergeParagraph(cur.buffer()->params(), pars_, pit);
1547 // finally, invoke the DEPM
1548 deleteEmptyParagraphMechanism(begPit, endPit, begPos, endPos,
1549 cur.buffer()->params().track_changes);
1552 cur.clearSelection();
1553 setCursorIntern(cur, begPit, begPos);
1554 cur.screenUpdateFlags(Update::Force);
1555 cur.forceBufferUpdate();
1559 void Text::acceptChanges()
1561 BufferParams const & bparams = owner_->buffer().params();
1562 lyx::acceptChanges(pars_, bparams);
1563 deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.track_changes);
1567 void Text::rejectChanges()
1569 BufferParams const & bparams = owner_->buffer().params();
1570 pit_type pars_size = static_cast<pit_type>(pars_.size());
1572 // first, reject changes within each individual paragraph
1573 // (do not consider end-of-par)
1574 for (pit_type pit = 0; pit < pars_size; ++pit) {
1575 if (!pars_[pit].empty()) // prevent assertion failure
1576 pars_[pit].rejectChanges(0, pars_[pit].size());
1579 // next, reject imaginary end-of-par characters
1580 for (pit_type pit = 0; pit < pars_size; ++pit) {
1581 pos_type pos = pars_[pit].size();
1583 if (pars_[pit].isDeleted(pos)) {
1584 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1585 } else if (pars_[pit].isInserted(pos)) {
1586 if (pit == pars_size - 1) {
1587 // we mark the par break at the end of the last
1588 // paragraph unchanged
1589 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1591 mergeParagraph(bparams, pars_, pit);
1598 // finally, invoke the DEPM
1599 deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.track_changes);
1603 void Text::deleteWordForward(Cursor & cur, bool const force)
1605 LBUFERR(this == cur.text());
1606 if (cur.lastpos() == 0)
1610 cur.selection(true);
1611 cursorForwardOneWord(cur);
1613 if (force || !cur.confirmDeletion()) {
1614 cutSelection(cur, false);
1615 cur.checkBufferStructure();
1621 void Text::deleteWordBackward(Cursor & cur, bool const force)
1623 LBUFERR(this == cur.text());
1624 if (cur.lastpos() == 0)
1625 cursorBackward(cur);
1628 cur.selection(true);
1629 cursorBackwardOneWord(cur);
1631 if (force || !cur.confirmDeletion()) {
1632 cutSelection(cur, false);
1633 cur.checkBufferStructure();
1639 // Kill to end of line.
1640 void Text::changeCase(Cursor & cur, TextCase action, bool partial)
1642 LBUFERR(this == cur.text());
1646 bool const gotsel = cur.selection();
1648 from = cur.selBegin();
1652 getWord(from, to, partial ? PARTIAL_WORD : WHOLE_WORD);
1653 cursorForwardOneWord(cur);
1656 cur.recordUndoSelection();
1658 pit_type begPit = from.pit();
1659 pit_type endPit = to.pit();
1661 pos_type begPos = from.pos();
1662 pos_type endPos = to.pos();
1664 pos_type right = 0; // needed after the for loop
1666 for (pit_type pit = begPit; pit <= endPit; ++pit) {
1667 Paragraph & par = pars_[pit];
1668 pos_type const pos = (pit == begPit ? begPos : 0);
1669 right = (pit == endPit ? endPos : par.size());
1670 par.changeCase(cur.buffer()->params(), pos, right, action);
1673 // the selection may have changed due to logically-only deleted chars
1675 setCursor(cur, begPit, begPos);
1677 setCursor(cur, endPit, right);
1680 setCursor(cur, endPit, right);
1682 cur.checkBufferStructure();
1686 bool Text::handleBibitems(Cursor & cur)
1688 if (cur.paragraph().layout().labeltype != LABEL_BIBLIO)
1694 BufferParams const & bufparams = cur.buffer()->params();
1695 Paragraph const & par = cur.paragraph();
1696 Cursor prevcur = cur;
1697 if (cur.pit() > 0) {
1699 prevcur.pos() = prevcur.lastpos();
1701 Paragraph const & prevpar = prevcur.paragraph();
1703 // if a bibitem is deleted, merge with previous paragraph
1704 // if this is a bibliography item as well
1705 if (cur.pit() > 0 && par.layout() == prevpar.layout()) {
1706 cur.recordUndo(prevcur.pit());
1707 mergeParagraph(bufparams, cur.text()->paragraphs(),
1709 cur.forceBufferUpdate();
1710 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1711 cur.screenUpdateFlags(Update::Force);
1715 // otherwise reset to default
1716 cur.paragraph().setPlainOrDefaultLayout(bufparams.documentClass());
1721 bool Text::erase(Cursor & cur)
1723 LASSERT(this == cur.text(), return false);
1724 bool needsUpdate = false;
1725 Paragraph & par = cur.paragraph();
1727 if (cur.pos() != cur.lastpos()) {
1728 // this is the code for a normal delete, not pasting
1730 cur.recordUndo(DELETE_UNDO);
1731 bool const was_inset = cur.paragraph().isInset(cur.pos());
1732 if(!par.eraseChar(cur.pos(), cur.buffer()->params().track_changes))
1733 // the character has been logically deleted only => skip it
1734 cur.top().forwardPos();
1737 cur.forceBufferUpdate();
1739 cur.checkBufferStructure();
1742 if (cur.pit() == cur.lastpit())
1743 return dissolveInset(cur);
1745 if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1746 cur.recordUndo(DELETE_UNDO);
1747 par.setChange(cur.pos(), Change(Change::DELETED));
1751 setCursorIntern(cur, cur.pit() + 1, 0);
1752 needsUpdate = backspacePos0(cur);
1756 needsUpdate |= handleBibitems(cur);
1759 // Make sure the cursor is correct. Is this really needed?
1760 // No, not really... at least not here!
1761 cur.top().setPitPos(cur.pit(), cur.pos());
1762 cur.checkBufferStructure();
1769 bool Text::backspacePos0(Cursor & cur)
1771 LBUFERR(this == cur.text());
1775 BufferParams const & bufparams = cur.buffer()->params();
1776 ParagraphList & plist = cur.text()->paragraphs();
1777 Paragraph const & par = cur.paragraph();
1778 Cursor prevcur = cur;
1780 prevcur.pos() = prevcur.lastpos();
1781 Paragraph const & prevpar = prevcur.paragraph();
1783 // is it an empty paragraph?
1784 if (cur.lastpos() == 0
1785 || (cur.lastpos() == 1 && par.isSeparator(0))) {
1786 cur.recordUndo(prevcur.pit());
1787 plist.erase(plist.iterator_at(cur.pit()));
1789 // is previous par empty?
1790 else if (prevcur.lastpos() == 0
1791 || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) {
1792 cur.recordUndo(prevcur.pit());
1793 plist.erase(plist.iterator_at(prevcur.pit()));
1795 // FIXME: Do we really not want to allow this???
1796 // Pasting is not allowed, if the paragraphs have different
1797 // layouts. I think it is a real bug of all other
1798 // word processors to allow it. It confuses the user.
1799 // Correction: Pasting is always allowed with standard-layout
1800 // or the empty layout.
1802 cur.recordUndo(prevcur.pit());
1803 mergeParagraph(bufparams, plist, prevcur.pit());
1806 cur.forceBufferUpdate();
1807 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1813 bool Text::backspace(Cursor & cur)
1815 LBUFERR(this == cur.text());
1816 bool needsUpdate = false;
1817 if (cur.pos() == 0) {
1819 return dissolveInset(cur);
1821 Cursor prev_cur = cur;
1824 if (!cur.paragraph().empty()
1825 && !prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1826 cur.recordUndo(prev_cur.pit(), prev_cur.pit());
1827 prev_cur.paragraph().setChange(prev_cur.lastpos(), Change(Change::DELETED));
1828 setCursorIntern(cur, prev_cur.pit(), prev_cur.lastpos());
1831 // The cursor is at the beginning of a paragraph, so
1832 // the backspace will collapse two paragraphs into one.
1833 needsUpdate = backspacePos0(cur);
1836 // this is the code for a normal backspace, not pasting
1838 cur.recordUndo(DELETE_UNDO);
1839 // We used to do cursorBackwardIntern() here, but it is
1840 // not a good idea since it triggers the auto-delete
1841 // mechanism. So we do a cursorBackwardIntern()-lite,
1842 // without the dreaded mechanism. (JMarc)
1843 setCursorIntern(cur, cur.pit(), cur.pos() - 1,
1844 false, cur.boundary());
1845 bool const was_inset = cur.paragraph().isInset(cur.pos());
1846 cur.paragraph().eraseChar(cur.pos(), cur.buffer()->params().track_changes);
1848 cur.forceBufferUpdate();
1850 cur.checkBufferStructure();
1853 if (cur.pos() == cur.lastpos())
1854 cur.setCurrentFont();
1856 needsUpdate |= handleBibitems(cur);
1858 // A singlePar update is not enough in this case.
1859 // cur.screenUpdateFlags(Update::Force);
1860 cur.top().setPitPos(cur.pit(), cur.pos());
1866 bool Text::dissolveInset(Cursor & cur)
1868 LASSERT(this == cur.text(), return false);
1870 if (isMainText() || cur.inset().nargs() != 1)
1873 cur.recordUndoInset();
1875 cur.selHandle(false);
1876 // save position inside inset
1877 pos_type spos = cur.pos();
1878 pit_type spit = cur.pit();
1879 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1881 // update cursor offset
1885 // remember position outside inset to delete inset later
1886 // we do not do it now to avoid memory reuse issues (see #10667).
1887 DocIterator inset_it = cur;
1891 Buffer & b = *cur.buffer();
1892 // Is there anything in this text?
1893 if (inset_non_empty) {
1895 // we clear the cache so that we won't get conflicts with labels
1896 // that get pasted into the buffer. we should update this before
1897 // its being empty matters. if not (i.e., if we encounter bugs),
1898 // then this should instead be:
1899 // cur.buffer().updateBuffer();
1900 // but we'll try the cheaper solution here.
1901 cur.buffer()->clearReferenceCache();
1903 ParagraphList & plist = paragraphs();
1904 if (!lyxrc.ct_markup_copied)
1905 // Do not revive deleted text
1906 lyx::acceptChanges(plist, b.params());
1908 // ERT paragraphs have the Language latex_language.
1909 // This is invalid outside of ERT, so we need to
1910 // change it to the buffer language.
1911 for (auto & p : plist)
1912 p.changeLanguage(b.params(), latex_language, b.language());
1914 /* If the inset is the only thing in paragraph and the layout
1915 * is not plain, then the layout of the first paragraph of
1916 * inset should be remembered.
1917 * FIXME: this does not work as expected when change tracking
1918 * is on However, we do not really know what to do in this
1921 DocumentClass const & tclass = cur.buffer()->params().documentClass();
1922 if (inset_it.lastpos() == 1
1923 && !tclass.isPlainLayout(plist[0].layout())
1924 && !tclass.isDefaultLayout(plist[0].layout())) {
1925 // Copy all parameters except depth.
1926 Paragraph & par = cur.paragraph();
1927 par.setLayout(plist[0].layout());
1928 depth_type const dpth = par.getDepth();
1929 par.params() = plist[0].params();
1930 par.params().depth(dpth);
1933 pasteParagraphList(cur, plist, b.params().documentClassPtr(),
1934 b.params().authors(),
1935 b.errorList("Paste"));
1938 // delete the inset now
1939 inset_it.paragraph().eraseChar(inset_it.pos(), b.params().track_changes);
1942 cur.pit() = min(cur.lastpit(), spit);
1943 cur.pos() = min(cur.lastpos(), spos);
1944 // Ensure the current language is set correctly (bug 6292)
1945 cur.text()->setCursor(cur, cur.pit(), cur.pos());
1946 cur.clearSelection();
1948 cur.forceBufferUpdate();
1954 bool Text::splitInset(Cursor & cur)
1956 LASSERT(this == cur.text(), return false);
1958 if (isMainText() || cur.inset().nargs() != 1)
1962 if (cur.selection()) {
1963 // start from selection begin
1964 setCursor(cur, cur.selBegin().pit(), cur.selBegin().pos());
1965 cur.clearSelection();
1967 // save split position inside inset
1968 // (we need to copy the whole inset first)
1969 pos_type spos = cur.pos();
1970 pit_type spit = cur.pit();
1971 // some things only need to be done if the inset has content
1972 bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1974 // move right before the inset
1977 // remember position outside inset
1978 pos_type ipos = cur.pos();
1979 pit_type ipit = cur.pit();
1984 cap::copySelectionToTemp(cur);
1985 cur.clearSelection();
1987 // paste copied inset
1988 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
1989 cur.forceBufferUpdate();
1991 // if the inset has text, cut after split position
1992 // and paste to new inset
1993 if (inset_non_empty) {
1994 // go back to first inset
1995 cur.text()->setCursor(cur, ipit, ipos);
1997 setCursor(cur, spit, spos);
1999 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
2001 // Remember whether there was something cut that has to be pasted below
2003 bool const hasCut = cur.selection();
2004 cap::cutSelectionToTemp(cur);
2006 cur.selHandle(false);
2008 bool atlastpos = false;
2009 if (cur.pos() == 0 && cur.pit() > 0) {
2010 // if we are at par start, remove this par
2011 cur.text()->backspace(cur);
2012 cur.forceBufferUpdate();
2013 } else if (cur.pos() == cur.lastpos())
2015 // Move out of and jump over inset
2023 cur.text()->selectAll(cur);
2024 cutSelection(cur, false);
2025 // If there was something cut paste it
2027 cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
2028 cur.text()->setCursor(cur, 0, 0);
2029 if (atlastpos && cur.paragraph().isFreeSpacing() && cur.paragraph().empty()) {
2030 // We started from par end, remove extra empty par in free spacing insets
2031 cur.text()->erase(cur);
2032 cur.forceBufferUpdate();
2041 void Text::getWord(CursorSlice & from, CursorSlice & to,
2042 word_location const loc) const
2045 pars_[to.pit()].locateWord(from.pos(), to.pos(), loc);
2049 void Text::write(ostream & os) const
2051 Buffer const & buf = owner_->buffer();
2052 ParagraphList::const_iterator pit = paragraphs().begin();
2053 ParagraphList::const_iterator end = paragraphs().end();
2055 for (; pit != end; ++pit)
2056 pit->write(os, buf.params(), dth);
2058 // Close begin_deeper
2059 for(; dth > 0; --dth)
2060 os << "\n\\end_deeper";
2064 bool Text::read(Lexer & lex,
2065 ErrorList & errorList, InsetText * insetPtr)
2067 Buffer const & buf = owner_->buffer();
2068 depth_type depth = 0;
2071 while (lex.isOK()) {
2073 string const token = lex.getString();
2078 if (token == "\\end_inset")
2081 if (token == "\\end_body")
2084 if (token == "\\begin_body")
2087 if (token == "\\end_document") {
2092 if (token == "\\begin_layout") {
2093 lex.pushToken(token);
2096 par.setInsetOwner(insetPtr);
2097 par.params().depth(depth);
2098 par.setFont(0, Font(inherit_font, buf.params().language));
2099 pars_.push_back(par);
2100 readParagraph(pars_.back(), lex, errorList);
2102 // register the words in the global word list
2103 pars_.back().updateWords();
2104 } else if (token == "\\begin_deeper") {
2106 } else if (token == "\\end_deeper") {
2108 lex.printError("\\end_deeper: " "depth is already null");
2112 LYXERR0("Handling unknown body token: `" << token << '\'');
2116 // avoid a crash on weird documents (bug 4859)
2117 if (pars_.empty()) {
2119 par.setInsetOwner(insetPtr);
2120 par.params().depth(depth);
2121 par.setFont(0, Font(inherit_font,
2122 buf.params().language));
2123 par.setPlainOrDefaultLayout(buf.params().documentClass());
2124 pars_.push_back(par);
2131 // Returns the current state (font, depth etc.) as a message for status bar.
2132 docstring Text::currentState(CursorData const & cur, bool devel_mode) const
2134 LBUFERR(this == cur.text());
2135 Buffer & buf = *cur.buffer();
2136 Paragraph const & par = cur.paragraph();
2137 odocstringstream os;
2139 if (buf.params().track_changes)
2140 os << _("[Change Tracking] ");
2142 Change change = par.lookupChange(cur.pos());
2144 if (change.changed()) {
2145 docstring const author =
2146 buf.params().authors().get(change.author).nameAndEmail();
2147 docstring const date = formatted_datetime(change.changetime);
2148 os << bformat(_("Changed by %1$s[[author]] on %2$s[[date]]. "),
2152 // I think we should only show changes from the default
2154 // No, from the document font (MV)
2155 Font font = cur.real_current_font;
2156 font.fontInfo().reduce(buf.params().getFont().fontInfo());
2158 os << bformat(_("Font: %1$s"), font.stateText(&buf.params()));
2160 // The paragraph depth
2161 int depth = par.getDepth();
2163 os << bformat(_(", Depth: %1$d"), depth);
2165 // The paragraph spacing, but only if different from
2167 Spacing const & spacing = par.params().spacing();
2168 if (!spacing.isDefault()) {
2169 os << _(", Spacing: ");
2170 switch (spacing.getSpace()) {
2171 case Spacing::Single:
2174 case Spacing::Onehalf:
2177 case Spacing::Double:
2180 case Spacing::Other:
2181 os << _("Other (") << from_ascii(spacing.getValueAsString()) << ')';
2183 case Spacing::Default:
2184 // should never happen, do nothing
2189 // Custom text style
2190 InsetLayout const & layout = cur.inset().getLayout();
2191 if (layout.lyxtype() == InsetLyXType::CHARSTYLE)
2192 os << _(", Style: ") << translateIfPossible(layout.labelstring());
2195 os << _(", Inset: ") << &cur.inset();
2196 if (cur.lastidx() > 0)
2197 os << _(", Cell: ") << cur.idx();
2198 os << _(", Paragraph: ") << cur.pit();
2199 os << _(", Id: ") << par.id();
2200 os << _(", Position: ") << cur.pos();
2201 // FIXME: Why is the check for par.size() needed?
2202 // We are called with cur.pos() == par.size() quite often.
2203 if (!par.empty() && cur.pos() < par.size()) {
2204 // Force output of code point, not character
2205 size_t const c = par.getChar(cur.pos());
2206 if (c == META_INSET)
2207 os << ", Char: INSET";
2209 os << _(", Char: 0x") << hex << c;
2211 os << _(", Boundary: ") << cur.boundary();
2212 // Row & row = cur.textRow();
2213 // os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos());
2219 docstring Text::getPossibleLabel(DocIterator const & cur) const
2221 pit_type textpit = cur.pit();
2222 Layout const * layout = &(pars_[textpit].layout());
2224 // Will contain the label prefix.
2227 // For captions, we just take the caption type
2228 Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE);
2229 if (caption_inset) {
2230 string const & ftype = static_cast<InsetCaption *>(caption_inset)->floattype();
2231 FloatList const & fl = cur.buffer()->params().documentClass().floats();
2232 if (fl.typeExist(ftype)) {
2233 Floating const & flt = fl.getType(ftype);
2234 name = from_utf8(flt.refPrefix());
2237 name = from_utf8(ftype.substr(0,3));
2239 // For section, subsection, etc...
2240 if (layout->latextype == LATEX_PARAGRAPH && textpit != 0) {
2241 Layout const * layout2 = &(pars_[textpit - 1].layout());
2242 if (layout2->latextype != LATEX_PARAGRAPH) {
2247 if (layout->latextype != LATEX_PARAGRAPH)
2248 name = layout->refprefix;
2250 // If none of the above worked, see if the inset knows.
2252 InsetLayout const & il = cur.inset().getLayout();
2253 name = il.refprefix();
2258 docstring par_text = pars_[textpit].asString(AS_STR_SKIPDELETE);
2260 // The return string of math matrices might contain linebreaks
2261 par_text = subst(par_text, '\n', '-');
2262 int const numwords = 3;
2263 for (int i = 0; i < numwords; ++i) {
2264 if (par_text.empty())
2267 par_text = split(par_text, head, ' ');
2268 // Is it legal to use spaces in labels ?
2274 // Make sure it isn't too long
2275 unsigned int const max_label_length = 32;
2276 if (text.size() > max_label_length)
2277 text.resize(max_label_length);
2280 text = name + ':' + text;
2282 // We need a unique label
2283 docstring label = text;
2285 while (cur.buffer()->activeLabel(label)) {
2286 label = text + '-' + convert<docstring>(i);
2294 docstring Text::asString(int options) const
2296 return asString(0, pars_.size(), options);
2300 docstring Text::asString(pit_type beg, pit_type end, int options) const
2302 size_t i = size_t(beg);
2303 docstring str = pars_[i].asString(options);
2304 for (++i; i != size_t(end); ++i) {
2306 str += pars_[i].asString(options);
2312 void Text::shortenForOutliner(docstring & str, size_t const maxlen)
2314 support::truncateWithEllipsis(str, maxlen);
2315 for (char_type & c : str)
2316 if (c == L'\n' || c == L'\t')
2321 void Text::forOutliner(docstring & os, size_t const maxlen,
2322 bool const shorten) const
2324 pit_type end = pars_.size() - 1;
2325 if (0 <= end && !pars_[0].labelString().empty())
2326 os += pars_[0].labelString() + ' ';
2327 forOutliner(os, maxlen, 0, end, shorten);
2331 void Text::forOutliner(docstring & os, size_t const maxlen,
2332 pit_type pit_start, pit_type pit_end,
2333 bool const shorten) const
2335 size_t tmplen = shorten ? maxlen + 1 : maxlen;
2336 pit_type end = min(size_t(pit_end), pars_.size() - 1);
2338 for (pit_type i = pit_start; i <= end && os.length() < tmplen; ++i) {
2341 // This function lets the first label be treated separately
2342 pars_[i].forOutliner(os, tmplen, false, !first);
2346 shortenForOutliner(os, maxlen);
2350 void Text::charsTranspose(Cursor & cur)
2352 LBUFERR(this == cur.text());
2354 pos_type pos = cur.pos();
2356 // If cursor is at beginning or end of paragraph, do nothing.
2357 if (pos == cur.lastpos() || pos == 0)
2360 Paragraph & par = cur.paragraph();
2362 // Get the positions of the characters to be transposed.
2363 pos_type pos1 = pos - 1;
2364 pos_type pos2 = pos;
2366 // In change tracking mode, ignore deleted characters.
2367 while (pos2 < cur.lastpos() && par.isDeleted(pos2))
2369 if (pos2 == cur.lastpos())
2372 while (pos1 >= 0 && par.isDeleted(pos1))
2377 // Don't do anything if one of the "characters" is not regular text.
2378 if (par.isInset(pos1) || par.isInset(pos2))
2381 // Store the characters to be transposed (including font information).
2382 char_type const char1 = par.getChar(pos1);
2384 par.getFontSettings(cur.buffer()->params(), pos1);
2386 char_type const char2 = par.getChar(pos2);
2388 par.getFontSettings(cur.buffer()->params(), pos2);
2390 // And finally, we are ready to perform the transposition.
2391 // Track the changes if Change Tracking is enabled.
2392 bool const trackChanges = cur.buffer()->params().track_changes;
2396 par.eraseChar(pos2, trackChanges);
2397 par.eraseChar(pos1, trackChanges);
2398 par.insertChar(pos1, char2, font2, trackChanges);
2399 par.insertChar(pos2, char1, font1, trackChanges);
2401 cur.checkBufferStructure();
2403 // After the transposition, move cursor to after the transposition.
2404 setCursor(cur, cur.pit(), pos2);
2409 DocIterator Text::macrocontextPosition() const
2411 return macrocontext_position_;
2415 void Text::setMacrocontextPosition(DocIterator const & pos)
2417 macrocontext_position_ = pos;
2421 bool Text::completionSupported(Cursor const & cur) const
2423 Paragraph const & par = cur.paragraph();
2424 return !cur.buffer()->isReadonly()
2427 && (cur.pos() >= par.size() || par.isWordSeparator(cur.pos()))
2428 && !par.isWordSeparator(cur.pos() - 1);
2432 CompletionList const * Text::createCompletionList(Cursor const & cur) const
2434 WordList const & list = theWordList(cur.getFont().language()->lang());
2435 return new TextCompletionList(cur, list);
2439 bool Text::insertCompletion(Cursor & cur, docstring const & s)
2441 LBUFERR(cur.bv().cursor() == cur);
2442 if (cur.buffer()->isReadonly())
2446 cur.bv().cursor() = cur;
2447 if (!(cur.result().screenUpdate() & Update::Force))
2448 cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
2453 docstring Text::completionPrefix(Cursor const & cur) const
2455 CursorSlice from = cur.top();
2456 CursorSlice to = from;
2457 getWord(from, to, PREVIOUS_WORD);
2459 return cur.paragraph().asString(from.pos(), to.pos());
2462 bool Text::isMainText() const
2464 return &owner_->buffer().text() == this;
2468 // Note that this is supposed to return a fully realized font.
2469 FontInfo Text::layoutFont(pit_type const pit) const
2471 Layout const & layout = pars_[pit].layout();
2473 if (!pars_[pit].getDepth()) {
2474 FontInfo lf = layout.resfont;
2475 // In case the default family has been customized
2476 if (layout.font.family() == INHERIT_FAMILY)
2477 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
2478 FontInfo icf = (!isMainText())
2479 // inside insets, we call the getFont() method
2481 // outside, we access the layout font directly
2482 : owner_->getLayout().font();
2487 FontInfo font = layout.font;
2488 // Realize with the fonts of lesser depth.
2489 //font.realize(outerFont(pit));
2490 font.realize(owner_->buffer().params().getFont().fontInfo());
2496 // Note that this is supposed to return a fully realized font.
2497 FontInfo Text::labelFont(Paragraph const & par) const
2499 Buffer const & buffer = owner_->buffer();
2500 Layout const & layout = par.layout();
2502 if (!par.getDepth()) {
2503 FontInfo lf = layout.reslabelfont;
2504 // In case the default family has been customized
2505 if (layout.labelfont.family() == INHERIT_FAMILY)
2506 lf.setFamily(buffer.params().getFont().fontInfo().family());
2510 FontInfo font = layout.labelfont;
2511 // Realize with the fonts of lesser depth.
2512 font.realize(buffer.params().getFont().fontInfo());
2518 void Text::setCharFont(pit_type pit,
2519 pos_type pos, Font const & fnt, Font const & display_font)
2521 Buffer const & buffer = owner_->buffer();
2523 Layout const & layout = pars_[pit].layout();
2525 // Get concrete layout font to reduce against
2526 FontInfo layoutfont;
2528 if (pos < pars_[pit].beginOfBody())
2529 layoutfont = layout.labelfont;
2531 layoutfont = layout.font;
2533 // Realize against environment font information
2534 if (pars_[pit].getDepth()) {
2536 while (!layoutfont.resolved() &&
2537 tp != pit_type(paragraphs().size()) &&
2538 pars_[tp].getDepth()) {
2540 if (tp != pit_type(paragraphs().size()))
2541 layoutfont.realize(pars_[tp].layout().font);
2545 // Inside inset, apply the inset's font attributes if any
2548 layoutfont.realize(display_font.fontInfo());
2550 layoutfont.realize(buffer.params().getFont().fontInfo());
2552 // Now, reduce font against full layout font
2553 font.fontInfo().reduce(layoutfont);
2555 pars_[pit].setFont(pos, font);
2559 void Text::setInsetFont(BufferView const & bv, pit_type pit,
2560 pos_type pos, Font const & font)
2562 Inset * const inset = pars_[pit].getInset(pos);
2563 LASSERT(inset && inset->resetFontEdit(), return);
2565 idx_type endidx = inset->nargs();
2566 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
2567 Text * text = cs.text();
2569 // last position of the cell
2570 CursorSlice cellend = cs;
2571 cellend.pit() = cellend.lastpit();
2572 cellend.pos() = cellend.lastpos();
2573 text->setFont(bv, cs, cellend, font);
2579 void Text::setLayout(pit_type start, pit_type end,
2580 docstring const & layout)
2582 // FIXME: make this work in multicell selection case
2583 LASSERT(start != end, return);
2585 Buffer const & buffer = owner_->buffer();
2586 BufferParams const & bp = buffer.params();
2587 Layout const & lyxlayout = bp.documentClass()[layout];
2589 for (pit_type pit = start; pit != end; ++pit) {
2590 Paragraph & par = pars_[pit];
2591 // Is this a separating paragraph? If so,
2592 // this needs to be standard layout
2593 bool const is_separator = par.size() == 1
2594 && par.isEnvSeparator(0);
2595 par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
2596 if (lyxlayout.margintype == MARGIN_MANUAL)
2597 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
2600 deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
2604 // set layout over selection and make a total rebreak of those paragraphs
2605 void Text::setLayout(Cursor & cur, docstring const & layout)
2607 LBUFERR(this == cur.text());
2609 pit_type start = cur.selBegin().pit();
2610 pit_type end = cur.selEnd().pit() + 1;
2611 cur.recordUndoSelection();
2612 setLayout(start, end, layout);
2614 cur.setCurrentFont();
2615 cur.forceBufferUpdate();
2619 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
2620 Paragraph const & par, int max_depth)
2622 int const depth = par.params().depth();
2623 if (type == Text::INC_DEPTH && depth < max_depth)
2625 if (type == Text::DEC_DEPTH && depth > 0)
2631 bool Text::changeDepthAllowed(Cursor const & cur, DEPTH_CHANGE type) const
2633 LBUFERR(this == cur.text());
2634 // this happens when selecting several cells in tabular (bug 2630)
2635 if (cur.selBegin().idx() != cur.selEnd().idx())
2638 pit_type const beg = cur.selBegin().pit();
2639 pit_type const end = cur.selEnd().pit() + 1;
2640 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2642 for (pit_type pit = beg; pit != end; ++pit) {
2643 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
2645 max_depth = pars_[pit].getMaxDepthAfter();
2651 void Text::changeDepth(Cursor const & cur, DEPTH_CHANGE type)
2653 LBUFERR(this == cur.text());
2654 pit_type const beg = cur.selBegin().pit();
2655 pit_type const end = cur.selEnd().pit() + 1;
2656 cur.recordUndoSelection();
2657 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2659 for (pit_type pit = beg; pit != end; ++pit) {
2660 Paragraph & par = pars_[pit];
2661 if (lyx::changeDepthAllowed(type, par, max_depth)) {
2662 int const depth = par.params().depth();
2663 if (type == INC_DEPTH)
2664 par.params().depth(depth + 1);
2666 par.params().depth(depth - 1);
2668 max_depth = par.getMaxDepthAfter();
2670 // this handles the counter labels, and also fixes up
2671 // depth values for follow-on (child) paragraphs
2672 cur.forceBufferUpdate();
2676 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
2678 LASSERT(this == cur.text(), return);
2680 // If there is a selection, record undo before the cursor font is changed.
2681 if (cur.selection())
2682 cur.recordUndoSelection();
2684 // Set the current_font
2685 // Determine basis font
2686 FontInfo layoutfont;
2687 pit_type pit = cur.pit();
2688 if (cur.pos() < pars_[pit].beginOfBody())
2689 layoutfont = labelFont(pars_[pit]);
2691 layoutfont = layoutFont(pit);
2693 // Update current font
2694 cur.real_current_font.update(font,
2695 cur.buffer()->params().language,
2698 // Reduce to implicit settings
2699 cur.current_font = cur.real_current_font;
2700 cur.current_font.fontInfo().reduce(layoutfont);
2701 // And resolve it completely
2702 cur.real_current_font.fontInfo().realize(layoutfont);
2704 // if there is no selection that's all we need to do
2705 if (!cur.selection())
2708 // Ok, we have a selection.
2709 Font newfont = font;
2712 // Toggling behaves as follows: We check the first character of the
2713 // selection. If it's (say) got EMPH on, then we set to off; if off,
2714 // then to on. With families and the like, we set it to INHERIT, if
2715 // we already have it.
2716 CursorSlice const & sl = cur.selBegin();
2717 Text const & text = *sl.text();
2718 Paragraph const & par = text.getPar(sl.pit());
2720 // get font at the position
2721 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
2722 text.outerFont(sl.pit()));
2723 FontInfo const & oldfi = oldfont.fontInfo();
2725 FontInfo & newfi = newfont.fontInfo();
2727 FontFamily newfam = newfi.family();
2728 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
2729 newfam == oldfi.family())
2730 newfi.setFamily(INHERIT_FAMILY);
2732 FontSeries newser = newfi.series();
2733 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
2734 newfi.setSeries(INHERIT_SERIES);
2736 FontShape newshp = newfi.shape();
2737 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
2738 newshp == oldfi.shape())
2739 newfi.setShape(INHERIT_SHAPE);
2741 ColorCode newcol = newfi.color();
2742 if (newcol != Color_none && newcol != Color_inherit
2743 && newcol != Color_ignore && newcol == oldfi.color())
2744 newfi.setColor(Color_none);
2747 if (newfi.emph() == FONT_TOGGLE)
2748 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
2749 if (newfi.underbar() == FONT_TOGGLE)
2750 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
2751 if (newfi.strikeout() == FONT_TOGGLE)
2752 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
2753 if (newfi.xout() == FONT_TOGGLE)
2754 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
2755 if (newfi.uuline() == FONT_TOGGLE)
2756 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
2757 if (newfi.uwave() == FONT_TOGGLE)
2758 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
2759 if (newfi.noun() == FONT_TOGGLE)
2760 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
2761 if (newfi.number() == FONT_TOGGLE)
2762 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
2763 if (newfi.nospellcheck() == FONT_TOGGLE)
2764 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
2767 setFont(cur.bv(), cur.selectionBegin().top(),
2768 cur.selectionEnd().top(), newfont);
2772 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
2773 CursorSlice const & end, Font const & font)
2775 Buffer const & buffer = bv.buffer();
2777 // Don't use forwardChar here as ditend might have
2778 // pos() == lastpos() and forwardChar would miss it.
2779 // Can't use forwardPos either as this descends into
2781 Language const * language = buffer.params().language;
2782 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
2783 if (dit.pos() == dit.lastpos())
2785 pit_type const pit = dit.pit();
2786 pos_type const pos = dit.pos();
2787 Inset * inset = pars_[pit].getInset(pos);
2788 if (inset && inset->resetFontEdit()) {
2789 // We need to propagate the font change to all
2790 // text cells of the inset (bugs 1973, 6919).
2791 setInsetFont(bv, pit, pos, font);
2793 TextMetrics const & tm = bv.textMetrics(this);
2794 Font f = tm.displayFont(pit, pos);
2795 f.update(font, language);
2796 setCharFont(pit, pos, f, tm.font_);
2797 // font change may change language...
2798 // spell checker has to know that
2799 pars_[pit].requestSpellCheck(pos);
2804 bool Text::cursorTop(Cursor & cur)
2806 LBUFERR(this == cur.text());
2807 return setCursor(cur, 0, 0);
2811 bool Text::cursorBottom(Cursor & cur)
2813 LBUFERR(this == cur.text());
2814 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
2818 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
2820 LBUFERR(this == cur.text());
2821 // If the mask is completely neutral, tell user
2822 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
2823 // Could only happen with user style
2824 cur.message(_("No font change defined."));
2828 // Try implicit word selection
2829 // If there is a change in the language the implicit word selection
2831 CursorSlice const resetCursor = cur.top();
2832 bool const implicitSelection =
2833 font.language() == ignore_language
2834 && font.fontInfo().number() == FONT_IGNORE
2835 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
2838 setFont(cur, font, toggleall);
2840 // Implicit selections are cleared afterwards
2841 // and cursor is set to the original position.
2842 if (implicitSelection) {
2843 cur.clearSelection();
2844 cur.top() = resetCursor;
2848 // if there was no selection at all, the point was to change cursor font.
2849 // Otherwise, we want to reset it to local text font.
2850 if (cur.selection() || implicitSelection)
2851 cur.setCurrentFont();
2855 docstring Text::getStringForDialog(Cursor & cur)
2857 LBUFERR(this == cur.text());
2859 if (cur.selection())
2860 return cur.selectionAsString(false);
2862 // Try implicit word selection. If there is a change
2863 // in the language the implicit word selection is
2865 selectWordWhenUnderCursor(cur, WHOLE_WORD);
2866 docstring const & retval = cur.selectionAsString(false);
2867 cur.clearSelection();
2872 void Text::setLabelWidthStringToSequence(Cursor const & cur,
2873 docstring const & s)
2876 // Find first of same layout in sequence
2877 while (!isFirstInSequence(c.pit())) {
2878 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
2881 // now apply label width string to every par
2883 depth_type const depth = c.paragraph().getDepth();
2884 Layout const & layout = c.paragraph().layout();
2885 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
2886 while (c.paragraph().getDepth() > depth) {
2888 if (c.pit() > c.lastpit())
2891 if (c.paragraph().getDepth() < depth)
2893 if (c.paragraph().layout() != layout)
2896 c.paragraph().setLabelWidthString(s);
2901 void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
2903 LBUFERR(cur.text());
2906 string const argument = to_utf8(arg);
2907 depth_type priordepth = -1;
2910 c.setCursor(cur.selectionBegin());
2911 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
2912 Paragraph & par = c.paragraph();
2913 ParagraphParameters params = par.params();
2914 params.read(argument, merge);
2915 // Changes to label width string apply to all paragraphs
2916 // with same layout in a sequence.
2917 // Do this only once for a selected range of paragraphs
2918 // of the same layout and depth.
2920 par.params().apply(params, par.layout());
2921 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2922 setLabelWidthStringToSequence(c, params.labelWidthString());
2923 priordepth = par.getDepth();
2924 priorlayout = par.layout();
2929 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
2931 LBUFERR(cur.text());
2933 depth_type priordepth = -1;
2936 c.setCursor(cur.selectionBegin());
2937 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
2938 Paragraph & par = c.paragraph();
2939 // Changes to label width string apply to all paragraphs
2940 // with same layout in a sequence.
2941 // Do this only once for a selected range of paragraphs
2942 // of the same layout and depth.
2944 par.params().apply(p, par.layout());
2945 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2946 setLabelWidthStringToSequence(c,
2947 par.params().labelWidthString());
2948 priordepth = par.getDepth();
2949 priorlayout = par.layout();
2954 // this really should just insert the inset and not move the cursor.
2955 void Text::insertInset(Cursor & cur, Inset * inset)
2957 LBUFERR(this == cur.text());
2959 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
2960 Change(cur.buffer()->params().track_changes
2961 ? Change::INSERTED : Change::UNCHANGED));
2965 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
2966 bool setfont, bool boundary)
2968 TextMetrics const & tm = cur.bv().textMetrics(this);
2969 bool const update_needed = !tm.contains(pit);
2971 setCursorIntern(cur, pit, pos, setfont, boundary);
2972 return cur.bv().checkDepm(cur, old) || update_needed;
2976 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
2977 bool setfont, bool boundary)
2979 LBUFERR(this == cur.text());
2980 cur.boundary(boundary);
2981 cur.top().setPitPos(pit, pos);
2983 cur.setCurrentFont();
2987 bool Text::checkAndActivateInset(Cursor & cur, bool front)
2989 if (front && cur.pos() == cur.lastpos())
2991 if (!front && cur.pos() == 0)
2993 Inset * inset = front ? cur.nextInset() : cur.prevInset();
2994 if (!inset || !inset->editable())
2996 if (cur.selection() && cur.realAnchor().find(inset) == -1)
2999 * Apparently, when entering an inset we are expected to be positioned
3000 * *before* it in the containing paragraph, regardless of the direction
3001 * from which we are entering. Otherwise, cursor placement goes awry,
3002 * and when we exit from the beginning, we'll be placed *after* the
3007 inset->edit(cur, front);
3008 cur.setCurrentFont();
3009 cur.boundary(false);
3014 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
3016 if (cur.pos() == -1)
3018 if (cur.pos() == cur.lastpos())
3020 Paragraph & par = cur.paragraph();
3021 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
3022 if (!inset || !inset->editable())
3024 if (cur.selection() && cur.realAnchor().find(inset) == -1)
3026 inset->edit(cur, movingForward,
3027 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
3028 cur.setCurrentFont();
3029 cur.boundary(false);
3034 bool Text::cursorBackward(Cursor & cur)
3036 // Tell BufferView to test for FitCursor in any case!
3037 cur.screenUpdateFlags(Update::FitCursor);
3039 // not at paragraph start?
3040 if (cur.pos() > 0) {
3041 // if on right side of boundary (i.e. not at paragraph end, but line end)
3042 // -> skip it, i.e. set boundary to true, i.e. go only logically left
3043 // there are some exceptions to ignore this: lineseps, newlines, spaces
3045 // some effectless debug code to see the values in the debugger
3046 bool bound = cur.boundary();
3047 int rowpos = cur.textRow().pos();
3048 int pos = cur.pos();
3049 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
3050 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
3051 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
3053 if (!cur.boundary() &&
3054 cur.textRow().pos() == cur.pos() &&
3055 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
3056 !cur.paragraph().isNewline(cur.pos() - 1) &&
3057 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
3058 !cur.paragraph().isSeparator(cur.pos() - 1)) {
3059 return setCursor(cur, cur.pit(), cur.pos(), true, true);
3062 // go left and try to enter inset
3063 if (checkAndActivateInset(cur, false))
3066 // normal character left
3067 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
3070 // move to the previous paragraph or do nothing
3071 if (cur.pit() > 0) {
3072 Paragraph & par = getPar(cur.pit() - 1);
3073 pos_type lastpos = par.size();
3074 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
3075 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
3077 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
3083 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
3085 Cursor temp_cur = cur;
3086 temp_cur.posVisLeft(skip_inset);
3087 if (temp_cur.depth() > cur.depth()) {
3091 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3092 true, temp_cur.boundary());
3096 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
3098 Cursor temp_cur = cur;
3099 temp_cur.posVisRight(skip_inset);
3100 if (temp_cur.depth() > cur.depth()) {
3104 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3105 true, temp_cur.boundary());
3109 bool Text::cursorForward(Cursor & cur)
3111 // Tell BufferView to test for FitCursor in any case!
3112 cur.screenUpdateFlags(Update::FitCursor);
3114 // not at paragraph end?
3115 if (cur.pos() != cur.lastpos()) {
3116 // in front of editable inset, i.e. jump into it?
3117 if (checkAndActivateInset(cur, true))
3120 TextMetrics const & tm = cur.bv().textMetrics(this);
3121 // if left of boundary -> just jump to right side
3122 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
3123 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
3124 return setCursor(cur, cur.pit(), cur.pos(), true, false);
3126 // next position is left of boundary,
3127 // but go to next line for special cases like space, newline, linesep
3129 // some effectless debug code to see the values in the debugger
3130 int endpos = cur.textRow().endpos();
3131 int lastpos = cur.lastpos();
3132 int pos = cur.pos();
3133 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
3134 bool newline = cur.paragraph().isNewline(cur.pos());
3135 bool sep = cur.paragraph().isSeparator(cur.pos());
3136 if (cur.pos() != cur.lastpos()) {
3137 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
3138 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
3139 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
3142 if (cur.textRow().endpos() == cur.pos() + 1) {
3143 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
3144 cur.pos() + 1 == cur.lastpos() &&
3145 cur.pit() != cur.lastpit()) {
3146 // move to next paragraph
3147 return setCursor(cur, cur.pit() + 1, 0, true, false);
3148 } else if (cur.textRow().endpos() != cur.lastpos() &&
3149 !cur.paragraph().isNewline(cur.pos()) &&
3150 !cur.paragraph().isEnvSeparator(cur.pos()) &&
3151 !cur.paragraph().isLineSeparator(cur.pos()) &&
3152 !cur.paragraph().isSeparator(cur.pos())) {
3153 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3157 // in front of RTL boundary? Stay on this side of the boundary because:
3158 // ab|cDDEEFFghi -> abc|DDEEFFghi
3159 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
3160 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3163 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
3166 // move to next paragraph
3167 if (cur.pit() != cur.lastpit())
3168 return setCursor(cur, cur.pit() + 1, 0, true, false);
3173 bool Text::cursorUpParagraph(Cursor & cur)
3175 bool updated = false;
3177 updated = setCursor(cur, cur.pit(), 0);
3178 else if (cur.pit() != 0)
3179 updated = setCursor(cur, cur.pit() - 1, 0);
3184 bool Text::cursorDownParagraph(Cursor & cur)
3186 bool updated = false;
3187 if (cur.pit() != cur.lastpit())
3188 if (lyxrc.mac_like_cursor_movement)
3189 if (cur.pos() == cur.lastpos())
3190 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
3192 updated = setCursor(cur, cur.pit(), cur.lastpos());
3194 updated = setCursor(cur, cur.pit() + 1, 0);
3196 updated = setCursor(cur, cur.pit(), cur.lastpos());
3202 /** delete num_spaces characters between from and to. Return the
3203 * number of spaces that got physically deleted (not marked as
3205 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
3206 int num_spaces, bool const trackChanges)
3208 if (num_spaces <= 0)
3211 // First, delete spaces marked as inserted
3213 while (pos < to && num_spaces > 0) {
3214 Change const & change = par.lookupChange(pos);
3215 if (change.inserted() && change.currentAuthor()) {
3216 par.eraseChar(pos, trackChanges);
3223 // Then remove remaining spaces
3224 int const psize = par.size();
3225 par.eraseChars(from, from + num_spaces, trackChanges);
3226 return psize - par.size();
3232 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
3233 Cursor & old, bool & need_anchor_change)
3235 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
3237 Paragraph & oldpar = old.paragraph();
3238 bool const trackChanges = cur.buffer()->params().track_changes;
3239 bool result = false;
3241 // We do nothing if cursor did not move
3242 if (cur.top() == old.top())
3245 // We do not do anything on read-only documents
3246 if (cur.buffer()->isReadonly())
3249 // Whether a common inset is found and whether the cursor is still in
3250 // the same paragraph (possibly nested).
3251 int const depth = cur.find(&old.inset());
3252 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
3253 && old.pit() == cur[depth].pit();
3256 * (1) If the chars around the old cursor were spaces and the
3257 * paragraph is not in free spacing mode, delete some of them, but
3258 * only if the cursor has really moved.
3261 /* There are still some small problems that can lead to
3262 double spaces stored in the document file or space at
3263 the beginning of paragraphs(). This happens if you have
3264 the cursor between two spaces and then save. Or if you
3265 cut and paste and the selection has a space at the
3266 beginning and then save right after the paste. (Lgb)
3268 if (!oldpar.isFreeSpacing()) {
3269 // find range of spaces around cursors
3270 pos_type from = old.pos();
3272 && oldpar.isLineSeparator(from - 1)
3273 && !oldpar.isDeleted(from - 1))
3275 pos_type to = old.pos();
3276 while (to < old.lastpos()
3277 && oldpar.isLineSeparator(to)
3278 && !oldpar.isDeleted(to))
3281 int num_spaces = to - from;
3282 // If we are not at the start of the paragraph, keep one space
3283 if (from != to && from > 0)
3286 // If cursor is inside range, keep one additional space
3287 if (same_par && cur.pos() > from && cur.pos() < to)
3290 // Remove spaces and adapt cursor.
3291 if (num_spaces > 0) {
3294 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
3295 // correct cur position
3296 // FIXME: there can be other cursors pointing there, we should update them
3298 if (cur[depth].pos() >= to)
3299 cur[depth].pos() -= deleted;
3300 else if (cur[depth].pos() > from)
3301 cur[depth].pos() = min(from + 1, old.lastpos());
3302 need_anchor_change = true;
3309 * (2) If the paragraph where the cursor was is empty, delete it
3312 // only do our other magic if we changed paragraph
3316 // only do our magic if the paragraph is empty
3317 if (!oldpar.empty())
3320 // don't delete anything if this is the ONLY paragraph!
3321 if (old.lastpit() == 0)
3324 // Do not delete empty paragraphs with keepempty set.
3325 if (oldpar.allowEmpty())
3329 old.recordUndo(max(old.pit() - 1, pit_type(0)),
3330 min(old.pit() + 1, old.lastpit()));
3331 ParagraphList & plist = old.text()->paragraphs();
3332 bool const soa = oldpar.params().startOfAppendix();
3333 plist.erase(plist.iterator_at(old.pit()));
3334 // do not lose start of appendix marker (bug 4212)
3335 if (soa && old.pit() < pit_type(plist.size()))
3336 plist[old.pit()].params().startOfAppendix(true);
3338 // see #warning (FIXME?) above
3339 if (cur.depth() >= old.depth()) {
3340 CursorSlice & curslice = cur[old.depth() - 1];
3341 if (&curslice.inset() == &old.inset()
3342 && curslice.idx() == old.idx()
3343 && curslice.pit() > old.pit()) {
3345 // since a paragraph has been deleted, all the
3346 // insets after `old' have been copied and
3347 // their address has changed. Therefore we
3348 // need to `regenerate' cur. (JMarc)
3349 cur.updateInsets(&(cur.bottom().inset()));
3350 need_anchor_change = true;
3358 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
3360 pos_type last_pos = pars_[last].size() - 1;
3361 deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
3365 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
3366 pos_type first_pos, pos_type last_pos,
3369 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
3371 for (pit_type pit = first; pit <= last; ++pit) {
3372 Paragraph & par = pars_[pit];
3375 * (1) Delete consecutive spaces
3377 if (!par.isFreeSpacing()) {
3378 pos_type from = (pit == first) ? first_pos : 0;
3379 pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
3380 while (from < to_pos) {
3382 while (from < par.size()
3383 && (!par.isLineSeparator(from) || par.isDeleted(from)))
3385 // find string of spaces
3387 while (to < par.size()
3388 && par.isLineSeparator(to) && !par.isDeleted(to))
3390 // empty? We are done
3394 int num_spaces = to - from;
3396 // If we are not at the extremity of the paragraph, keep one space
3397 if (from != to && from > 0 && to < par.size())
3400 // Remove spaces if needed
3401 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
3402 from = to - deleted;
3407 * (2) Delete empty pragraphs
3410 // don't delete anything if this is the only remaining paragraph
3411 // within the given range. Note: Text::acceptOrRejectChanges()
3412 // sets the cursor to 'first' after calling DEPM
3416 // don't delete empty paragraphs with keepempty set
3417 if (par.allowEmpty())
3420 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
3421 pars_.erase(pars_.iterator_at(pit));
3433 typedef limited_stack<pair<docstring, Font>> FontStack;
3434 static FontStack freeFonts(15);
3435 static bool toggleall = false;
3437 void toggleAndShow(Cursor & cur, Text * text,
3438 Font const & font, bool togall = true)
3440 text->toggleFree(cur, font, togall);
3442 if (font.language() != ignore_language ||
3443 font.fontInfo().number() != FONT_IGNORE) {
3444 TextMetrics const & tm = cur.bv().textMetrics(text);
3445 if (cur.boundary() != tm.isRTLBoundary(cur.pit(), cur.pos(),
3446 cur.real_current_font))
3447 text->setCursor(cur, cur.pit(), cur.pos(),
3448 false, !cur.boundary());
3449 if (font.language() != ignore_language)
3450 // We need a buffer update if we change the language
3451 // (e.g., with info insets or if the selection contains
3453 cur.forceBufferUpdate();
3458 void moveCursor(Cursor & cur, bool selecting)
3460 if (selecting || cur.mark())
3465 void finishChange(Cursor & cur, bool selecting)
3468 moveCursor(cur, selecting);
3472 void mathDispatch(Cursor & cur, FuncRequest const & cmd)
3475 docstring sel = cur.selectionAsString(false);
3477 // It may happen that sel is empty but there is a selection
3478 replaceSelection(cur);
3480 // Is this a valid formula?
3484 #ifdef ENABLE_ASSERTIONS
3485 const int old_pos = cur.pos();
3487 cur.insert(new InsetMathHull(cur.buffer(), hullSimple));
3488 #ifdef ENABLE_ASSERTIONS
3489 LATTEST(old_pos == cur.pos());
3491 cur.nextInset()->edit(cur, true);
3492 if (cmd.action() != LFUN_MATH_MODE)
3493 // LFUN_MATH_MODE has a different meaning in math mode
3496 InsetMathHull * formula = new InsetMathHull(cur.buffer());
3497 string const selstr = to_utf8(sel);
3498 istringstream is(selstr);
3501 if (!formula->readQuiet(lex)) {
3502 // No valid formula, let's try with delims
3503 is.str("$" + selstr + "$");
3505 if (!formula->readQuiet(lex)) {
3506 // Still not valid, leave it as is
3513 cur.insert(formula);
3514 cur.nextInset()->edit(cur, true);
3515 LASSERT(cur.inMathed(), return);
3518 cur.selection(true);
3519 cur.pos() = cur.lastpos();
3520 if (cmd.action() != LFUN_MATH_MODE)
3521 // LFUN_MATH_MODE has a different meaning in math mode
3523 cur.clearSelection();
3524 cur.pos() = cur.lastpos();
3528 cur.message(from_utf8(N_("Math editor mode")));
3530 cur.message(from_utf8(N_("No valid math formula")));
3534 void regexpDispatch(Cursor & cur, FuncRequest const & cmd)
3536 LASSERT(cmd.action() == LFUN_REGEXP_MODE, return);
3537 if (cur.inRegexped()) {
3538 cur.message(_("Already in regular expression mode"));
3542 docstring sel = cur.selectionAsString(false);
3544 // It may happen that sel is empty but there is a selection
3545 replaceSelection(cur);
3547 cur.insert(new InsetMathHull(cur.buffer(), hullRegexp));
3548 cur.nextInset()->edit(cur, true);
3549 cur.niceInsert(sel);
3551 cur.message(_("Regexp editor mode"));
3555 void specialChar(Cursor & cur, InsetSpecialChar::Kind kind)
3558 cap::replaceSelection(cur);
3559 cur.insert(new InsetSpecialChar(kind));
3564 void ipaChar(Cursor & cur, InsetIPAChar::Kind kind)
3567 cap::replaceSelection(cur);
3568 cur.insert(new InsetIPAChar(kind));
3573 bool doInsertInset(Cursor & cur, Text * text,
3574 FuncRequest const & cmd, bool edit,
3575 bool pastesel, bool resetfont = false)
3577 Buffer & buffer = cur.bv().buffer();
3578 BufferParams const & bparams = buffer.params();
3579 Inset * inset = createInset(&buffer, cmd);
3583 if (InsetCollapsible * ci = inset->asInsetCollapsible())
3584 ci->setButtonLabel();
3587 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3588 bool cotextinsert = false;
3589 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3590 Layout const & lay = cur.paragraph().layout();
3591 Layout::LaTeXArgMap args = lay.args();
3592 Layout::LaTeXArgMap::const_iterator const lait = args.find(ia->name());
3593 if (lait != args.end())
3594 cotextinsert = (*lait).second.insertcotext;
3596 InsetLayout const & il = cur.inset().getLayout();
3598 Layout::LaTeXArgMap::const_iterator const ilait = args.find(ia->name());
3599 if (ilait != args.end())
3600 cotextinsert = (*ilait).second.insertcotext;
3602 // The argument requests to insert a copy of the co-text to the inset
3605 // If we have a selection within a paragraph, use this
3606 if (cur.selection() && cur.selBegin().pit() == cur.selEnd().pit())
3607 ds = cur.selectionAsString(false);
3608 // else use the whole paragraph
3610 ds = cur.paragraph().asString();
3611 text->insertInset(cur, inset);
3612 ia->init(cur.paragraph());
3614 inset->edit(cur, true);
3615 // Now put co-text into inset
3616 Font const f(inherit_font, cur.current_font.language());
3618 cur.text()->insertStringAsLines(cur, ds, f);
3619 cur.leaveInset(*inset);
3625 bool gotsel = false;
3626 bool move_layout = false;
3627 if (cur.selection()) {
3628 if (cmd.action() == LFUN_INDEX_INSERT)
3629 copySelectionToTemp(cur);
3631 cutSelectionToTemp(cur, pastesel);
3632 /* Move layout information inside the inset if the whole
3633 * paragraph and the inset allows setting layout
3634 * FIXME: this does not work as expected when change tracking is on
3635 * However, we do not really know what to do in this case.
3636 * FIXME: figure out a good test in the environment case (see #12251).
3638 if (cur.paragraph().layout().isCommand()
3639 && cur.paragraph().empty()
3640 && !inset->forcePlainLayout()) {
3641 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3645 cur.clearSelection();
3647 } else if (cmd.action() == LFUN_INDEX_INSERT) {
3648 gotsel = text->selectWordWhenUnderCursor(cur, WHOLE_WORD);
3649 copySelectionToTemp(cur);
3650 cur.clearSelection();
3652 text->insertInset(cur, inset);
3654 InsetText * inset_text = inset->asInsetText();
3656 Font const & font = inset->inheritFont()
3657 ? cur.bv().textMetrics(text).displayFont(cur.pit(), cur.pos())
3658 : bparams.getFont();
3659 inset_text->setOuterFont(cur.bv(), font.fontInfo());
3662 if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3663 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3664 ia->init(cur.paragraph());
3668 inset->edit(cur, true);
3670 if (!gotsel || !pastesel)
3673 pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
3674 cur.buffer()->errors("Paste");
3675 cur.clearSelection(); // bug 393
3679 // Reset of font (not language) is requested.
3680 // Used by InsetIndex (#11961).
3681 Language const * lang = cur.getFont().language();
3682 Font font(bparams.getFont().fontInfo(), lang);
3683 cur.paragraph().resetFonts(font);
3685 inset_text->fixParagraphsFont();
3688 /* If the containing paragraph has kept its layout, reset the
3689 * layout of the first paragraph of the inset.
3692 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3693 // FIXME: what does this do?
3694 if (cmd.action() == LFUN_FLEX_INSERT)
3697 cur.leaveInset(*inset);
3698 if (cmd.action() == LFUN_PREVIEW_INSERT
3699 || cmd.action() == LFUN_IPA_INSERT)
3701 notifyCursorLeavesOrEnters(old, cur);
3703 cur.leaveInset(*inset);
3704 // reset surrounding par to default
3705 DocumentClass const & dc = bparams.documentClass();
3706 docstring const layoutname = inset->usePlainLayout()
3707 ? dc.plainLayoutName()
3708 : dc.defaultLayoutName();
3709 text->setLayout(cur, layoutname);
3715 /// the type of outline operation
3717 OutlineUp, // Move this header with text down
3718 OutlineDown, // Move this header with text up
3719 OutlineIn, // Make this header deeper
3720 OutlineOut // Make this header shallower
3724 void insertSeparator(Cursor const & cur, depth_type const depth)
3726 Buffer & buf = *cur.buffer();
3727 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
3728 DocumentClass const & tc = buf.params().documentClass();
3729 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
3730 + from_ascii("\" ignoreautonests")));
3731 // FIXME: Bibitem mess!
3732 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
3733 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
3734 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
3735 while (cur.paragraph().params().depth() > depth)
3736 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
3740 void outline(OutlineOp mode, Cursor & cur, bool local)
3742 Buffer & buf = *cur.buffer();
3743 Text & text = *cur.text();
3744 pit_type & pit = cur.pit();
3745 ParagraphList & pars = text.paragraphs();
3746 ParagraphList::iterator const bgn = pars.begin();
3747 // The first paragraph of the area to be copied:
3748 ParagraphList::iterator start = pars.iterator_at(pit);
3749 // The final paragraph of area to be copied:
3750 ParagraphList::iterator finish = start;
3751 ParagraphList::iterator const end = pars.end();
3752 depth_type const current_depth = cur.paragraph().params().depth();
3754 int const thistoclevel = text.getTocLevel(distance(bgn, start));
3757 // Move out (down) from this section header
3761 if (!local || (mode != OutlineIn && mode != OutlineOut)) {
3762 // Seek the one (on same level) below
3763 for (; finish != end; ++finish) {
3764 toclevel = text.getTocLevel(distance(bgn, finish));
3765 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
3772 if (start == pars.begin())
3775 ParagraphList::iterator dest = start;
3776 // Move out (up) from this header
3779 // Search previous same-level header above
3782 toclevel = text.getTocLevel(distance(bgn, dest));
3784 && (toclevel == Layout::NOT_IN_TOC
3785 || toclevel > thistoclevel));
3786 // Not found; do nothing
3787 if (toclevel == Layout::NOT_IN_TOC || toclevel > thistoclevel)
3789 pit_type newpit = distance(bgn, dest);
3790 pit_type const len = distance(start, finish);
3791 pit_type const deletepit = pit + len;
3792 buf.undo().recordUndo(cur, newpit, deletepit - 1);
3793 // If we move an environment upwards, make sure it is
3794 // separated from its new neighbour below:
3795 // If an environment of the same layout follows, and the moved
3796 // paragraph sequence does not end with a separator, insert one.
3797 ParagraphList::iterator lastmoved = finish;
3799 if (start->layout().isEnvironment()
3800 && dest->layout() == start->layout()
3801 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3802 cur.pit() = distance(bgn, lastmoved);
3803 cur.pos() = cur.lastpos();
3804 insertSeparator(cur, current_depth);
3807 // Likewise, if we moved an environment upwards, make sure it
3808 // is separated from its new neighbour above.
3809 // The paragraph before the target of movement
3811 ParagraphList::iterator before = dest;
3813 // Get the parent paragraph (outer in nested context)
3814 pit_type const parent =
3815 before->params().depth() > current_depth
3816 ? text.depthHook(distance(bgn, before), current_depth)
3817 : distance(bgn, before);
3818 // If a environment with same layout preceeds the moved one in the new
3819 // position, and there is no separator yet, insert one.
3820 if (start->layout().isEnvironment()
3821 && pars[parent].layout() == start->layout()
3822 && !before->isEnvSeparator(before->beginOfBody())) {
3823 cur.pit() = distance(bgn, before);
3824 cur.pos() = cur.lastpos();
3825 insertSeparator(cur, current_depth);
3829 newpit = distance(bgn, dest);
3830 pars.splice(dest, start, finish);
3838 // Go one down from *this* header:
3839 ParagraphList::iterator dest = next(finish, 1);
3840 // Go further down to find header to insert in front of:
3841 for (; dest != end; ++dest) {
3842 toclevel = text.getTocLevel(distance(bgn, dest));
3843 if (toclevel != Layout::NOT_IN_TOC
3844 && toclevel <= thistoclevel)
3847 // One such was found, so go on...
3848 // If we move an environment downwards, make sure it is
3849 // separated from its new neighbour above.
3850 pit_type newpit = distance(bgn, dest);
3851 buf.undo().recordUndo(cur, pit, newpit - 1);
3852 // The paragraph before the target of movement
3853 ParagraphList::iterator before = dest;
3855 // Get the parent paragraph (outer in nested context)
3856 pit_type const parent =
3857 before->params().depth() > current_depth
3858 ? text.depthHook(distance(bgn, before), current_depth)
3859 : distance(bgn, before);
3860 // If a environment with same layout preceeds the moved one in the new
3861 // position, and there is no separator yet, insert one.
3862 if (start->layout().isEnvironment()
3863 && pars[parent].layout() == start->layout()
3864 && !before->isEnvSeparator(before->beginOfBody())) {
3865 cur.pit() = distance(bgn, before);
3866 cur.pos() = cur.lastpos();
3867 insertSeparator(cur, current_depth);
3870 // Likewise, make sure moved environments are separated
3871 // from their new neighbour below:
3872 // If an environment of the same layout follows, and the moved
3873 // paragraph sequence does not end with a separator, insert one.
3874 ParagraphList::iterator lastmoved = finish;
3877 && start->layout().isEnvironment()
3878 && dest->layout() == start->layout()
3879 && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3880 cur.pit() = distance(bgn, lastmoved);
3881 cur.pos() = cur.lastpos();
3882 insertSeparator(cur, current_depth);
3885 newpit = distance(bgn, dest);
3886 pit_type const len = distance(start, finish);
3887 pars.splice(dest, start, finish);
3888 cur.pit() = newpit - len;
3893 // We first iterate without actually doing something
3894 // in order to check whether the action flattens the structure.
3895 // If so, warn (#11178).
3896 ParagraphList::iterator cstart = start;
3897 bool strucchange = false;
3898 for (; cstart != finish; ++cstart) {
3899 toclevel = text.getTocLevel(distance(bgn, cstart));
3900 if (toclevel == Layout::NOT_IN_TOC)
3903 DocumentClass const & tc = buf.params().documentClass();
3904 int const newtoclevel =
3905 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3908 for (auto const & lay : tc) {
3909 if (lay.toclevel == newtoclevel
3910 && lay.isNumHeadingLabelType()
3911 && cstart->layout().isNumHeadingLabelType()) {
3922 && frontend::Alert::prompt(_("Action flattens document structure"),
3923 _("This action will cause some headings that have been "
3924 "on different level before to be on the same level "
3925 "since there is no more lower or higher heading level. "
3928 _("&Yes, continue nonetheless"),
3929 _("&No, quit operation")) == 1)
3932 pit_type const len = distance(start, finish);
3933 buf.undo().recordUndo(cur, pit, pit + len - 1);
3934 for (; start != finish; ++start) {
3935 toclevel = text.getTocLevel(distance(bgn, start));
3936 if (toclevel == Layout::NOT_IN_TOC)
3939 DocumentClass const & tc = buf.params().documentClass();
3940 int const newtoclevel =
3941 (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3943 for (auto const & lay : tc) {
3944 if (lay.toclevel == newtoclevel
3945 && lay.isNumHeadingLabelType()
3946 && start->layout().isNumHeadingLabelType()) {
3947 start->setLayout(lay);
3961 void Text::number(Cursor & cur)
3963 FontInfo font = ignore_font;
3964 font.setNumber(FONT_TOGGLE);
3965 toggleAndShow(cur, this, Font(font, ignore_language));
3969 bool Text::isRTL(pit_type const pit) const
3971 Buffer const & buffer = owner_->buffer();
3972 return pars_[pit].isRTL(buffer.params());
3978 Language const * getLanguage(Cursor const & cur, string const & lang)
3980 return lang.empty() ? cur.getFont().language() : languages.getLanguage(lang);
3984 docstring resolveLayout(docstring layout, DocIterator const & dit)
3986 Paragraph const & par = dit.paragraph();
3987 DocumentClass const & tclass = dit.buffer()->params().documentClass();
3990 layout = tclass.defaultLayoutName();
3992 if (dit.inset().forcePlainLayout(dit.idx()))
3993 // in this case only the empty layout is allowed
3994 layout = tclass.plainLayoutName();
3995 else if (par.usePlainLayout()) {
3996 // in this case, default layout maps to empty layout
3997 if (layout == tclass.defaultLayoutName())
3998 layout = tclass.plainLayoutName();
4000 // otherwise, the empty layout maps to the default
4001 if (layout == tclass.plainLayoutName())
4002 layout = tclass.defaultLayoutName();
4005 // If the entry is obsolete, use the new one instead.
4006 if (tclass.hasLayout(layout)) {
4007 docstring const & obs = tclass[layout].obsoleted_by();
4011 if (!tclass.hasLayout(layout))
4017 bool isAlreadyLayout(docstring const & layout, CursorData const & cur)
4019 ParagraphList const & pars = cur.text()->paragraphs();
4021 pit_type pit = cur.selBegin().pit();
4022 pit_type const epit = cur.selEnd().pit() + 1;
4023 for ( ; pit != epit; ++pit)
4024 if (pars[pit].layout().name() != layout)
4034 void Text::dispatch(Cursor & cur, FuncRequest & cmd)
4036 LYXERR(Debug::ACTION, "Text::dispatch: cmd: " << cmd);
4038 // Dispatch if the cursor is inside the text. It is not the
4039 // case for context menus (bug 5797).
4040 if (cur.text() != this) {
4045 BufferView * bv = &cur.bv();
4046 TextMetrics * tm = &bv->textMetrics(this);
4047 if (!tm->contains(cur.pit())) {
4048 lyx::dispatch(FuncRequest(LFUN_SCREEN_SHOW_CURSOR));
4049 tm = &bv->textMetrics(this);
4052 // FIXME: We use the update flag to indicates wether a singlePar or a
4053 // full screen update is needed. We reset it here but shall we restore it
4055 cur.noScreenUpdate();
4057 LBUFERR(this == cur.text());
4059 // NOTE: This should NOT be a reference. See commit 94a5481a.
4060 CursorSlice const oldTopSlice = cur.top();
4061 bool const oldBoundary = cur.boundary();
4062 bool const oldSelection = cur.selection();
4063 // Signals that, even if needsUpdate == false, an update of the
4064 // cursor paragraph is required
4065 bool singleParUpdate = lyxaction.funcHasFlag(cmd.action(),
4066 LyXAction::SingleParUpdate);
4067 // Signals that a full-screen update is required
4068 bool needsUpdate = !(lyxaction.funcHasFlag(cmd.action(),
4069 LyXAction::NoUpdate) || singleParUpdate);
4070 bool const last_misspelled = lyxrc.spellcheck_continuously
4071 && cur.paragraph().isMisspelled(cur.pos(), true);
4073 FuncCode const act = cmd.action();
4076 case LFUN_PARAGRAPH_MOVE_DOWN: {
4077 pit_type const pit = cur.pit();
4078 cur.recordUndo(pit, pit + 1);
4079 pars_.swap(pit, pit + 1);
4081 cur.forceBufferUpdate();
4086 case LFUN_PARAGRAPH_MOVE_UP: {
4087 pit_type const pit = cur.pit();
4088 cur.recordUndo(pit - 1, pit);
4090 pars_.swap(pit, pit - 1);
4093 cur.forceBufferUpdate();
4097 case LFUN_APPENDIX: {
4098 Paragraph & par = cur.paragraph();
4099 bool start = !par.params().startOfAppendix();
4101 // FIXME: The code below only makes sense at top level.
4102 // Should LFUN_APPENDIX be restricted to top-level paragraphs?
4103 // ensure that we have only one start_of_appendix in this document
4104 // FIXME: this don't work for multipart document!
4105 for (pit_type tmp = 0, end = pars_.size(); tmp != end; ++tmp) {
4106 if (pars_[tmp].params().startOfAppendix()) {
4107 cur.recordUndo(tmp, tmp);
4108 pars_[tmp].params().startOfAppendix(false);
4114 par.params().startOfAppendix(start);
4116 // we can set the refreshing parameters now
4117 cur.forceBufferUpdate();
4121 case LFUN_WORD_DELETE_FORWARD:
4122 if (cur.selection())
4123 cutSelection(cur, false);
4125 deleteWordForward(cur, cmd.getArg(0) != "confirm");
4126 finishChange(cur, false);
4129 case LFUN_WORD_DELETE_BACKWARD:
4130 if (cur.selection())
4131 cutSelection(cur, false);
4133 deleteWordBackward(cur, cmd.getArg(0) != "confirm");
4134 finishChange(cur, false);
4137 case LFUN_LINE_DELETE_FORWARD:
4138 if (cur.selection())
4139 cutSelection(cur, false);
4141 tm->deleteLineForward(cur);
4142 finishChange(cur, false);
4145 case LFUN_BUFFER_BEGIN:
4146 case LFUN_BUFFER_BEGIN_SELECT:
4147 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_BEGIN_SELECT);
4148 if (cur.depth() == 1)
4149 needsUpdate |= cursorTop(cur);
4152 cur.screenUpdateFlags(Update::FitCursor);
4155 case LFUN_BUFFER_END:
4156 case LFUN_BUFFER_END_SELECT:
4157 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_END_SELECT);
4158 if (cur.depth() == 1)
4159 needsUpdate |= cursorBottom(cur);
4162 cur.screenUpdateFlags(Update::FitCursor);
4165 case LFUN_INSET_BEGIN:
4166 case LFUN_INSET_BEGIN_SELECT:
4167 needsUpdate |= cur.selHandle(act == LFUN_INSET_BEGIN_SELECT);
4168 if (cur.depth() == 1 || !cur.top().at_begin())
4169 needsUpdate |= cursorTop(cur);
4172 cur.screenUpdateFlags(Update::FitCursor);
4175 case LFUN_INSET_END:
4176 case LFUN_INSET_END_SELECT:
4177 needsUpdate |= cur.selHandle(act == LFUN_INSET_END_SELECT);
4178 if (cur.depth() == 1 || !cur.top().at_end())
4179 needsUpdate |= cursorBottom(cur);
4182 cur.screenUpdateFlags(Update::FitCursor);
4185 case LFUN_CHAR_FORWARD:
4186 case LFUN_CHAR_FORWARD_SELECT: {
4187 //LYXERR0(" LFUN_CHAR_FORWARD[SEL]:\n" << cur);
4188 needsUpdate |= cur.selHandle(act == LFUN_CHAR_FORWARD_SELECT);
4189 bool const cur_moved = cursorForward(cur);
4190 needsUpdate |= cur_moved;
4192 if (!cur_moved && cur.depth() > 1
4193 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4195 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4197 // we will be moving out the inset, so we should execute
4198 // the depm-mechanism.
4199 // The cursor hasn't changed yet. To give the DEPM the
4200 // possibility of doing something we must provide it with
4201 // two different cursors.
4203 dummy.pos() = dummy.pit() = 0;
4204 if (cur.bv().checkDepm(dummy, cur))
4205 cur.forceBufferUpdate();
4210 case LFUN_CHAR_BACKWARD:
4211 case LFUN_CHAR_BACKWARD_SELECT: {
4212 //lyxerr << "handle LFUN_CHAR_BACKWARD[_SELECT]:\n" << cur << endl;
4213 needsUpdate |= cur.selHandle(act == LFUN_CHAR_BACKWARD_SELECT);
4214 bool const cur_moved = cursorBackward(cur);
4215 needsUpdate |= cur_moved;
4217 if (!cur_moved && cur.depth() > 1
4218 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4220 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4222 // we will be moving out the inset, so we should execute
4223 // the depm-mechanism.
4224 // The cursor hasn't changed yet. To give the DEPM the
4225 // possibility of doing something we must provide it with
4226 // two different cursors.
4228 dummy.pos() = cur.lastpos();
4229 dummy.pit() = cur.lastpit();
4230 if (cur.bv().checkDepm(dummy, cur))
4231 cur.forceBufferUpdate();
4236 case LFUN_CHAR_LEFT:
4237 case LFUN_CHAR_LEFT_SELECT:
4238 if (lyxrc.visual_cursor) {
4239 needsUpdate |= cur.selHandle(act == LFUN_CHAR_LEFT_SELECT);
4240 bool const cur_moved = cursorVisLeft(cur);
4241 needsUpdate |= cur_moved;
4242 if (!cur_moved && cur.depth() > 1
4243 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4245 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4248 if (cur.reverseDirectionNeeded()) {
4249 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4250 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4252 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4253 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4260 case LFUN_CHAR_RIGHT:
4261 case LFUN_CHAR_RIGHT_SELECT:
4262 if (lyxrc.visual_cursor) {
4263 needsUpdate |= cur.selHandle(cmd.action() == LFUN_CHAR_RIGHT_SELECT);
4264 bool const cur_moved = cursorVisRight(cur);
4265 needsUpdate |= cur_moved;
4266 if (!cur_moved && cur.depth() > 1
4267 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4269 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4272 if (cur.reverseDirectionNeeded()) {
4273 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4274 LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4276 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4277 LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4285 case LFUN_UP_SELECT:
4286 case LFUN_DOWN_SELECT:
4289 // stop/start the selection
4290 bool select = cmd.action() == LFUN_DOWN_SELECT ||
4291 cmd.action() == LFUN_UP_SELECT;
4293 // move cursor up/down
4294 bool up = cmd.action() == LFUN_UP_SELECT || cmd.action() == LFUN_UP;
4295 bool const atFirstOrLastRow = cur.atFirstOrLastRow(up);
4297 if (!atFirstOrLastRow) {
4298 needsUpdate |= cur.selHandle(select);
4299 cur.upDownInText(up, needsUpdate);
4300 needsUpdate |= cur.beforeDispatchCursor().inMathed();
4302 pos_type newpos = up ? 0 : cur.lastpos();
4303 if (lyxrc.mac_like_cursor_movement && cur.pos() != newpos) {
4304 needsUpdate |= cur.selHandle(select);
4305 // we do not reset the targetx of the cursor
4307 needsUpdate |= bv->checkDepm(cur, bv->cursor());
4308 cur.updateTextTargetOffset();
4310 cur.forceBufferUpdate();
4314 // if the cursor cannot be moved up or down do not remove
4315 // the selection right now, but wait for the next dispatch.
4317 needsUpdate |= cur.selHandle(select);
4318 cur.upDownInText(up, needsUpdate);
4325 case LFUN_PARAGRAPH_SELECT:
4327 needsUpdate |= setCursor(cur, cur.pit(), 0);
4328 needsUpdate |= cur.selHandle(true);
4329 if (cur.pos() < cur.lastpos())
4330 needsUpdate |= setCursor(cur, cur.pit(), cur.lastpos());
4333 case LFUN_PARAGRAPH_UP:
4334 case LFUN_PARAGRAPH_UP_SELECT:
4335 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_UP_SELECT);
4336 needsUpdate |= cursorUpParagraph(cur);
4339 case LFUN_PARAGRAPH_DOWN:
4340 case LFUN_PARAGRAPH_DOWN_SELECT:
4341 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_DOWN_SELECT);
4342 needsUpdate |= cursorDownParagraph(cur);
4345 case LFUN_LINE_BEGIN:
4346 case LFUN_LINE_BEGIN_SELECT:
4347 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_BEGIN_SELECT);
4348 needsUpdate |= tm->cursorHome(cur);
4352 case LFUN_LINE_END_SELECT:
4353 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_END_SELECT);
4354 needsUpdate |= tm->cursorEnd(cur);
4357 case LFUN_SECTION_SELECT: {
4358 Buffer const & buf = *cur.buffer();
4359 pit_type const pit = cur.pit();
4360 ParagraphList & pars = buf.text().paragraphs();
4361 ParagraphList::iterator bgn = pars.begin();
4362 // The first paragraph of the area to be selected:
4363 ParagraphList::iterator start = pars.iterator_at(pit);
4364 // The final paragraph of area to be selected:
4365 ParagraphList::iterator finish = start;
4366 ParagraphList::iterator end = pars.end();
4368 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
4369 if (thistoclevel == Layout::NOT_IN_TOC)
4373 Cursor const old_cur = cur;
4374 needsUpdate |= cur.selHandle(true);
4376 // Move out (down) from this section header
4380 // Seek the one (on same level) below
4381 for (; finish != end; ++finish, ++cur.pit()) {
4382 int const toclevel = buf.text().getTocLevel(distance(bgn, finish));
4383 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
4386 cur.pos() = cur.lastpos();
4387 cur.boundary(false);
4388 cur.setCurrentFont();
4390 needsUpdate |= cur != old_cur;
4394 case LFUN_WORD_RIGHT:
4395 case LFUN_WORD_RIGHT_SELECT:
4396 if (lyxrc.visual_cursor) {
4397 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_RIGHT_SELECT);
4398 bool const cur_moved = cursorVisRightOneWord(cur);
4399 needsUpdate |= cur_moved;
4400 if (!cur_moved && cur.depth() > 1
4401 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4403 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4406 if (cur.reverseDirectionNeeded()) {
4407 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4408 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4410 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4411 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4418 case LFUN_WORD_FORWARD:
4419 case LFUN_WORD_FORWARD_SELECT: {
4420 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_FORWARD_SELECT);
4421 bool const cur_moved = cursorForwardOneWord(cur);
4422 needsUpdate |= cur_moved;
4424 if (!cur_moved && cur.depth() > 1
4425 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4427 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4429 // we will be moving out the inset, so we should execute
4430 // the depm-mechanism.
4431 // The cursor hasn't changed yet. To give the DEPM the
4432 // possibility of doing something we must provide it with
4433 // two different cursors.
4435 dummy.pos() = dummy.pit() = 0;
4436 if (cur.bv().checkDepm(dummy, cur))
4437 cur.forceBufferUpdate();
4442 case LFUN_WORD_LEFT:
4443 case LFUN_WORD_LEFT_SELECT:
4444 if (lyxrc.visual_cursor) {
4445 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_LEFT_SELECT);
4446 bool const cur_moved = cursorVisLeftOneWord(cur);
4447 needsUpdate |= cur_moved;
4448 if (!cur_moved && cur.depth() > 1
4449 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4451 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4454 if (cur.reverseDirectionNeeded()) {
4455 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4456 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4458 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4459 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4466 case LFUN_WORD_BACKWARD:
4467 case LFUN_WORD_BACKWARD_SELECT: {
4468 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_BACKWARD_SELECT);
4469 bool const cur_moved = cursorBackwardOneWord(cur);
4470 needsUpdate |= cur_moved;
4472 if (!cur_moved && cur.depth() > 1
4473 && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4475 cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4477 // we will be moving out the inset, so we should execute
4478 // the depm-mechanism.
4479 // The cursor hasn't changed yet. To give the DEPM the
4480 // possibility of doing something we must provide it with
4481 // two different cursors.
4483 dummy.pos() = cur.lastpos();
4484 dummy.pit() = cur.lastpit();
4485 if (cur.bv().checkDepm(dummy, cur))
4486 cur.forceBufferUpdate();
4491 case LFUN_WORD_SELECT: {
4492 selectWord(cur, WHOLE_WORD);
4493 finishChange(cur, true);
4497 case LFUN_NEWLINE_INSERT: {
4498 InsetNewlineParams inp;
4499 docstring const & arg = cmd.argument();
4500 if (arg == "linebreak")
4501 inp.kind = InsetNewlineParams::LINEBREAK;
4503 inp.kind = InsetNewlineParams::NEWLINE;
4504 cap::replaceSelection(cur);
4506 cur.insert(new InsetNewline(inp));
4508 moveCursor(cur, false);
4512 case LFUN_TAB_INSERT: {
4513 bool const multi_par_selection = cur.selection() &&
4514 cur.selBegin().pit() != cur.selEnd().pit();
4515 if (multi_par_selection) {
4516 // If there is a multi-paragraph selection, a tab is inserted
4517 // at the beginning of each paragraph.
4518 cur.recordUndoSelection();
4519 pit_type const pit_end = cur.selEnd().pit();
4520 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4521 pars_[pit].insertChar(0, '\t',
4522 bv->buffer().params().track_changes);
4523 // Update the selection pos to make sure the selection does not
4524 // change as the inserted tab will increase the logical pos.
4525 if (cur.realAnchor().pit() == pit)
4526 cur.realAnchor().forwardPos();
4527 if (cur.pit() == pit)
4532 // Maybe we shouldn't allow tabs within a line, because they
4533 // are not (yet) aligned as one might do expect.
4534 FuncRequest ncmd(LFUN_SELF_INSERT, from_ascii("\t"));
4535 dispatch(cur, ncmd);
4540 case LFUN_TAB_DELETE: {
4541 bool const tc = bv->buffer().params().track_changes;
4542 if (cur.selection()) {
4543 // If there is a selection, a tab (if present) is removed from
4544 // the beginning of each paragraph.
4545 cur.recordUndoSelection();
4546 pit_type const pit_end = cur.selEnd().pit();
4547 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4548 Paragraph & par = paragraphs()[pit];
4551 char_type const c = par.getChar(0);
4552 if (c == '\t' || c == ' ') {
4553 // remove either 1 tab or 4 spaces.
4554 int const n = (c == ' ' ? 4 : 1);
4555 for (int i = 0; i < n
4556 && !par.empty() && par.getChar(0) == c; ++i) {
4557 if (cur.pit() == pit)
4559 if (cur.realAnchor().pit() == pit
4560 && cur.realAnchor().pos() > 0 )
4561 cur.realAnchor().backwardPos();
4562 par.eraseChar(0, tc);
4568 // If there is no selection, try to remove a tab or some spaces
4569 // before the position of the cursor.
4570 Paragraph & par = paragraphs()[cur.pit()];
4571 pos_type const pos = cur.pos();
4576 char_type const c = par.getChar(pos - 1);
4580 par.eraseChar(cur.pos(), tc);
4582 for (int n_spaces = 0;
4584 && par.getChar(cur.pos() - 1) == ' '
4588 par.eraseChar(cur.pos(), tc);
4595 case LFUN_CHAR_DELETE_FORWARD:
4596 if (!cur.selection()) {
4597 if (cur.pos() == cur.paragraph().size())
4598 // Par boundary, force full-screen update
4599 singleParUpdate = false;
4600 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion()) {
4602 cur.selection(true);
4607 needsUpdate |= erase(cur);
4610 cutSelection(cur, false);
4611 cur.setCurrentFont();
4612 singleParUpdate = false;
4614 moveCursor(cur, false);
4617 case LFUN_CHAR_DELETE_BACKWARD:
4618 if (!cur.selection()) {
4619 if (bv->getIntl().getTransManager().backspace()) {
4620 bool par_boundary = cur.pos() == 0;
4621 bool first_par = cur.pit() == 0;
4622 // Par boundary, full-screen update
4624 singleParUpdate = false;
4625 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion(true)) {
4627 cur.selection(true);
4632 needsUpdate |= backspace(cur);
4634 if (par_boundary && !first_par && cur.pos() > 0
4635 && cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
4636 needsUpdate |= backspace(cur);
4641 DocIterator const dit = cur.selectionBegin();
4642 cutSelection(cur, false);
4643 if (cur.buffer()->params().track_changes)
4644 // since we're doing backwards deletion,
4645 // and the selection is not really cut,
4646 // move cursor before selection (#11630)
4648 cur.setCurrentFont();
4649 singleParUpdate = false;
4653 case LFUN_PARAGRAPH_BREAK: {
4654 cap::replaceSelection(cur);
4655 pit_type pit = cur.pit();
4656 Paragraph const & par = pars_[pit];
4657 bool lastpar = (pit == pit_type(pars_.size() - 1));
4658 Paragraph const & nextpar = lastpar ? par : pars_[pit + 1];
4659 pit_type prev = pit > 0 ? depthHook(pit, par.getDepth()) : pit;
4660 if (prev < pit && cur.pos() == par.beginOfBody()
4661 && par.empty() && !par.isEnvSeparator(cur.pos())
4662 && !par.layout().keepempty
4663 && !par.layout().isCommand()
4664 && pars_[prev].layout() != par.layout()
4665 && pars_[prev].layout().isEnvironment()
4666 && !nextpar.isEnvSeparator(nextpar.beginOfBody())) {
4667 if (par.layout().isEnvironment()
4668 && pars_[prev].getDepth() == par.getDepth()) {
4669 docstring const layout = par.layout().name();
4670 DocumentClass const & tc = bv->buffer().params().documentClass();
4671 lyx::dispatch(FuncRequest(LFUN_LAYOUT, tc.plainLayout().name()));
4672 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4673 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
4674 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
4676 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4677 breakParagraph(cur);
4679 Font const f(inherit_font, cur.current_font.language());
4680 pars_[cur.pit() - 1].resetFonts(f);
4682 if (par.isEnvSeparator(cur.pos()) && cmd.getArg(1) != "ignoresep")
4684 breakParagraph(cur, cmd.getArg(0) == "inverse");
4687 // If we have a list and autoinsert item insets,
4689 Layout::LaTeXArgMap args = par.layout().args();
4690 for (auto const & thearg : args) {
4691 Layout::latexarg arg = thearg.second;
4692 if (arg.autoinsert && prefixIs(thearg.first, "item:")) {
4693 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, thearg.first);
4694 lyx::dispatch(cmd2);
4700 case LFUN_INSET_INSERT: {
4703 // We have to avoid triggering InstantPreview loading
4704 // before inserting into the document. See bug #5626.
4705 bool loaded = bv->buffer().isFullyLoaded();
4706 bv->buffer().setFullyLoaded(false);
4707 Inset * inset = createInset(&bv->buffer(), cmd);
4708 bv->buffer().setFullyLoaded(loaded);
4711 // FIXME (Abdel 01/02/2006):
4712 // What follows would be a partial fix for bug 2154:
4713 // http://www.lyx.org/trac/ticket/2154
4714 // This automatically put the label inset _after_ a
4715 // numbered section. It should be possible to extend the mechanism
4716 // to any kind of LateX environement.
4717 // The correct way to fix that bug would be at LateX generation.
4718 // I'll let the code here for reference as it could be used for some
4719 // other feature like "automatic labelling".
4721 Paragraph & par = pars_[cur.pit()];
4722 if (inset->lyxCode() == LABEL_CODE
4723 && !par.layout().counter.empty()) {
4724 // Go to the end of the paragraph
4725 // Warning: Because of Change-Tracking, the last
4726 // position is 'size()' and not 'size()-1':
4727 cur.pos() = par.size();
4728 // Insert a new paragraph
4729 FuncRequest fr(LFUN_PARAGRAPH_BREAK);
4733 if (cur.selection())
4734 cutSelection(cur, false);
4736 cur.forceBufferUpdate();
4737 if (inset->editable() && inset->asInsetText())
4738 inset->edit(cur, true);
4742 // trigger InstantPreview now
4743 if (inset->lyxCode() == EXTERNAL_CODE) {
4744 InsetExternal & ins =
4745 static_cast<InsetExternal &>(*inset);
4746 ins.updatePreview();
4753 case LFUN_INSET_DISSOLVE: {
4754 if (dissolveInset(cur)) {
4756 cur.forceBufferUpdate();
4761 case LFUN_INSET_SPLIT: {
4762 if (splitInset(cur)) {
4764 cur.forceBufferUpdate();
4769 case LFUN_GRAPHICS_SET_GROUP: {
4770 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
4776 string id = to_utf8(cmd.argument());
4777 string grp = graphics::getGroupParams(bv->buffer(), id);
4778 InsetGraphicsParams tmp, inspar = ins->getParams();
4781 inspar.groupId = to_utf8(cmd.argument());
4783 InsetGraphics::string2params(grp, bv->buffer(), tmp);
4784 tmp.filename = inspar.filename;
4788 ins->setParams(inspar);
4792 case LFUN_SPACE_INSERT:
4793 if (cur.paragraph().layout().free_spacing)
4794 insertChar(cur, ' ');
4796 doInsertInset(cur, this, cmd, false, false);
4799 moveCursor(cur, false);
4802 case LFUN_SPECIALCHAR_INSERT: {
4803 string const name = to_utf8(cmd.argument());
4804 if (name == "hyphenation")
4805 specialChar(cur, InsetSpecialChar::HYPHENATION);
4806 else if (name == "allowbreak")
4807 specialChar(cur, InsetSpecialChar::ALLOWBREAK);
4808 else if (name == "ligature-break")
4809 specialChar(cur, InsetSpecialChar::LIGATURE_BREAK);
4810 else if (name == "slash")
4811 specialChar(cur, InsetSpecialChar::SLASH);
4812 else if (name == "nobreakdash")
4813 specialChar(cur, InsetSpecialChar::NOBREAKDASH);
4814 else if (name == "dots")
4815 specialChar(cur, InsetSpecialChar::LDOTS);
4816 else if (name == "end-of-sentence")
4817 specialChar(cur, InsetSpecialChar::END_OF_SENTENCE);
4818 else if (name == "menu-separator")
4819 specialChar(cur, InsetSpecialChar::MENU_SEPARATOR);
4820 else if (name == "lyx")
4821 specialChar(cur, InsetSpecialChar::PHRASE_LYX);
4822 else if (name == "tex")
4823 specialChar(cur, InsetSpecialChar::PHRASE_TEX);
4824 else if (name == "latex")
4825 specialChar(cur, InsetSpecialChar::PHRASE_LATEX);
4826 else if (name == "latex2e")
4827 specialChar(cur, InsetSpecialChar::PHRASE_LATEX2E);
4828 else if (name.empty())
4829 lyxerr << "LyX function 'specialchar-insert' needs an argument." << endl;
4831 lyxerr << "Wrong argument for LyX function 'specialchar-insert'." << endl;
4835 case LFUN_IPAMACRO_INSERT: {
4836 string const arg = cmd.getArg(0);
4837 if (arg == "deco") {
4838 // Open the inset, and move the current selection
4840 doInsertInset(cur, this, cmd, true, true);
4842 // Some insets are numbered, others are shown in the outline pane so
4843 // let's update the labels and the toc backend.
4844 cur.forceBufferUpdate();
4847 if (arg == "tone-falling")
4848 ipaChar(cur, InsetIPAChar::TONE_FALLING);
4849 else if (arg == "tone-rising")
4850 ipaChar(cur, InsetIPAChar::TONE_RISING);
4851 else if (arg == "tone-high-rising")
4852 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING);
4853 else if (arg == "tone-low-rising")
4854 ipaChar(cur, InsetIPAChar::TONE_LOW_RISING);
4855 else if (arg == "tone-high-rising-falling")
4856 ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING_FALLING);
4857 else if (arg.empty())
4858 lyxerr << "LyX function 'ipamacro-insert' needs an argument." << endl;
4860 lyxerr << "Wrong argument for LyX function 'ipamacro-insert'." << endl;
4864 case LFUN_WORD_UPCASE:
4865 changeCase(cur, text_uppercase, cmd.getArg(0) == "partial");
4868 case LFUN_WORD_LOWCASE:
4869 changeCase(cur, text_lowercase, cmd.getArg(0) == "partial");
4872 case LFUN_WORD_CAPITALIZE:
4873 changeCase(cur, text_capitalization, cmd.getArg(0) == "partial");
4876 case LFUN_CHARS_TRANSPOSE:
4877 charsTranspose(cur);
4881 cur.message(_("Paste"));
4882 LASSERT(cur.selBegin().idx() == cur.selEnd().idx(), break);
4883 cap::replaceSelection(cur);
4885 // without argument?
4886 string const arg = to_utf8(cmd.argument());
4888 bool tryGraphics = true;
4889 if (theClipboard().isInternal())
4890 pasteFromStack(cur, bv->buffer().errorList("Paste"), 0);
4891 else if (theClipboard().hasTextContents()) {
4892 if (pasteClipboardText(cur, bv->buffer().errorList("Paste"),
4893 !cur.paragraph().parbreakIsNewline(),
4894 Clipboard::AnyTextType))
4895 tryGraphics = false;
4897 if (tryGraphics && theClipboard().hasGraphicsContents())
4898 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"));
4899 } else if (isStrUnsignedInt(arg)) {
4900 // we have a numerical argument
4901 pasteFromStack(cur, bv->buffer().errorList("Paste"),
4902 convert<unsigned int>(arg));
4903 } else if (arg == "html" || arg == "latex") {
4904 Clipboard::TextType type = (arg == "html") ?
4905 Clipboard::HtmlTextType : Clipboard::LaTeXTextType;
4906 pasteClipboardText(cur, bv->buffer().errorList("Paste"), true, type);
4908 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
4910 type = Clipboard::PdfGraphicsType;
4911 else if (arg == "png")
4912 type = Clipboard::PngGraphicsType;
4913 else if (arg == "jpeg")
4914 type = Clipboard::JpegGraphicsType;
4915 else if (arg == "linkback")
4916 type = Clipboard::LinkBackGraphicsType;
4917 else if (arg == "emf")
4918 type = Clipboard::EmfGraphicsType;
4919 else if (arg == "wmf")
4920 type = Clipboard::WmfGraphicsType;
4922 // we also check in getStatus()
4923 LYXERR0("Unrecognized graphics type: " << arg);
4925 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"), type);
4928 bv->buffer().errors("Paste");
4929 bv->buffer().updatePreviews(); // bug 11619
4930 cur.clearSelection(); // bug 393
4936 cutSelection(cur, true);
4937 cur.message(_("Cut"));
4940 case LFUN_SERVER_GET_XY:
4941 cur.message(from_utf8(
4942 convert<string>(tm->cursorX(cur.top(), cur.boundary()))
4943 + ' ' + convert<string>(tm->cursorY(cur.top(), cur.boundary()))));
4946 case LFUN_SERVER_SET_XY: {
4949 istringstream is(to_utf8(cmd.argument()));
4952 lyxerr << "SETXY: Could not parse coordinates in '"
4953 << to_utf8(cmd.argument()) << endl;
4955 tm->setCursorFromCoordinates(cur, x, y);
4959 case LFUN_SERVER_GET_LAYOUT:
4960 cur.message(cur.paragraph().layout().name());
4964 case LFUN_LAYOUT_TOGGLE: {
4965 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
4966 docstring req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
4967 LYXERR(Debug::INFO, "LFUN_LAYOUT: (arg) " << to_utf8(req_layout));
4969 docstring layout = resolveLayout(req_layout, cur);
4970 if (layout.empty()) {
4971 cur.errorMessage(from_utf8(N_("Layout ")) + req_layout +
4972 from_utf8(N_(" not known")));
4976 docstring const old_layout = cur.paragraph().layout().name();
4977 bool change_layout = !isAlreadyLayout(layout, cur);
4979 if (cmd.action() == LFUN_LAYOUT_TOGGLE && !change_layout) {
4980 change_layout = true;
4981 layout = resolveLayout(docstring(), cur);
4984 if (change_layout) {
4985 setLayout(cur, layout);
4986 if (cur.pit() > 0 && !ignoreautonests) {
4987 pit_type prev_pit = cur.pit() - 1;
4988 depth_type const cur_depth = pars_[cur.pit()].getDepth();
4989 // Scan for the previous par on same nesting level
4990 while (prev_pit > 0 && pars_[prev_pit].getDepth() > cur_depth)
4992 set<docstring> const & autonests =
4993 pars_[prev_pit].layout().autonests();
4994 set<docstring> const & autonested =
4995 pars_[cur.pit()].layout().isAutonestedBy();
4996 if (autonests.find(layout) != autonests.end()
4997 || autonested.find(old_layout) != autonested.end())
4998 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5002 DocumentClass const & tclass = bv->buffer().params().documentClass();
5003 bool inautoarg = false;
5004 for (auto const & la_pair : tclass[layout].args()) {
5005 Layout::latexarg const & arg = la_pair.second;
5006 if (arg.autoinsert) {
5007 // If we had already inserted an arg automatically,
5008 // leave this now in order to insert the next one.
5010 cur.leaveInset(cur.inset());
5013 FuncRequest const cmd2(LFUN_ARGUMENT_INSERT, la_pair.first);
5014 lyx::dispatch(cmd2);
5022 case LFUN_ENVIRONMENT_SPLIT: {
5023 bool const outer = cmd.argument() == "outer";
5024 bool const previous = cmd.argument() == "previous";
5025 bool const before = cmd.argument() == "before";
5026 bool const normal = cmd.argument().empty();
5027 Paragraph const & para = cur.paragraph();
5029 if (para.layout().isEnvironment())
5030 layout = para.layout().name();
5031 depth_type split_depth = cur.paragraph().params().depth();
5032 vector<depth_type> nextpars_depth;
5033 if (outer || previous) {
5034 // check if we have an environment in our scope
5035 pit_type pit = cur.pit();
5036 Paragraph cpar = pars_[pit];
5042 if (layout.empty() && previous
5043 && cpar.layout().isEnvironment()
5044 && cpar.params().depth() <= split_depth)
5045 layout = cpar.layout().name();
5046 if (cpar.params().depth() < split_depth
5047 && cpar.layout().isEnvironment()) {
5049 layout = cpar.layout().name();
5050 split_depth = cpar.params().depth();
5052 if (cpar.params().depth() == 0)
5056 if ((outer || normal) && cur.pit() < cur.lastpit()) {
5057 // save nesting of following paragraphs if they are deeper
5059 pit_type offset = 1;
5060 depth_type cur_depth = pars_[cur.pit()].params().depth();
5061 while (cur.pit() + offset <= cur.lastpit()) {
5062 Paragraph cpar = pars_[cur.pit() + offset];
5063 depth_type nextpar_depth = cpar.params().depth();
5064 if (cur_depth <= nextpar_depth && nextpar_depth > 0) {
5065 nextpars_depth.push_back(nextpar_depth);
5066 cur_depth = nextpar_depth;
5073 cur.top().setPitPos(cur.pit(), 0);
5074 if (before || cur.pos() > 0)
5075 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
5076 else if (previous && cur.nextInset() && cur.nextInset()->lyxCode() == SEPARATOR_CODE)
5077 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5079 while (cur.paragraph().params().depth() > split_depth)
5080 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
5082 DocumentClass const & tc = bv->buffer().params().documentClass();
5083 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
5084 + from_ascii("\" ignoreautonests")));
5085 // FIXME: Bibitem mess!
5086 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
5087 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
5088 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
5091 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5092 while (cur.paragraph().params().depth() < split_depth)
5093 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5096 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
5097 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
5098 if ((outer || normal) && !nextpars_depth.empty()) {
5099 // restore nesting of following paragraphs
5100 DocIterator scur = cur;
5101 depth_type max_depth = cur.paragraph().params().depth() + 1;
5102 for (auto nextpar_depth : nextpars_depth) {
5104 while (cur.paragraph().params().depth() < min(nextpar_depth, max_depth)) {
5105 depth_type const olddepth = cur.paragraph().params().depth();
5106 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5107 if (olddepth == cur.paragraph().params().depth())
5108 // leave loop if no incrementation happens
5111 max_depth = cur.paragraph().params().depth() + 1;
5113 cur.setCursor(scur);
5119 case LFUN_CLIPBOARD_PASTE:
5120 cap::replaceSelection(cur);
5121 pasteClipboardText(cur, bv->buffer().errorList("Paste"),
5122 cmd.argument() == "paragraph");
5123 bv->buffer().errors("Paste");
5126 case LFUN_CLIPBOARD_PASTE_SIMPLE:
5127 cap::replaceSelection(cur);
5128 pasteSimpleText(cur, cmd.argument() == "paragraph");
5131 case LFUN_PRIMARY_SELECTION_PASTE:
5132 cap::replaceSelection(cur);
5133 pasteString(cur, theSelection().get(),
5134 cmd.argument() == "paragraph");
5137 case LFUN_SELECTION_PASTE:
5138 // Copy the selection buffer to the clipboard stack,
5139 // because we want it to appear in the "Edit->Paste
5141 cap::replaceSelection(cur);
5142 cap::copySelectionToStack();
5143 cap::pasteSelection(bv->cursor(), bv->buffer().errorList("Paste"));
5144 bv->buffer().errors("Paste");
5147 case LFUN_QUOTE_INSERT: {
5148 cap::replaceSelection(cur);
5151 Paragraph const & par = cur.paragraph();
5152 pos_type pos = cur.pos();
5153 // Ignore deleted text before cursor
5154 while (pos > 0 && par.isDeleted(pos - 1))
5157 bool const inner = (cmd.getArg(0) == "single" || cmd.getArg(0) == "inner");
5159 // Guess quote side.
5160 // A space triggers an opening quote. This is passed if the preceding
5161 // char/inset is a space or at paragraph start.
5163 if (pos > 0 && !par.isSpace(pos - 1)) {
5164 if (cur.prevInset() && cur.prevInset()->lyxCode() == QUOTE_CODE) {
5165 // If an opening double quotation mark precedes, and this
5166 // is a single quote, make it opening as well
5168 static_cast<InsetQuotes &>(*cur.prevInset());
5169 string const type = ins.getType();
5170 if (!suffixIs(type, "ld") || !inner)
5171 c = par.getChar(pos - 1);
5173 else if (!cur.prevInset()
5174 || (cur.prevInset() && cur.prevInset()->isChar()))
5175 // If a char precedes, pass that and let InsetQuote decide
5176 c = par.getChar(pos - 1);
5179 if (par.getInset(pos - 1)
5180 && !par.getInset(pos - 1)->isPartOfTextSequence()) {
5181 // skip "invisible" insets
5185 c = par.getChar(pos - 1);
5190 QuoteLevel const quote_level = inner
5191 ? QuoteLevel::Secondary : QuoteLevel::Primary;
5192 cur.insert(new InsetQuotes(cur.buffer(), c, quote_level, cmd.getArg(1), cmd.getArg(2)));
5193 cur.buffer()->updateBuffer();
5198 case LFUN_MOUSE_TRIPLE:
5199 if (cmd.button() == mouse_button::button1) {
5201 setCursor(cur, cur.pit(), 0);
5204 if (cur.pos() < cur.lastpos())
5205 setCursor(cur, cur.pit(), cur.lastpos());
5211 case LFUN_MOUSE_DOUBLE:
5212 if (cmd.button() == mouse_button::button1) {
5213 selectWord(cur, WHOLE_WORD);
5218 // Single-click on work area
5219 case LFUN_MOUSE_PRESS: {
5220 // We are not marking a selection with the keyboard in any case.
5221 Cursor & bvcur = cur.bv().cursor();
5222 bvcur.setMark(false);
5223 switch (cmd.button()) {
5224 case mouse_button::button1:
5225 if (!bvcur.selection())
5227 bvcur.resetAnchor();
5228 if (!bv->mouseSetCursor(cur, cmd.modifier() == ShiftModifier))
5229 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5230 // FIXME: move this to mouseSetCursor?
5231 if (bvcur.wordSelection() && bvcur.inTexted())
5232 expandWordSel(bvcur);
5235 case mouse_button::button2:
5236 if (lyxrc.mouse_middlebutton_paste) {
5237 // Middle mouse pasting.
5238 bv->mouseSetCursor(cur);
5240 FuncRequest(LFUN_COMMAND_ALTERNATIVES,
5241 "selection-paste ; primary-selection-paste paragraph"));
5243 cur.noScreenUpdate();
5246 case mouse_button::button3: {
5247 // Don't do anything if we right-click a
5248 // selection, a context menu will popup.
5249 if (bvcur.selection() && cur >= bvcur.selectionBegin()
5250 && cur <= bvcur.selectionEnd()) {
5251 cur.noScreenUpdate();
5254 if (!bv->mouseSetCursor(cur, false))
5255 cur.screenUpdateFlags(Update::FitCursor);
5261 } // switch (cmd.button())
5264 case LFUN_MOUSE_MOTION: {
5265 // Mouse motion with right or middle mouse do nothing for now.
5266 if (cmd.button() != mouse_button::button1) {
5267 cur.noScreenUpdate();
5270 // ignore motions deeper nested than the real anchor
5271 Cursor & bvcur = cur.bv().cursor();
5272 if (!bvcur.realAnchor().hasPart(cur)) {
5276 CursorSlice old = bvcur.top();
5278 int const wh = bv->workHeight();
5279 int const y = max(0, min(wh - 1, cmd.y()));
5281 tm->setCursorFromCoordinates(cur, cmd.x(), y);
5282 cur.setTargetX(cmd.x());
5283 // Don't allow selecting a separator inset
5284 if (cur.pos() && cur.paragraph().isEnvSeparator(cur.pos() - 1))
5287 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5288 else if (cmd.y() < 0)
5289 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5290 // This is to allow jumping over large insets
5291 if (cur.top() == old) {
5293 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5294 else if (cmd.y() < 0)
5295 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5297 // We continue with our existing selection or start a new one, so don't
5298 // reset the anchor.
5299 bvcur.setCursor(cur);
5300 if (bvcur.wordSelection() && bvcur.inTexted())
5301 expandWordSel(bvcur);
5302 bvcur.selection(true);
5303 bvcur.setCurrentFont();
5304 if (cur.top() == old) {
5305 // We didn't move one iota, so no need to update the screen.
5306 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5307 //cur.noScreenUpdate();
5313 case LFUN_MOUSE_RELEASE:
5314 switch (cmd.button()) {
5315 case mouse_button::button1:
5316 // Cursor was set at LFUN_MOUSE_PRESS or LFUN_MOUSE_MOTION time.
5317 // If there is a new selection, update persistent selection;
5318 // otherwise, single click does not clear persistent selection
5320 if (cur.selection()) {
5321 // Finish selection. If double click,
5322 // cur is moved to the end of word by
5323 // selectWord but bvcur is current
5325 cur.bv().cursor().setSelection();
5326 // We might have removed an empty but drawn selection
5327 // (probably a margin)
5328 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5330 cur.noScreenUpdate();
5331 // FIXME: We could try to handle drag and drop of selection here.
5334 case mouse_button::button2:
5335 // Middle mouse pasting is handled at mouse press time,
5336 // see LFUN_MOUSE_PRESS.
5337 cur.noScreenUpdate();
5340 case mouse_button::button3:
5341 // Cursor was set at LFUN_MOUSE_PRESS time.
5342 // FIXME: If there is a selection we could try to handle a special
5343 // drag & drop context menu.
5344 cur.noScreenUpdate();
5347 case mouse_button::none:
5348 case mouse_button::button4:
5349 case mouse_button::button5:
5351 } // switch (cmd.button())
5355 case LFUN_SELF_INSERT: {
5356 if (cmd.argument().empty())
5359 // Automatically delete the currently selected
5360 // text and replace it with what is being
5361 // typed in now. Depends on lyxrc settings
5362 // "auto_region_delete", which defaults to
5365 if (lyxrc.auto_region_delete && cur.selection()) {
5366 cutSelection(cur, false);
5367 cur.setCurrentFont();
5369 cur.clearSelection();
5371 for (char_type c : cmd.argument())
5372 bv->translateAndInsert(c, this, cur);
5375 moveCursor(cur, false);
5376 cur.markNewWordPosition();
5377 bv->bookmarkEditPosition();
5381 case LFUN_HREF_INSERT: {
5382 docstring content = cmd.argument();
5383 if (content.empty() && cur.selection())
5384 content = cur.selectionAsString(false);
5386 InsetCommandParams p(HYPERLINK_CODE);
5387 if (!content.empty()){
5388 // if it looks like a link, we'll put it as target,
5389 // otherwise as name (bug #8792).
5392 // regex_match(to_utf8(content), matches, link_re)
5393 // because smatch stores pointers to the substrings rather
5394 // than making copies of them. And those pointers become
5395 // invalid after regex_match returns, since it is then
5396 // being given a temporary object. (Thanks to Georg for
5397 // figuring that out.)
5398 regex const link_re("^(([a-z]+):|www\\.).*");
5400 string const c = to_utf8(lowercase(content));
5402 if (c.substr(0,7) == "mailto:") {
5403 p["target"] = content;
5404 p["type"] = from_ascii("mailto:");
5405 } else if (regex_match(c, matches, link_re)) {
5406 p["target"] = content;
5407 string protocol = matches.str(1);
5408 if (protocol == "file")
5409 p["type"] = from_ascii("file:");
5411 p["name"] = content;
5413 string const data = InsetCommand::params2string(p);
5415 // we need to have a target. if we already have one, then
5416 // that gets used at the default for the name, too, which
5417 // is probably what is wanted.
5418 if (p["target"].empty()) {
5419 bv->showDialog("href", data);
5421 FuncRequest fr(LFUN_INSET_INSERT, data);
5427 case LFUN_LABEL_INSERT: {
5428 InsetCommandParams p(LABEL_CODE);
5429 // Try to generate a valid label
5430 p["name"] = (cmd.argument().empty()) ?
5431 cur.getPossibleLabel() :
5433 string const data = InsetCommand::params2string(p);
5435 if (cmd.argument().empty()) {
5436 bv->showDialog("label", data);
5438 FuncRequest fr(LFUN_INSET_INSERT, data);
5444 case LFUN_INFO_INSERT: {
5445 if (cmd.argument().empty()) {
5446 bv->showDialog("info", cur.current_font.language()->lang());
5449 inset = createInset(cur.buffer(), cmd);
5453 insertInset(cur, inset);
5454 cur.forceBufferUpdate();
5459 case LFUN_CAPTION_INSERT:
5460 case LFUN_FOOTNOTE_INSERT:
5461 case LFUN_NOTE_INSERT:
5462 case LFUN_BOX_INSERT:
5463 case LFUN_BRANCH_INSERT:
5464 case LFUN_PHANTOM_INSERT:
5465 case LFUN_ERT_INSERT:
5466 case LFUN_INDEXMACRO_INSERT:
5467 case LFUN_LISTING_INSERT:
5468 case LFUN_MARGINALNOTE_INSERT:
5469 case LFUN_ARGUMENT_INSERT:
5470 case LFUN_INDEX_INSERT:
5471 case LFUN_PREVIEW_INSERT:
5472 case LFUN_SCRIPT_INSERT:
5473 case LFUN_IPA_INSERT: {
5474 // Indexes reset font formatting (#11961)
5475 bool const resetfont = cmd.action() == LFUN_INDEX_INSERT;
5476 // Open the inset, and move the current selection
5478 doInsertInset(cur, this, cmd, true, true, resetfont);
5480 cur.setCurrentFont();
5481 // Some insets are numbered, others are shown in the outline pane so
5482 // let's update the labels and the toc backend.
5483 cur.forceBufferUpdate();
5487 case LFUN_FLEX_INSERT: {
5488 // Open the inset, and move the current selection
5490 bool const sel = cur.selection();
5491 doInsertInset(cur, this, cmd, true, true);
5492 // Insert auto-insert arguments
5493 bool autoargs = false, inautoarg = false;
5494 Layout::LaTeXArgMap args = cur.inset().getLayout().args();
5495 for (auto const & argt : args) {
5496 Layout::latexarg arg = argt.second;
5497 if (!inautoarg && arg.insertonnewline && cur.pos() > 0) {
5498 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5499 lyx::dispatch(cmd2);
5501 if (arg.autoinsert) {
5502 // The cursor might have been invalidated by the replaceSelection.
5503 cur.buffer()->changed(true);
5504 // If we had already inserted an arg automatically,
5505 // leave this now in order to insert the next one.
5507 cur.leaveInset(cur.inset());
5508 cur.setCurrentFont();
5510 if (arg.insertonnewline && cur.pos() > 0) {
5511 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5512 lyx::dispatch(cmd2);
5515 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, argt.first);
5516 lyx::dispatch(cmd2);
5523 cur.leaveInset(cur.inset());
5526 // Some insets are numbered, others are shown in the outline pane so
5527 // let's update the labels and the toc backend.
5528 cur.forceBufferUpdate();
5532 case LFUN_TABULAR_INSERT: {
5533 // if there were no arguments, just open the dialog
5534 if (cmd.argument().empty()) {
5535 bv->showDialog("tabularcreate");
5537 } else if (cur.buffer()->masterParams().tablestyle != "default"
5538 || bv->buffer().params().documentClass().tablestyle() != "default") {
5539 string tabstyle = cur.buffer()->masterParams().tablestyle;
5540 if (tabstyle == "default")
5541 tabstyle = bv->buffer().params().documentClass().tablestyle();
5542 if (!libFileSearch("tabletemplates", tabstyle + ".lyx").empty()) {
5543 FuncRequest fr(LFUN_TABULAR_STYLE_INSERT,
5544 tabstyle + " " + to_ascii(cmd.argument()));
5548 // Unknown style. Report and fall back to default.
5549 cur.errorMessage(from_utf8(N_("Table Style ")) + from_utf8(tabstyle) +
5550 from_utf8(N_(" not known")));
5552 if (doInsertInset(cur, this, cmd, false, true))
5557 case LFUN_TABULAR_STYLE_INSERT: {
5558 string const style = cmd.getArg(0);
5559 string const rows = cmd.getArg(1);
5560 string const cols = cmd.getArg(2);
5561 if (cols.empty() || !isStrInt(cols)
5562 || rows.empty() || !isStrInt(rows))
5564 int const r = convert<int>(rows);
5565 int const c = convert<int>(cols);
5572 FileName const tabstyle = libFileSearch("tabletemplates",
5573 style + suffix + ".lyx", "lyx");
5574 if (tabstyle.empty())
5576 UndoGroupHelper ugh(cur.buffer());
5578 FuncRequest cmd2(LFUN_FILE_INSERT, tabstyle.absFileName() + " ignorelang");
5579 lyx::dispatch(cmd2);
5583 // move one cell up to middle cell
5585 // add the missing rows
5586 int const addrows = r - 3;
5587 for (int i = 0 ; i < addrows ; ++i) {
5588 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-row");
5592 // add the missing columns
5593 int const addcols = c - 1;
5594 for (int i = 0 ; i < addcols ; ++i) {
5595 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-column");
5604 case LFUN_FLOAT_INSERT:
5605 case LFUN_FLOAT_WIDE_INSERT:
5606 case LFUN_WRAP_INSERT: {
5607 // will some content be moved into the inset?
5608 bool const content = cur.selection();
5609 // does the content consist of multiple paragraphs?
5610 bool const singlepar = (cur.selBegin().pit() == cur.selEnd().pit());
5612 doInsertInset(cur, this, cmd, true, true);
5615 // If some single-par content is moved into the inset,
5616 // doInsertInset puts the cursor outside the inset.
5617 // To insert the caption we put it back into the inset.
5618 // FIXME cleanup doInsertInset to avoid such dances!
5619 if (content && singlepar)
5622 ParagraphList & pars = cur.text()->paragraphs();
5624 DocumentClass const & tclass = bv->buffer().params().documentClass();
5626 // add a separate paragraph for the caption inset
5627 pars.push_back(Paragraph());
5628 pars.back().setInsetOwner(&cur.text()->inset());
5629 pars.back().setPlainOrDefaultLayout(tclass);
5630 int cap_pit = pars.size() - 1;
5632 // if an empty inset was created, we create an additional empty
5633 // paragraph at the bottom so that the user can choose where to put
5634 // the graphics (or table).
5636 pars.push_back(Paragraph());
5637 pars.back().setInsetOwner(&cur.text()->inset());
5638 pars.back().setPlainOrDefaultLayout(tclass);
5641 // reposition the cursor to the caption
5642 cur.pit() = cap_pit;
5644 // FIXME: This Text/Cursor dispatch handling is a mess!
5645 // We cannot use Cursor::dispatch here it needs access to up to
5647 FuncRequest cmd_caption(LFUN_CAPTION_INSERT);
5648 doInsertInset(cur, cur.text(), cmd_caption, true, false);
5649 cur.forceBufferUpdate();
5650 cur.screenUpdateFlags(Update::Force);
5651 // FIXME: When leaving the Float (or Wrap) inset we should
5652 // delete any empty paragraph left above or below the
5657 case LFUN_NOMENCL_INSERT: {
5658 InsetCommandParams p(NOMENCL_CODE);
5659 if (cmd.argument().empty()) {
5661 bv->cursor().innerText()->getStringForDialog(bv->cursor());
5662 cur.clearSelection();
5664 p["symbol"] = cmd.argument();
5665 string const data = InsetCommand::params2string(p);
5666 bv->showDialog("nomenclature", data);
5670 case LFUN_INDEX_PRINT: {
5671 InsetCommandParams p(INDEX_PRINT_CODE);
5672 if (cmd.argument().empty())
5673 p["type"] = from_ascii("idx");
5675 p["type"] = cmd.argument();
5676 string const data = InsetCommand::params2string(p);
5677 FuncRequest fr(LFUN_INSET_INSERT, data);
5682 case LFUN_NOMENCL_PRINT:
5683 case LFUN_NEWPAGE_INSERT:
5685 doInsertInset(cur, this, cmd, false, false);
5689 case LFUN_SEPARATOR_INSERT: {
5690 doInsertInset(cur, this, cmd, false, false);
5692 // remove a following space
5693 Paragraph & par = cur.paragraph();
5694 if (cur.pos() != cur.lastpos() && par.isLineSeparator(cur.pos()))
5695 par.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
5699 case LFUN_DEPTH_DECREMENT:
5700 changeDepth(cur, DEC_DEPTH);
5703 case LFUN_DEPTH_INCREMENT:
5704 changeDepth(cur, INC_DEPTH);
5707 case LFUN_REGEXP_MODE:
5708 regexpDispatch(cur, cmd);
5711 case LFUN_MATH_MODE: {
5712 if (cmd.argument() == "on" || cmd.argument() == "") {
5713 // don't pass "on" as argument
5714 // (it would appear literally in the first cell)
5715 docstring sel = cur.selectionAsString(false);
5716 InsetMathMacroTemplate * macro = new InsetMathMacroTemplate(cur.buffer());
5717 // create a macro template if we see "\\newcommand" somewhere, and
5718 // an ordinary formula otherwise
5720 && (sel.find(from_ascii("\\newcommand")) != string::npos
5721 || sel.find(from_ascii("\\newlyxcommand")) != string::npos
5722 || sel.find(from_ascii("\\def")) != string::npos)
5723 && macro->fromString(sel)) {
5725 replaceSelection(cur);
5728 // no meaningful macro template was found
5730 mathDispatch(cur,FuncRequest(LFUN_MATH_MODE));
5733 // The argument is meaningful
5734 // We replace cmd with LFUN_MATH_INSERT because LFUN_MATH_MODE
5735 // has a different meaning in math mode
5736 mathDispatch(cur, FuncRequest(LFUN_MATH_INSERT,cmd.argument()));
5740 case LFUN_MATH_MACRO:
5741 if (cmd.argument().empty())
5742 cur.errorMessage(from_utf8(N_("Missing argument")));
5745 string s = to_utf8(cmd.argument());
5746 string const s1 = token(s, ' ', 1);
5747 int const nargs = s1.empty() ? 0 : convert<int>(s1);
5748 string const s2 = token(s, ' ', 2);
5749 MacroType type = MacroTypeNewcommand;
5751 type = MacroTypeDef;
5752 InsetMathMacroTemplate * inset = new InsetMathMacroTemplate(cur.buffer(),
5753 from_utf8(token(s, ' ', 0)), nargs, false, type);
5754 inset->setBuffer(bv->buffer());
5755 insertInset(cur, inset);
5757 // enter macro inset and select the name
5759 cur.top().pos() = cur.top().lastpos();
5761 cur.selection(true);
5762 cur.top().pos() = 0;
5766 case LFUN_MATH_DISPLAY:
5767 case LFUN_MATH_SUBSCRIPT:
5768 case LFUN_MATH_SUPERSCRIPT:
5769 case LFUN_MATH_INSERT:
5770 case LFUN_MATH_AMS_MATRIX:
5771 case LFUN_MATH_MATRIX:
5772 case LFUN_MATH_DELIM:
5773 case LFUN_MATH_BIGDELIM:
5774 mathDispatch(cur, cmd);
5777 case LFUN_FONT_EMPH: {
5778 Font font(ignore_font, ignore_language);
5779 font.fontInfo().setEmph(FONT_TOGGLE);
5780 toggleAndShow(cur, this, font);
5784 case LFUN_FONT_ITAL: {
5785 Font font(ignore_font, ignore_language);
5786 font.fontInfo().setShape(ITALIC_SHAPE);
5787 toggleAndShow(cur, this, font);
5791 case LFUN_FONT_BOLD:
5792 case LFUN_FONT_BOLDSYMBOL: {
5793 Font font(ignore_font, ignore_language);
5794 font.fontInfo().setSeries(BOLD_SERIES);
5795 toggleAndShow(cur, this, font);
5799 case LFUN_FONT_NOUN: {
5800 Font font(ignore_font, ignore_language);
5801 font.fontInfo().setNoun(FONT_TOGGLE);
5802 toggleAndShow(cur, this, font);
5806 case LFUN_FONT_TYPEWRITER: {
5807 Font font(ignore_font, ignore_language);
5808 font.fontInfo().setFamily(TYPEWRITER_FAMILY); // no good
5809 toggleAndShow(cur, this, font);
5813 case LFUN_FONT_SANS: {
5814 Font font(ignore_font, ignore_language);
5815 font.fontInfo().setFamily(SANS_FAMILY);
5816 toggleAndShow(cur, this, font);
5820 case LFUN_FONT_ROMAN: {
5821 Font font(ignore_font, ignore_language);
5822 font.fontInfo().setFamily(ROMAN_FAMILY);
5823 toggleAndShow(cur, this, font);
5827 case LFUN_FONT_DEFAULT: {
5828 Font font(inherit_font, ignore_language);
5829 toggleAndShow(cur, this, font);
5833 case LFUN_FONT_STRIKEOUT: {
5834 Font font(ignore_font, ignore_language);
5835 font.fontInfo().setStrikeout(FONT_TOGGLE);
5836 toggleAndShow(cur, this, font);
5840 case LFUN_FONT_CROSSOUT: {
5841 Font font(ignore_font, ignore_language);
5842 font.fontInfo().setXout(FONT_TOGGLE);
5843 toggleAndShow(cur, this, font);
5847 case LFUN_FONT_UNDERUNDERLINE: {
5848 Font font(ignore_font, ignore_language);
5849 font.fontInfo().setUuline(FONT_TOGGLE);
5850 toggleAndShow(cur, this, font);
5854 case LFUN_FONT_UNDERWAVE: {
5855 Font font(ignore_font, ignore_language);
5856 font.fontInfo().setUwave(FONT_TOGGLE);
5857 toggleAndShow(cur, this, font);
5861 case LFUN_FONT_UNDERLINE: {
5862 Font font(ignore_font, ignore_language);
5863 font.fontInfo().setUnderbar(FONT_TOGGLE);
5864 toggleAndShow(cur, this, font);
5868 case LFUN_FONT_NO_SPELLCHECK: {
5869 Font font(ignore_font, ignore_language);
5870 font.fontInfo().setNoSpellcheck(FONT_TOGGLE);
5871 toggleAndShow(cur, this, font);
5875 case LFUN_FONT_SIZE: {
5876 Font font(ignore_font, ignore_language);
5877 setLyXSize(to_utf8(cmd.argument()), font.fontInfo());
5878 toggleAndShow(cur, this, font);
5882 case LFUN_LANGUAGE: {
5883 string const lang_arg = cmd.getArg(0);
5884 bool const reset = (lang_arg.empty() || lang_arg == "reset");
5885 Language const * lang =
5886 reset ? cur.bv().buffer().params().language
5887 : languages.getLanguage(lang_arg);
5888 // we allow reset_language, which is 0, but only if it
5889 // was requested via empty or "reset" arg.
5890 if (!lang && !reset)
5892 bool const toggle = (cmd.getArg(1) != "set");
5893 selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
5894 Font font(ignore_font, lang);
5895 toggleAndShow(cur, this, font, toggle);
5899 case LFUN_TEXTSTYLE_APPLY: {
5900 unsigned int num = 0;
5901 string const arg = to_utf8(cmd.argument());
5904 if (isStrUnsignedInt(arg)) {
5905 num = convert<uint>(arg);
5906 if (num >= freeFonts.size()) {
5907 cur.message(_("Invalid argument (number exceeds stack size)!"));
5911 cur.message(_("Invalid argument (must be a non-negative number)!"));
5915 toggleAndShow(cur, this, freeFonts[num].second, toggleall);
5916 cur.message(bformat(_("Text properties applied: %1$s"), freeFonts[num].first));
5920 // Set the freefont using the contents of \param data dispatched from
5921 // the frontends and apply it at the current cursor location.
5922 case LFUN_TEXTSTYLE_UPDATE: {
5923 Font font(ignore_font, ignore_language);
5925 if (font.fromString(to_utf8(cmd.argument()), toggle)) {
5926 docstring const props = font.stateText(&bv->buffer().params(), true);
5927 freeFonts.push(make_pair(props, font));
5929 toggleAndShow(cur, this, font, toggleall);
5930 cur.message(bformat(_("Text properties applied: %1$s"), props));
5932 LYXERR0("Invalid argument of textstyle-update");
5936 case LFUN_FINISHED_LEFT:
5937 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_LEFT:\n" << cur);
5938 // We're leaving an inset, going left. If the inset is LTR, we're
5939 // leaving from the front, so we should not move (remain at --- but
5940 // not in --- the inset). If the inset is RTL, move left, without
5941 // entering the inset itself; i.e., move to after the inset.
5942 if (cur.paragraph().getFontSettings(
5943 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5944 cursorVisLeft(cur, true);
5947 case LFUN_FINISHED_RIGHT:
5948 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_RIGHT:\n" << cur);
5949 // We're leaving an inset, going right. If the inset is RTL, we're
5950 // leaving from the front, so we should not move (remain at --- but
5951 // not in --- the inset). If the inset is LTR, move right, without
5952 // entering the inset itself; i.e., move to after the inset.
5953 if (!cur.paragraph().getFontSettings(
5954 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5955 cursorVisRight(cur, true);
5958 case LFUN_FINISHED_BACKWARD:
5959 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_BACKWARD:\n" << cur);
5960 cur.setCurrentFont();
5963 case LFUN_FINISHED_FORWARD:
5964 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_FORWARD:\n" << cur);
5966 cur.setCurrentFont();
5969 case LFUN_LAYOUT_PARAGRAPH: {
5971 params2string(cur.paragraph(), data);
5972 data = "show\n" + data;
5973 bv->showDialog("paragraph", data);
5977 case LFUN_PARAGRAPH_UPDATE: {
5979 params2string(cur.paragraph(), data);
5981 // Will the paragraph accept changes from the dialog?
5983 cur.inset().allowParagraphCustomization(cur.idx());
5985 data = "update " + convert<string>(accept) + '\n' + data;
5986 bv->updateDialog("paragraph", data);
5990 case LFUN_ACCENT_UMLAUT:
5991 case LFUN_ACCENT_CIRCUMFLEX:
5992 case LFUN_ACCENT_GRAVE:
5993 case LFUN_ACCENT_ACUTE:
5994 case LFUN_ACCENT_TILDE:
5995 case LFUN_ACCENT_PERISPOMENI:
5996 case LFUN_ACCENT_CEDILLA:
5997 case LFUN_ACCENT_MACRON:
5998 case LFUN_ACCENT_DOT:
5999 case LFUN_ACCENT_UNDERDOT:
6000 case LFUN_ACCENT_UNDERBAR:
6001 case LFUN_ACCENT_CARON:
6002 case LFUN_ACCENT_BREVE:
6003 case LFUN_ACCENT_TIE:
6004 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
6005 case LFUN_ACCENT_CIRCLE:
6006 case LFUN_ACCENT_OGONEK:
6007 theApp()->handleKeyFunc(cmd.action());
6008 if (!cmd.argument().empty())
6009 // FIXME: Are all these characters encoded in one byte in utf8?
6010 bv->translateAndInsert(cmd.argument()[0], this, cur);
6011 cur.screenUpdateFlags(Update::FitCursor);
6014 case LFUN_FLOAT_LIST_INSERT: {
6015 DocumentClass const & tclass = bv->buffer().params().documentClass();
6016 if (tclass.floats().typeExist(to_utf8(cmd.argument()))) {
6018 if (cur.selection())
6019 cutSelection(cur, false);
6020 breakParagraph(cur);
6022 if (cur.lastpos() != 0) {
6023 cursorBackward(cur);
6024 breakParagraph(cur);
6027 docstring const laystr = cur.inset().usePlainLayout() ?
6028 tclass.plainLayoutName() :
6029 tclass.defaultLayoutName();
6030 setLayout(cur, laystr);
6031 ParagraphParameters p;
6032 // FIXME If this call were replaced with one to clearParagraphParams(),
6033 // then we could get rid of this method altogether.
6034 setParagraphs(cur, p);
6035 // FIXME This should be simplified when InsetFloatList takes a
6036 // Buffer in its constructor.
6037 InsetFloatList * ifl = new InsetFloatList(cur.buffer(), to_utf8(cmd.argument()));
6038 ifl->setBuffer(bv->buffer());
6039 insertInset(cur, ifl);
6042 lyxerr << "Non-existent float type: "
6043 << to_utf8(cmd.argument()) << endl;
6048 case LFUN_CHANGE_ACCEPT: {
6049 acceptOrRejectChanges(cur, ACCEPT);
6053 case LFUN_CHANGE_REJECT: {
6054 acceptOrRejectChanges(cur, REJECT);
6058 case LFUN_THESAURUS_ENTRY: {
6059 Language const * language = cur.getFont().language();
6060 docstring arg = cmd.argument();
6062 arg = cur.selectionAsString(false);
6063 // Too large. We unselect if needed and try to get
6064 // the first word in selection or under cursor
6065 if (arg.size() > 100 || arg.empty()) {
6066 if (cur.selection()) {
6067 DocIterator selbeg = cur.selectionBegin();
6068 cur.clearSelection();
6069 setCursorIntern(cur, selbeg.pit(), selbeg.pos());
6070 cur.screenUpdateFlags(Update::Force);
6072 // Get word or selection
6073 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6074 arg = cur.selectionAsString(false);
6075 arg += " lang=" + from_ascii(language->lang());
6078 string lang = cmd.getArg(1);
6079 // This duplicates the code in GuiThesaurus::initialiseParams
6080 if (prefixIs(lang, "lang=")) {
6081 language = languages.getLanguage(lang.substr(5));
6083 language = cur.getFont().language();
6086 string lang = language->code();
6087 if (lyxrc.thesaurusdir_path.empty() && !thesaurus.thesaurusInstalled(from_ascii(lang))) {
6088 LYXERR(Debug::ACTION, "Command " << cmd << ". Thesaurus not found for language " << lang);
6089 frontend::Alert::warning(_("Path to thesaurus directory not set!"),
6090 _("The path to the thesaurus directory has not been specified.\n"
6091 "The thesaurus is not functional.\n"
6092 "Please refer to sec. 6.15.1 of the User's Guide for setup\n"
6095 bv->showDialog("thesaurus", to_utf8(arg));
6099 case LFUN_SPELLING_ADD: {
6100 Language const * language = getLanguage(cur, cmd.getArg(1));
6101 docstring word = from_utf8(cmd.getArg(0));
6103 word = cur.selectionAsString(false);
6105 if (word.size() > 100 || word.empty()) {
6106 // Get word or selection
6107 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6108 word = cur.selectionAsString(false);
6111 WordLangTuple wl(word, language);
6112 theSpellChecker()->insert(wl);
6116 case LFUN_SPELLING_ADD_LOCAL: {
6117 Language const * language = getLanguage(cur, cmd.getArg(1));
6118 docstring word = from_utf8(cmd.getArg(0));
6120 word = cur.selectionAsString(false);
6121 if (word.size() > 100)
6124 // Get word or selection
6125 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6126 word = cur.selectionAsString(false);
6129 WordLangTuple wl(word, language);
6130 if (!bv->buffer().params().spellignored(wl)) {
6131 cur.recordUndoBufferParams();
6132 bv->buffer().params().spellignore().push_back(wl);
6134 // trigger re-check of whole buffer
6135 bv->buffer().requestSpellcheck();
6140 case LFUN_SPELLING_REMOVE_LOCAL: {
6141 Language const * language = getLanguage(cur, cmd.getArg(1));
6142 docstring word = from_utf8(cmd.getArg(0));
6144 word = cur.selectionAsString(false);
6145 if (word.size() > 100)
6148 // Get word or selection
6149 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6150 word = cur.selectionAsString(false);
6153 WordLangTuple wl(word, language);
6154 bool has_item = false;
6155 vector<WordLangTuple>::const_iterator it = bv->buffer().params().spellignore().begin();
6156 for (; it != bv->buffer().params().spellignore().end(); ++it) {
6157 if (it->lang()->code() != wl.lang()->code())
6159 if (it->word() == wl.word()) {
6165 cur.recordUndoBufferParams();
6166 bv->buffer().params().spellignore().erase(it);
6168 // trigger re-check of whole buffer
6169 bv->buffer().requestSpellcheck();
6175 case LFUN_SPELLING_IGNORE: {
6176 Language const * language = getLanguage(cur, cmd.getArg(1));
6177 docstring word = from_utf8(cmd.getArg(0));
6179 word = cur.selectionAsString(false);
6181 if (word.size() > 100 || word.empty()) {
6182 // Get word or selection
6183 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6184 word = cur.selectionAsString(false);
6187 WordLangTuple wl(word, language);
6188 theSpellChecker()->accept(wl);
6192 case LFUN_SPELLING_REMOVE: {
6193 Language const * language = getLanguage(cur, cmd.getArg(1));
6194 docstring word = from_utf8(cmd.getArg(0));
6196 word = cur.selectionAsString(false);
6198 if (word.size() > 100 || word.empty()) {
6199 // Get word or selection
6200 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6201 word = cur.selectionAsString(false);
6204 WordLangTuple wl(word, language);
6205 theSpellChecker()->remove(wl);
6209 case LFUN_PARAGRAPH_PARAMS_APPLY: {
6210 // Given data, an encoding of the ParagraphParameters
6211 // generated in the Paragraph dialog, this function sets
6212 // the current paragraph, or currently selected paragraphs,
6214 // NOTE: This function overrides all existing settings.
6215 setParagraphs(cur, cmd.argument());
6216 cur.message(_("Paragraph layout set"));
6220 case LFUN_PARAGRAPH_PARAMS: {
6221 // Given data, an encoding of the ParagraphParameters as we'd
6222 // find them in a LyX file, this function modifies the current paragraph,
6223 // or currently selected paragraphs.
6224 // NOTE: This function only modifies, and does not override, existing
6226 setParagraphs(cur, cmd.argument(), true);
6227 cur.message(_("Paragraph layout set"));
6232 if (cur.selection()) {
6233 cur.selection(false);
6236 // This used to be LFUN_FINISHED_RIGHT, I think FORWARD is more
6237 // correct, but I'm not 100% sure -- dov, 071019
6238 cmd = FuncRequest(LFUN_FINISHED_FORWARD);
6242 case LFUN_OUTLINE_UP: {
6243 pos_type const opos = cur.pos();
6244 outline(OutlineUp, cur, false);
6245 setCursor(cur, cur.pit(), opos);
6246 cur.forceBufferUpdate();
6251 case LFUN_OUTLINE_DOWN: {
6252 pos_type const opos = cur.pos();
6253 outline(OutlineDown, cur, false);
6254 setCursor(cur, cur.pit(), opos);
6255 cur.forceBufferUpdate();
6260 case LFUN_OUTLINE_IN:
6261 outline(OutlineIn, cur, cmd.getArg(0) == "local");
6262 cur.forceBufferUpdate();
6266 case LFUN_OUTLINE_OUT:
6267 outline(OutlineOut, cur, cmd.getArg(0) == "local");
6268 cur.forceBufferUpdate();
6272 case LFUN_SERVER_GET_STATISTICS: {
6273 DocIterator from, to;
6274 if (cur.selection()) {
6275 from = cur.selectionBegin();
6276 to = cur.selectionEnd();
6278 from = doc_iterator_begin(cur.buffer());
6279 to = doc_iterator_end(cur.buffer());
6282 cur.buffer()->updateStatistics(from, to);
6283 string const arg0 = cmd.getArg(0);
6284 if (arg0 == "words") {
6285 cur.message(convert<docstring>(cur.buffer()->wordCount()));
6286 } else if (arg0 == "chars") {
6287 cur.message(convert<docstring>(cur.buffer()->charCount(false)));
6288 } else if (arg0 == "chars-space") {
6289 cur.message(convert<docstring>(cur.buffer()->charCount(true)));
6291 cur.message(convert<docstring>(cur.buffer()->wordCount()) + " "
6292 + convert<docstring>(cur.buffer()->charCount(false)) + " "
6293 + convert<docstring>(cur.buffer()->charCount(true)));
6299 LYXERR(Debug::ACTION, "Command " << cmd << " not DISPATCHED by Text");
6304 needsUpdate |= (cur.pos() != cur.lastpos()) && cur.selection();
6306 if (lyxrc.spellcheck_continuously && !needsUpdate) {
6307 // Check for misspelled text
6308 // The redraw is useful because of the painting of
6309 // misspelled markers depends on the cursor position.
6310 // Trigger a redraw for cursor moves inside misspelled text.
6311 if (!cur.inTexted()) {
6312 // move from regular text to math
6313 needsUpdate = last_misspelled;
6314 } else if (oldTopSlice != cur.top() || oldBoundary != cur.boundary()) {
6315 // move inside regular text
6316 needsUpdate = last_misspelled
6317 || cur.paragraph().isMisspelled(cur.pos(), true);
6321 // FIXME: The cursor flag is reset two lines below
6322 // so we need to check here if some of the LFUN did touch that.
6323 // for now only Text::erase() and Text::backspace() do that.
6324 // The plan is to verify all the LFUNs and then to remove this
6325 // singleParUpdate boolean altogether.
6326 if (cur.result().screenUpdate() & Update::Force) {
6327 singleParUpdate = false;
6331 // FIXME: the following code should go in favor of fine grained
6332 // update flag treatment.
6333 if (singleParUpdate) {
6334 // Inserting characters does not change par height in general. So, try
6335 // to update _only_ this paragraph. BufferView will detect if a full
6336 // metrics update is needed anyway.
6337 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
6341 && &oldTopSlice.inset() == &cur.inset()
6342 && oldTopSlice.idx() == cur.idx()
6343 && !oldSelection // oldSelection is a backup of cur.selection() at the beginning of the function.
6344 && !cur.selection())
6345 // FIXME: it would be better if we could just do this
6347 //if (cur.result().update() != Update::FitCursor)
6348 // cur.noScreenUpdate();
6350 // But some LFUNs do not set Update::FitCursor when needed, so we
6351 // do it for all. This is not very harmfull as FitCursor will provoke
6352 // a full redraw only if needed but still, a proper review of all LFUN
6353 // should be done and this needsUpdate boolean can then be removed.
6354 cur.screenUpdateFlags(Update::FitCursor);
6356 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
6360 bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
6361 FuncStatus & status) const
6363 LBUFERR(this == cur.text());
6365 FontInfo const & fontinfo = cur.real_current_font.fontInfo();
6367 bool allow_in_passthru = false;
6368 InsetCode code = NO_CODE;
6370 switch (cmd.action()) {
6372 case LFUN_DEPTH_DECREMENT:
6373 enable = changeDepthAllowed(cur, DEC_DEPTH);
6376 case LFUN_DEPTH_INCREMENT:
6377 enable = changeDepthAllowed(cur, INC_DEPTH);
6381 // FIXME We really should not allow this to be put, e.g.,
6382 // in a footnote, or in ERT. But it would make sense in a
6383 // branch, so I'm not sure what to do.
6384 status.setOnOff(cur.paragraph().params().startOfAppendix());
6387 case LFUN_DIALOG_SHOW_NEW_INSET:
6388 if (cmd.argument() == "bibitem")
6389 code = BIBITEM_CODE;
6390 else if (cmd.argument() == "bibtex") {
6392 // not allowed in description items
6393 enable = !inDescriptionItem(cur);
6395 else if (cmd.argument() == "box")
6397 else if (cmd.argument() == "branch")
6399 else if (cmd.argument() == "citation")
6401 else if (cmd.argument() == "counter")
6402 code = COUNTER_CODE;
6403 else if (cmd.argument() == "ert")
6405 else if (cmd.argument() == "external")
6406 code = EXTERNAL_CODE;
6407 else if (cmd.argument() == "float")
6409 else if (cmd.argument() == "graphics")
6410 code = GRAPHICS_CODE;
6411 else if (cmd.argument() == "href")
6412 code = HYPERLINK_CODE;
6413 else if (cmd.argument() == "include")
6414 code = INCLUDE_CODE;
6415 else if (cmd.argument() == "index")
6417 else if (cmd.argument() == "index_print")
6418 code = INDEX_PRINT_CODE;
6419 else if (cmd.argument() == "listings")
6420 code = LISTINGS_CODE;
6421 else if (cmd.argument() == "mathspace")
6422 code = MATH_HULL_CODE;
6423 else if (cmd.argument() == "nomenclature")
6424 code = NOMENCL_CODE;
6425 else if (cmd.argument() == "nomencl_print")
6426 code = NOMENCL_PRINT_CODE;
6427 else if (cmd.argument() == "label")
6429 else if (cmd.argument() == "line")
6431 else if (cmd.argument() == "note")
6433 else if (cmd.argument() == "phantom")
6434 code = PHANTOM_CODE;
6435 else if (cmd.argument() == "ref")
6437 else if (cmd.argument() == "space")
6439 else if (cmd.argument() == "toc")
6441 else if (cmd.argument() == "vspace")
6443 else if (cmd.argument() == "wrap")
6447 case LFUN_ERT_INSERT:
6450 case LFUN_LISTING_INSERT:
6451 code = LISTINGS_CODE;
6452 // not allowed in description items
6453 enable = !inDescriptionItem(cur);
6455 case LFUN_FOOTNOTE_INSERT:
6458 case LFUN_TABULAR_INSERT:
6459 code = TABULAR_CODE;
6461 case LFUN_TABULAR_STYLE_INSERT:
6462 code = TABULAR_CODE;
6464 case LFUN_MARGINALNOTE_INSERT:
6467 case LFUN_FLOAT_INSERT:
6468 case LFUN_FLOAT_WIDE_INSERT:
6469 // FIXME: If there is a selection, we should check whether there
6470 // are floats in the selection, but this has performance issues, see
6471 // LFUN_CHANGE_ACCEPT/REJECT.
6473 if (inDescriptionItem(cur))
6474 // not allowed in description items
6477 InsetCode const inset_code = cur.inset().lyxCode();
6479 // algorithm floats cannot be put in another float
6480 if (to_utf8(cmd.argument()) == "algorithm") {
6481 enable = inset_code != WRAP_CODE && inset_code != FLOAT_CODE;
6485 // for figures and tables: only allow in another
6486 // float or wrap if it is of the same type and
6487 // not a subfloat already
6488 if(cur.inset().lyxCode() == code) {
6489 InsetFloat const & ins =
6490 static_cast<InsetFloat const &>(cur.inset());
6491 enable = ins.params().type == to_utf8(cmd.argument())
6492 && !ins.params().subfloat;
6493 } else if(cur.inset().lyxCode() == WRAP_CODE) {
6494 InsetWrap const & ins =
6495 static_cast<InsetWrap const &>(cur.inset());
6496 enable = ins.params().type == to_utf8(cmd.argument());
6500 case LFUN_WRAP_INSERT:
6502 // not allowed in description items
6503 enable = !inDescriptionItem(cur);
6505 case LFUN_FLOAT_LIST_INSERT: {
6506 code = FLOAT_LIST_CODE;
6507 // not allowed in description items
6508 enable = !inDescriptionItem(cur);
6510 FloatList const & floats = cur.buffer()->params().documentClass().floats();
6511 FloatList::const_iterator cit = floats[to_ascii(cmd.argument())];
6512 // make sure we know about such floats
6513 if (cit == floats.end() ||
6514 // and that we know how to generate a list of them
6515 (!cit->second.usesFloatPkg() && cit->second.listCommand().empty())) {
6516 status.setUnknown(true);
6517 // probably not necessary, but...
6523 case LFUN_CAPTION_INSERT: {
6524 code = CAPTION_CODE;
6525 string arg = cmd.getArg(0);
6526 bool varia = arg != "Unnumbered"
6527 && cur.inset().allowsCaptionVariation(arg);
6528 // not allowed in description items,
6529 // and in specific insets
6530 enable = !inDescriptionItem(cur)
6531 && (varia || arg.empty() || arg == "Standard");
6534 case LFUN_NOTE_INSERT:
6537 case LFUN_FLEX_INSERT: {
6539 docstring s = from_utf8(cmd.getArg(0));
6540 // Prepend "Flex:" prefix if not there
6541 if (!prefixIs(s, from_ascii("Flex:")))
6542 s = from_ascii("Flex:") + s;
6543 if (!cur.buffer()->params().documentClass().hasInsetLayout(s))
6547 cur.buffer()->params().documentClass().insetLayout(s).lyxtype();
6548 if (ilt != InsetLyXType::CHARSTYLE
6549 && ilt != InsetLyXType::CUSTOM
6550 && ilt != InsetLyXType::STANDARD)
6555 case LFUN_BOX_INSERT:
6558 case LFUN_BRANCH_INSERT:
6560 if (cur.buffer()->masterBuffer()->params().branchlist().empty()
6561 && cur.buffer()->params().branchlist().empty())
6564 case LFUN_IPA_INSERT:
6567 case LFUN_PHANTOM_INSERT:
6568 code = PHANTOM_CODE;
6570 case LFUN_LABEL_INSERT:
6573 case LFUN_INFO_INSERT:
6575 enable = cmd.argument().empty()
6576 || infoparams.validateArgument(cur.buffer(), cmd.argument(), true);
6578 case LFUN_ARGUMENT_INSERT: {
6580 allow_in_passthru = true;
6581 string const arg = cmd.getArg(0);
6586 Layout const & lay = cur.paragraph().layout();
6587 Layout::LaTeXArgMap args = lay.args();
6588 Layout::LaTeXArgMap::const_iterator const lait =
6590 if (lait != args.end()) {
6592 pit_type pit = cur.pit();
6593 pit_type lastpit = cur.pit();
6594 if (lay.isEnvironment() && !prefixIs(arg, "item:")) {
6595 // In a sequence of "merged" environment layouts, we only allow
6596 // non-item arguments once.
6597 lastpit = cur.lastpit();
6598 // get the first paragraph in sequence with this layout
6599 depth_type const current_depth = cur.paragraph().params().depth();
6603 Paragraph cpar = pars_[pit - 1];
6604 if (cpar.layout() == lay && cpar.params().depth() == current_depth)
6610 for (; pit <= lastpit; ++pit) {
6611 if (pars_[pit].layout() != lay)
6613 for (auto const & table : pars_[pit].insetList())
6614 if (InsetArgument const * ins = table.inset->asInsetArgument())
6615 if (ins->name() == arg) {
6616 // we have this already
6625 case LFUN_INDEX_INSERT:
6628 case LFUN_INDEX_PRINT:
6629 code = INDEX_PRINT_CODE;
6630 // not allowed in description items
6631 enable = !inDescriptionItem(cur);
6633 case LFUN_NOMENCL_INSERT:
6634 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6638 code = NOMENCL_CODE;
6640 case LFUN_NOMENCL_PRINT:
6641 code = NOMENCL_PRINT_CODE;
6642 // not allowed in description items
6643 enable = !inDescriptionItem(cur);
6645 case LFUN_HREF_INSERT:
6646 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6650 code = HYPERLINK_CODE;
6652 case LFUN_INDEXMACRO_INSERT: {
6653 string const arg = cmd.getArg(0);
6654 if (arg == "sortkey")
6655 code = INDEXMACRO_SORTKEY_CODE;
6657 code = INDEXMACRO_CODE;
6660 case LFUN_IPAMACRO_INSERT: {
6661 string const arg = cmd.getArg(0);
6663 code = IPADECO_CODE;
6665 code = IPACHAR_CODE;
6668 case LFUN_QUOTE_INSERT:
6669 // always allow this, since we will inset a raw quote
6670 // if an inset is not allowed.
6671 allow_in_passthru = true;
6673 case LFUN_SPECIALCHAR_INSERT:
6674 code = SPECIALCHAR_CODE;
6676 case LFUN_SPACE_INSERT:
6677 // slight hack: we know this is allowed in math mode
6681 case LFUN_PREVIEW_INSERT:
6682 code = PREVIEW_CODE;
6684 case LFUN_SCRIPT_INSERT:
6688 case LFUN_MATH_INSERT:
6689 case LFUN_MATH_AMS_MATRIX:
6690 case LFUN_MATH_MATRIX:
6691 case LFUN_MATH_DELIM:
6692 case LFUN_MATH_BIGDELIM:
6693 case LFUN_MATH_DISPLAY:
6694 case LFUN_MATH_MODE:
6695 case LFUN_MATH_MACRO:
6696 case LFUN_MATH_SUBSCRIPT:
6697 case LFUN_MATH_SUPERSCRIPT:
6698 code = MATH_HULL_CODE;
6701 case LFUN_REGEXP_MODE:
6702 code = MATH_HULL_CODE;
6703 enable = cur.buffer()->isInternal() && !cur.inRegexped();
6706 case LFUN_INSET_MODIFY:
6707 // We need to disable this, because we may get called for a
6709 // InsetTabular::getStatus() -> InsetText::getStatus()
6710 // and we don't handle LFUN_INSET_MODIFY.
6714 case LFUN_FONT_EMPH:
6715 status.setOnOff(fontinfo.emph() == FONT_ON);
6716 enable = !cur.paragraph().isPassThru();
6719 case LFUN_FONT_ITAL:
6720 status.setOnOff(fontinfo.shape() == ITALIC_SHAPE);
6721 enable = !cur.paragraph().isPassThru();
6724 case LFUN_FONT_NOUN:
6725 status.setOnOff(fontinfo.noun() == FONT_ON);
6726 enable = !cur.paragraph().isPassThru();
6729 case LFUN_FONT_BOLD:
6730 case LFUN_FONT_BOLDSYMBOL:
6731 status.setOnOff(fontinfo.series() == BOLD_SERIES);
6732 enable = !cur.paragraph().isPassThru();
6735 case LFUN_FONT_SANS:
6736 status.setOnOff(fontinfo.family() == SANS_FAMILY);
6737 enable = !cur.paragraph().isPassThru();
6740 case LFUN_FONT_ROMAN:
6741 status.setOnOff(fontinfo.family() == ROMAN_FAMILY);
6742 enable = !cur.paragraph().isPassThru();
6745 case LFUN_FONT_TYPEWRITER:
6746 status.setOnOff(fontinfo.family() == TYPEWRITER_FAMILY);
6747 enable = !cur.paragraph().isPassThru();
6751 enable = cur.selection();
6755 if (cmd.argument().empty()) {
6756 if (theClipboard().isInternal())
6757 enable = cap::numberOfSelections() > 0;
6759 enable = !theClipboard().empty();
6763 // we have an argument
6764 string const arg = to_utf8(cmd.argument());
6765 if (isStrUnsignedInt(arg)) {
6766 // it's a number and therefore means the internal stack
6767 unsigned int n = convert<unsigned int>(arg);
6768 enable = cap::numberOfSelections() > n;
6772 // explicit text type?
6773 if (arg == "html") {
6774 // Do not enable for PlainTextType, since some tidying in the
6775 // frontend is needed for HTML, which is too unsafe for plain text.
6776 enable = theClipboard().hasTextContents(Clipboard::HtmlTextType);
6778 } else if (arg == "latex") {
6779 // LaTeX is usually not available on the clipboard with
6780 // the correct MIME type, but in plain text.
6781 enable = theClipboard().hasTextContents(Clipboard::PlainTextType) ||
6782 theClipboard().hasTextContents(Clipboard::LaTeXTextType);
6786 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
6788 type = Clipboard::PdfGraphicsType;
6789 else if (arg == "png")
6790 type = Clipboard::PngGraphicsType;
6791 else if (arg == "jpeg")
6792 type = Clipboard::JpegGraphicsType;
6793 else if (arg == "linkback")
6794 type = Clipboard::LinkBackGraphicsType;
6795 else if (arg == "emf")
6796 type = Clipboard::EmfGraphicsType;
6797 else if (arg == "wmf")
6798 type = Clipboard::WmfGraphicsType;
6801 LYXERR0("Unrecognized graphics type: " << arg);
6802 // we don't want to assert if the user just mistyped the LFUN
6803 LATTEST(cmd.origin() != FuncRequest::INTERNAL);
6807 enable = theClipboard().hasGraphicsContents(type);
6811 case LFUN_CLIPBOARD_PASTE:
6812 case LFUN_CLIPBOARD_PASTE_SIMPLE:
6813 enable = !theClipboard().empty();
6816 case LFUN_PRIMARY_SELECTION_PASTE:
6817 status.setUnknown(!theSelection().supported());
6818 enable = cur.selection() || !theSelection().empty();
6821 case LFUN_SELECTION_PASTE:
6822 enable = cap::selection();
6825 case LFUN_PARAGRAPH_MOVE_UP:
6826 enable = cur.pit() > 0 && !cur.selection();
6829 case LFUN_PARAGRAPH_MOVE_DOWN:
6830 enable = cur.pit() < cur.lastpit() && !cur.selection();
6833 case LFUN_CHANGE_ACCEPT:
6834 case LFUN_CHANGE_REJECT:
6835 if (!cur.selection())
6836 enable = cur.paragraph().isChanged(cur.pos());
6838 // will enable if there is a change in the selection
6841 // cheap improvement for efficiency: using cached
6842 // buffer variable, if there is no change in the
6843 // document, no need to check further.
6844 if (!cur.buffer()->areChangesPresent())
6847 for (DocIterator it = cur.selectionBegin(); ; it.forwardPar()) {
6848 pos_type const beg = it.pos();
6850 bool const in_last_par = (it.pit() == cur.selectionEnd().pit() &&
6851 it.idx() == cur.selectionEnd().idx());
6853 end = cur.selectionEnd().pos();
6855 // the +1 is needed for cases, e.g., where there is a
6856 // paragraph break. See #11629.
6857 end = it.lastpos() + 1;
6858 if (beg != end && it.paragraph().isChanged(beg, end)) {
6862 if (beg != end && it.paragraph().hasChangedInsets(beg, end)) {
6872 case LFUN_OUTLINE_UP:
6873 case LFUN_OUTLINE_DOWN:
6874 case LFUN_OUTLINE_IN:
6875 case LFUN_OUTLINE_OUT:
6876 enable = cur.text()->getTocLevel(cur.pit()) != Layout::NOT_IN_TOC;
6879 case LFUN_NEWLINE_INSERT:
6880 // LaTeX restrictions (labels or empty par)
6881 enable = !cur.paragraph().isPassThru()
6882 && cur.pos() > cur.paragraph().beginOfBody();
6885 case LFUN_SEPARATOR_INSERT:
6886 // Always enabled for now
6890 case LFUN_TAB_INSERT:
6891 case LFUN_TAB_DELETE:
6892 enable = cur.paragraph().isPassThru();
6895 case LFUN_GRAPHICS_SET_GROUP: {
6896 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
6900 status.setOnOff(to_utf8(cmd.argument()) == ins->getParams().groupId);
6904 case LFUN_NEWPAGE_INSERT:
6905 // not allowed in description items
6906 code = NEWPAGE_CODE;
6907 enable = !inDescriptionItem(cur);
6911 enable = !cur.paragraph().isPassThru();
6912 status.setOnOff(cmd.getArg(0) == cur.real_current_font.language()->lang());
6915 case LFUN_PARAGRAPH_BREAK:
6916 enable = inset().allowMultiPar();
6919 case LFUN_SPELLING_ADD:
6920 case LFUN_SPELLING_ADD_LOCAL:
6921 case LFUN_SPELLING_REMOVE_LOCAL:
6922 case LFUN_SPELLING_IGNORE:
6923 case LFUN_SPELLING_REMOVE:
6924 enable = theSpellChecker() != nullptr;
6925 if (enable && !cmd.getArg(1).empty()) {
6926 // validate explicitly given language
6927 Language const * const lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
6928 enable &= lang != nullptr;
6933 case LFUN_LAYOUT_TOGGLE: {
6934 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
6935 docstring const req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
6936 docstring const layout = resolveLayout(req_layout, cur);
6938 // FIXME: make this work in multicell selection case
6939 enable = !owner_->forcePlainLayout() && !layout.empty() && !cur.selIsMultiCell();
6940 status.setOnOff(!owner_->forcePlainLayout() && !cur.selIsMultiCell()
6941 && isAlreadyLayout(layout, cur));
6945 case LFUN_ENVIRONMENT_SPLIT: {
6946 if (cmd.argument() == "outer") {
6947 // check if we have an environment in our nesting hierarchy
6949 depth_type const current_depth = cur.paragraph().params().depth();
6950 pit_type pit = cur.pit();
6951 Paragraph cpar = pars_[pit];
6953 if (pit == 0 || cpar.params().depth() == 0)
6957 if (cpar.params().depth() < current_depth)
6958 res = cpar.layout().isEnvironment();
6963 else if (cmd.argument() == "previous") {
6964 // look if we have an environment in the previous par
6965 pit_type pit = cur.pit();
6966 Paragraph cpar = pars_[pit];
6970 enable = cpar.layout().isEnvironment();
6976 else if (cur.paragraph().layout().isEnvironment()) {
6977 enable = cmd.argument() == "before"
6978 || cur.pos() > 0 || !isFirstInSequence(cur.pit());
6985 case LFUN_LAYOUT_PARAGRAPH:
6986 case LFUN_PARAGRAPH_PARAMS:
6987 case LFUN_PARAGRAPH_PARAMS_APPLY:
6988 case LFUN_PARAGRAPH_UPDATE:
6989 enable = owner_->allowParagraphCustomization();
6992 // FIXME: why are accent lfuns forbidden with pass_thru layouts?
6993 // Because they insert COMBINING DIACRITICAL Unicode characters,
6994 // that cannot be handled by LaTeX but must be converted according
6995 // to the definition in lib/unicodesymbols?
6996 case LFUN_ACCENT_ACUTE:
6997 case LFUN_ACCENT_BREVE:
6998 case LFUN_ACCENT_CARON:
6999 case LFUN_ACCENT_CEDILLA:
7000 case LFUN_ACCENT_CIRCLE:
7001 case LFUN_ACCENT_CIRCUMFLEX:
7002 case LFUN_ACCENT_DOT:
7003 case LFUN_ACCENT_GRAVE:
7004 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
7005 case LFUN_ACCENT_MACRON:
7006 case LFUN_ACCENT_OGONEK:
7007 case LFUN_ACCENT_TIE:
7008 case LFUN_ACCENT_TILDE:
7009 case LFUN_ACCENT_PERISPOMENI:
7010 case LFUN_ACCENT_UMLAUT:
7011 case LFUN_ACCENT_UNDERBAR:
7012 case LFUN_ACCENT_UNDERDOT:
7013 case LFUN_FONT_FRAK:
7014 case LFUN_FONT_SIZE:
7015 case LFUN_FONT_STATE:
7016 case LFUN_FONT_UNDERLINE:
7017 case LFUN_FONT_STRIKEOUT:
7018 case LFUN_FONT_CROSSOUT:
7019 case LFUN_FONT_UNDERUNDERLINE:
7020 case LFUN_FONT_UNDERWAVE:
7021 case LFUN_FONT_NO_SPELLCHECK:
7022 case LFUN_TEXTSTYLE_UPDATE:
7023 enable = !cur.paragraph().isPassThru();
7026 case LFUN_FONT_DEFAULT: {
7027 Font font(inherit_font, ignore_language);
7028 BufferParams const & bp = cur.buffer()->masterParams();
7029 if (cur.selection()) {
7031 // Check if we have a non-default font attribute
7032 // in the selection range.
7033 DocIterator const from = cur.selectionBegin();
7034 DocIterator const to = cur.selectionEnd();
7035 for (DocIterator dit = from ; dit != to && !dit.atEnd(); ) {
7036 if (!dit.inTexted()) {
7040 Paragraph const & par = dit.paragraph();
7041 pos_type const pos = dit.pos();
7042 Font tmp = par.getFontSettings(bp, pos);
7043 if (tmp.fontInfo() != font.fontInfo()
7044 || tmp.language() != bp.language) {
7052 // Disable if all is default already.
7053 enable = (cur.current_font.fontInfo() != font.fontInfo()
7054 || cur.current_font.language() != bp.language);
7058 case LFUN_TEXTSTYLE_APPLY:
7059 enable = !freeFonts.empty();
7062 case LFUN_WORD_DELETE_FORWARD:
7063 case LFUN_WORD_DELETE_BACKWARD:
7064 case LFUN_LINE_DELETE_FORWARD:
7065 case LFUN_WORD_FORWARD:
7066 case LFUN_WORD_BACKWARD:
7067 case LFUN_WORD_RIGHT:
7068 case LFUN_WORD_LEFT:
7069 case LFUN_CHAR_FORWARD:
7070 case LFUN_CHAR_FORWARD_SELECT:
7071 case LFUN_CHAR_BACKWARD:
7072 case LFUN_CHAR_BACKWARD_SELECT:
7073 case LFUN_CHAR_LEFT:
7074 case LFUN_CHAR_LEFT_SELECT:
7075 case LFUN_CHAR_RIGHT:
7076 case LFUN_CHAR_RIGHT_SELECT:
7078 case LFUN_UP_SELECT:
7080 case LFUN_DOWN_SELECT:
7081 case LFUN_PARAGRAPH_SELECT:
7082 case LFUN_PARAGRAPH_UP_SELECT:
7083 case LFUN_PARAGRAPH_DOWN_SELECT:
7084 case LFUN_LINE_BEGIN_SELECT:
7085 case LFUN_LINE_END_SELECT:
7086 case LFUN_WORD_FORWARD_SELECT:
7087 case LFUN_WORD_BACKWARD_SELECT:
7088 case LFUN_WORD_RIGHT_SELECT:
7089 case LFUN_WORD_LEFT_SELECT:
7090 case LFUN_WORD_SELECT:
7091 case LFUN_SECTION_SELECT:
7092 case LFUN_BUFFER_BEGIN:
7093 case LFUN_BUFFER_END:
7094 case LFUN_BUFFER_BEGIN_SELECT:
7095 case LFUN_BUFFER_END_SELECT:
7096 case LFUN_INSET_BEGIN:
7097 case LFUN_INSET_END:
7098 case LFUN_INSET_BEGIN_SELECT:
7099 case LFUN_INSET_END_SELECT:
7100 case LFUN_PARAGRAPH_UP:
7101 case LFUN_PARAGRAPH_DOWN:
7102 case LFUN_LINE_BEGIN:
7104 case LFUN_CHAR_DELETE_FORWARD:
7105 case LFUN_CHAR_DELETE_BACKWARD:
7106 case LFUN_WORD_UPCASE:
7107 case LFUN_WORD_LOWCASE:
7108 case LFUN_WORD_CAPITALIZE:
7109 case LFUN_CHARS_TRANSPOSE:
7110 case LFUN_SERVER_GET_XY:
7111 case LFUN_SERVER_SET_XY:
7112 case LFUN_SERVER_GET_LAYOUT:
7113 case LFUN_SELF_INSERT:
7114 case LFUN_UNICODE_INSERT:
7115 case LFUN_THESAURUS_ENTRY:
7117 case LFUN_SERVER_GET_STATISTICS:
7118 // these are handled in our dispatch()
7122 case LFUN_INSET_INSERT: {
7123 string const type = cmd.getArg(0);
7124 if (type == "toc") {
7126 // not allowed in description items
7127 //FIXME: couldn't this be merged in Inset::insetAllowed()?
7128 enable = !inDescriptionItem(cur);
7135 case LFUN_SEARCH_IGNORE: {
7136 bool const value = cmd.getArg(1) == "true";
7137 setIgnoreFormat(cmd.getArg(0), value);
7147 || !cur.inset().insetAllowed(code)
7148 || (cur.paragraph().layout().pass_thru && !allow_in_passthru)))
7151 status.setEnabled(enable);
7156 void Text::pasteString(Cursor & cur, docstring const & clip,
7159 if (!clip.empty()) {
7162 insertStringAsParagraphs(cur, clip, cur.current_font);
7164 insertStringAsLines(cur, clip, cur.current_font);
7169 // FIXME: an item inset would make things much easier.
7170 bool Text::inDescriptionItem(Cursor const & cur) const
7172 Paragraph const & par = cur.paragraph();
7173 pos_type const pos = cur.pos();
7174 pos_type const body_pos = par.beginOfBody();
7176 if (par.layout().latextype != LATEX_LIST_ENVIRONMENT
7177 && (par.layout().latextype != LATEX_ITEM_ENVIRONMENT
7178 || par.layout().margintype != MARGIN_FIRST_DYNAMIC))
7181 return (pos < body_pos
7183 && (pos == 0 || par.getChar(pos - 1) != ' ')));
7187 std::vector<docstring> Text::getFreeFonts() const
7189 vector<docstring> ffList;
7191 for (auto const & f : freeFonts)
7192 ffList.push_back(f.first);