]> git.lyx.org Git - lyx.git/blob - src/Text.cpp
Prevent par break caused by \egroup (#12821)
[lyx.git] / src / Text.cpp
1 /**
2  * \file src/Text.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alfredo Braunstein
7  * \author Allan Rae
8  * \author André Pönitz
9  * \author Angus Leeming
10  * \author Asger Alstrup
11  * \author Dekel Tsur
12  * \author Dov Feldstern
13  * \author Jean-Marc Lasgouttes
14  * \author John Levon
15  * \author Jürgen Vigna
16  * \author Lars Gullik Bjønnes
17  * \author Stefan Schimanski
18  *
19  * Full author contact details are available in file CREDITS.
20  */
21
22 #include <config.h>
23
24 #include "Text.h"
25
26 #include "Author.h"
27 #include "BranchList.h"
28 #include "Buffer.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
31 #include "Changes.h"
32 #include "CompletionList.h"
33 #include "Cursor.h"
34 #include "CursorSlice.h"
35 #include "CutAndPaste.h"
36 #include "DispatchResult.h"
37 #include "Encoding.h"
38 #include "ErrorList.h"
39 #include "factory.h"
40 #include "FloatList.h"
41 #include "Font.h"
42 #include "FuncRequest.h"
43 #include "FuncStatus.h"
44 #include "InsetList.h"
45 #include "Intl.h"
46 #include "Language.h"
47 #include "Layout.h"
48 #include "Lexer.h"
49 #include "LyX.h"
50 #include "LyXAction.h"
51 #include "lyxfind.h"
52 #include "LyXRC.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"
60 #include "WordList.h"
61
62 #include "frontends/alert.h"
63 #include "frontends/Application.h"
64 #include "frontends/Clipboard.h"
65 #include "frontends/Selection.h"
66
67 #include "mathed/InsetMathHull.h"
68 #include "mathed/InsetMathMacroTemplate.h"
69
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"
89
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"
103
104 #include <clocale>
105 #include <regex>
106 #include <sstream>
107
108 using namespace std;
109
110 namespace lyx {
111 using namespace support;
112 using namespace cap;
113 using frontend::Clipboard;
114
115
116 namespace {
117
118 bool moveItem(Paragraph & fromPar, pos_type fromPos,
119         Paragraph & toPar, pos_type toPos, BufferParams const & params)
120 {
121         // Note: moveItem() does not honour change tracking!
122         // Therefore, it should only be used for breaking and merging paragraphs
123
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);
127
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)) {
132                         delete tmpInset;
133                         return false;
134                 }
135                 return true;
136         }
137
138         char_type const tmpChar = fromPar.getChar(fromPos);
139         fromPar.eraseChar(fromPos, false);
140         toPar.insertChar(toPos, tmpChar, tmpFont, tmpChange);
141         return true;
142 }
143
144
145 } //namespace
146
147
148 void breakParagraphConservative(BufferParams const & bparams,
149         ParagraphList & pars, pit_type pit, pos_type pos)
150 {
151         // create a new paragraph
152         Paragraph & tmp = *pars.insert(pars.iterator_at(pit + 1), Paragraph());
153         Paragraph & par = pars[pit];
154
155         tmp.setInsetOwner(&par.inInset());
156         tmp.makeSameLayout(par);
157
158         LASSERT(pos <= par.size(), return);
159
160         if (pos < par.size()) {
161                 // move everything behind the break position to the new paragraph
162                 pos_type pos_end = par.size() - 1;
163
164                 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
165                         if (moveItem(par, pos, tmp, j, bparams)) {
166                                 ++j;
167                         }
168                 }
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));
173         }
174 }
175
176
177 void mergeParagraph(BufferParams const & bparams,
178         ParagraphList & pars, pit_type par_offset)
179 {
180         Paragraph & next = pars[par_offset + 1];
181         Paragraph & par = pars[par_offset];
182
183         pos_type pos_end = next.size() - 1;
184         pos_type pos_insert = par.size();
185
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));
193         }
194
195         Change change = next.lookupChange(next.size());
196
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)) {
200                         ++j;
201                 }
202         }
203
204         // move the change of the end-of-paragraph character
205         par.setChange(par.size(), change);
206
207         pars.erase(pars.iterator_at(par_offset + 1));
208 }
209
210
211 Text::Text(InsetText * owner, bool use_default_layout)
212         : owner_(owner)
213 {
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);
220         else
221                 par.setPlainLayout(dc);
222 }
223
224
225 Text::Text(InsetText * owner, Text const & text)
226         : owner_(owner), pars_(text.pars_)
227 {
228         for (auto & p : pars_)
229                 p.setInsetOwner(owner);
230 }
231
232
233 pit_type Text::depthHook(pit_type pit, depth_type depth) const
234 {
235         pit_type newpit = pit;
236
237         if (newpit != 0)
238                 --newpit;
239
240         while (newpit != 0 && pars_[newpit].getDepth() > depth)
241                 --newpit;
242
243         if (pars_[newpit].getDepth() > depth)
244                 return pit;
245
246         return newpit;
247 }
248
249
250 pit_type Text::outerHook(pit_type par_offset) const
251 {
252         Paragraph const & par = pars_[par_offset];
253
254         if (par.getDepth() == 0)
255                 return pars_.size();
256         return depthHook(par_offset, par.getDepth() - 1);
257 }
258
259
260 bool Text::isFirstInSequence(pit_type par_offset) const
261 {
262         Paragraph const & par = pars_[par_offset];
263
264         pit_type dhook_offset = depthHook(par_offset, par.getDepth());
265
266         if (dhook_offset == par_offset)
267                 return true;
268
269         Paragraph const & dhook = pars_[dhook_offset];
270
271         return dhook.layout() != par.layout()
272                 || dhook.getDepth() != par.getDepth();
273 }
274
275
276 pit_type Text::lastInSequence(pit_type pit) const
277 {
278         depth_type const depth = pars_[pit].getDepth();
279         pit_type newpit = pit;
280
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())))
285                 ++newpit;
286
287         return newpit;
288 }
289
290
291 int Text::getTocLevel(pit_type par_offset) const
292 {
293         Paragraph const & par = pars_[par_offset];
294
295         if (par.layout().isEnvironment() && !isFirstInSequence(par_offset))
296                 return Layout::NOT_IN_TOC;
297
298         return par.layout().toclevel;
299 }
300
301
302 Font const Text::outerFont(pit_type par_offset) const
303 {
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
310                && 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();
317                 }
318         }
319
320         return Font(tmpfont);
321 }
322
323
324 int Text::getEndLabel(pit_type p) const
325 {
326         pit_type pit = p;
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;
331
332                 if (endlabeltype != END_LABEL_NO_LABEL) {
333                         if (p + 1 == pit_type(pars_.size()))
334                                 return endlabeltype;
335
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()))
340                                 return endlabeltype;
341                         break;
342                 }
343                 if (par_depth == 0)
344                         break;
345                 pit = outerHook(pit);
346                 if (pit != pit_type(pars_.size()))
347                         par_depth = pars_[pit].getDepth();
348         }
349         return END_LABEL_NO_LABEL;
350 }
351
352
353 static void acceptOrRejectChanges(ParagraphList & pars,
354         BufferParams const & bparams, Text::ChangeOp op)
355 {
356         pit_type pars_size = static_cast<pit_type>(pars.size());
357
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());
365                         else
366                                 pars[pit].rejectChanges(0, pars[pit].size());
367                 }
368         }
369
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));
378                         } else {
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));
383                                 } else {
384                                         mergeParagraph(bparams, pars, pit);
385                                         --pit;
386                                         --pars_size;
387                                 }
388                         }
389                 }
390         }
391 }
392
393
394 void acceptChanges(ParagraphList & pars, BufferParams const & bparams)
395 {
396         acceptOrRejectChanges(pars, bparams, Text::ACCEPT);
397 }
398
399
400 void rejectChanges(ParagraphList & pars, BufferParams const & bparams)
401 {
402         acceptOrRejectChanges(pars, bparams, Text::REJECT);
403 }
404
405
406 InsetText const & Text::inset() const
407 {
408         return *owner_;
409 }
410
411
412
413 void Text::readParToken(Paragraph & par, Lexer & lex,
414         string const & token, Font & font, Change & change, ErrorList & errorList)
415 {
416         Buffer * buf = &owner_->buffer();
417         BufferParams & bp = buf->params();
418
419         if (token[0] != '\\') {
420                 docstring dstr = lex.getDocString();
421                 par.appendString(dstr, font, change);
422
423         } else if (token == "\\begin_layout") {
424                 lex.eatLine();
425                 docstring layoutname = lex.getDocString();
426
427                 font = Font(inherit_font, bp.language);
428                 change = Change(Change::UNCHANGED);
429
430                 DocumentClass const & tclass = bp.documentClass();
431
432                 if (layoutname.empty())
433                         layoutname = tclass.defaultLayoutName();
434
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();
442                 } else {
443                         // otherwise, the empty layout maps to the default
444                         if (layoutname == tclass.plainLayoutName())
445                                 layoutname = tclass.defaultLayoutName();
446                 }
447
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);
453                 if (added_one) {
454                         // Warn the user.
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}));
458                 }
459
460                 par.setLayout(bp.documentClass()[layoutname]);
461
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()]);
466
467                 par.params().read(lex);
468
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);
477                 if (inset)
478                         par.insertInset(par.size(), inset, font, change);
479                 else {
480                         lex.eatLine();
481                         docstring line = lex.getDocString();
482                         errorList.push_back(ErrorItem(_("Unknown Inset"), line,
483                                                       {par.id(), 0}, {par.id(), -1}));
484                 }
485         } else if (token == "\\family") {
486                 lex.next();
487                 setLyXFamily(lex.getString(), font.fontInfo());
488         } else if (token == "\\series") {
489                 lex.next();
490                 setLyXSeries(lex.getString(), font.fontInfo());
491         } else if (token == "\\shape") {
492                 lex.next();
493                 setLyXShape(lex.getString(), font.fontInfo());
494         } else if (token == "\\size") {
495                 lex.next();
496                 setLyXSize(lex.getString(), font.fontInfo());
497         } else if (token == "\\lang") {
498                 lex.next();
499                 string const tok = lex.getString();
500                 Language const * lang = languages.getLanguage(tok);
501                 if (lang) {
502                         font.setLanguage(lang);
503                 } else {
504                         font.setLanguage(bp.language);
505                         lex.printError("Unknown language `$$Token'");
506                 }
507         } else if (token == "\\numeric") {
508                 lex.next();
509                 font.fontInfo().setNumber(setLyXMisc(lex.getString()));
510         } else if (token == "\\nospellcheck") {
511                 lex.next();
512                 font.fontInfo().setNoSpellcheck(setLyXMisc(lex.getString()));
513         } else if (token == "\\emph") {
514                 lex.next();
515                 font.fontInfo().setEmph(setLyXMisc(lex.getString()));
516         } else if (token == "\\bar") {
517                 lex.next();
518                 string const tok = lex.getString();
519
520                 if (tok == "under")
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);
526                 else
527                         lex.printError("Unknown bar font flag "
528                                        "`$$Token'");
529         } else if (token == "\\strikeout") {
530                 lex.next();
531                 font.fontInfo().setStrikeout(setLyXMisc(lex.getString()));
532         } else if (token == "\\xout") {
533                 lex.next();
534                 font.fontInfo().setXout(setLyXMisc(lex.getString()));
535         } else if (token == "\\uuline") {
536                 lex.next();
537                 font.fontInfo().setUuline(setLyXMisc(lex.getString()));
538         } else if (token == "\\uwave") {
539                 lex.next();
540                 font.fontInfo().setUwave(setLyXMisc(lex.getString()));
541         } else if (token == "\\noun") {
542                 lex.next();
543                 font.fontInfo().setNoun(setLyXMisc(lex.getString()));
544         } else if (token == "\\color") {
545                 lex.next();
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>();
551                 inset->read(lex);
552                 inset->setBuffer(*buf);
553                 par.insertInset(par.size(), inset.release(), font, change);
554         } else if (token == "\\SpecialCharNoPassThru") {
555                 lex.next();
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>();
560                 inset->read(lex);
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);
573                         else
574                                 par.insert(par.size(), from_ascii("---"), font, change);
575                 } else {
576                         if (token == "\\twohyphens")
577                                 par.insertChar(par.size(), 0x2013, font, change);
578                         else
579                                 par.insertChar(par.size(), 0x2014, font, change);
580                 }
581         } else if (token == "\\backslash") {
582                 par.appendChar('\\', font, change);
583         } else if (token == "\\LyXTable") {
584                 auto inset = make_unique<InsetTabular>(buf);
585                 inset->read(lex);
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") {
590                 lex.eatLine();
591                 istringstream is(lex.getString());
592                 int aid;
593                 time_t ct;
594                 is >> aid >> ct;
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"),
605                                         aid),
606                                 {par.id(), par.size()}, {par.id(), par.size() + 1}));
607                         bp.addAuthor(Author(aid));
608                 }
609                 if (token == "\\change_inserted")
610                         change = Change(Change::INSERTED, am.find(aid)->second, ct);
611                 else
612                         change = Change(Change::DELETED, am.find(aid)->second, ct);
613         } else {
614                 lex.eatLine();
615                 errorList.push_back(ErrorItem(_("Unknown token"),
616                                               bformat(_("Unknown token: %1$s %2$s\n"),
617                                                       from_utf8(token),
618                                                       lex.getDocString()),
619                                               {par.id(), 0}, {par.id(), -1}));
620         }
621 }
622
623
624 void Text::readParagraph(Paragraph & par, Lexer & lex,
625         ErrorList & errorList)
626 {
627         lex.nextToken();
628         string token = lex.getString();
629         Font font;
630         Change change(Change::UNCHANGED);
631
632         while (lex.isOK()) {
633                 readParToken(par, lex, token, font, change, errorList);
634
635                 lex.nextToken();
636                 token = lex.getString();
637
638                 if (token.empty())
639                         continue;
640
641                 if (token == "\\end_layout") {
642                         //Ok, paragraph finished
643                         break;
644                 }
645
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";
654                         break;
655                 }
656         }
657         // Final change goes to paragraph break:
658         if (inset().allowMultiPar())
659                 par.setChange(par.size(), change);
660
661         // Initialize begin_of_body_ on load; redoParagraph maintains
662         par.setBeginOfBody();
663
664         // mark paragraph for spell checking on load
665         // par.requestSpellCheck();
666 }
667
668
669 class TextCompletionList : public CompletionList
670 {
671 public:
672         ///
673         TextCompletionList(Cursor const & cur, WordList const & list)
674                 : buffer_(cur.buffer()), list_(list)
675         {}
676         ///
677         virtual ~TextCompletionList() {}
678
679         ///
680         bool sorted() const override { return true; }
681         ///
682         size_t size() const override
683         {
684                 return list_.size();
685         }
686         ///
687         docstring const & data(size_t idx) const override
688         {
689                 return list_.word(idx);
690         }
691
692 private:
693         ///
694         Buffer const * buffer_;
695         ///
696         WordList const & list_;
697 };
698
699
700 bool Text::empty() const
701 {
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);
705 }
706
707
708 double Text::spacing(Paragraph const & par) const
709 {
710         if (par.params().spacing().isDefault())
711                 return owner_->buffer().params().spacing().getValue();
712         return par.params().spacing().getValue();
713 }
714
715
716 /**
717  * This breaks a paragraph at the specified position.
718  * The new paragraph will:
719  * - Decrease depth by one (or change layout to default layout) when
720  *    keep_layout == false
721  * - keep current depth and layout when keep_layout == true
722  */
723 static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
724                     bool keep_layout)
725 {
726         BufferParams const & bparams = text.inset().buffer().params();
727         ParagraphList & pars = text.paragraphs();
728         // create a new paragraph, and insert into the list
729         ParagraphList::iterator tmp =
730                 pars.insert(pars.iterator_at(par_offset + 1), Paragraph());
731
732         Paragraph & par = pars[par_offset];
733
734         // remember to set the inset_owner
735         tmp->setInsetOwner(&par.inInset());
736         // without doing that we get a crash when typing <Return> at the
737         // end of a paragraph
738         tmp->setPlainOrDefaultLayout(bparams.documentClass());
739
740         if (keep_layout) {
741                 tmp->setLayout(par.layout());
742                 tmp->setLabelWidthString(par.params().labelWidthString());
743                 tmp->params().depth(par.params().depth());
744         } else if (par.params().depth() > 0) {
745                 Paragraph const & hook = pars[text.outerHook(par_offset)];
746                 tmp->setLayout(hook.layout());
747                 // not sure the line below is useful
748                 tmp->setLabelWidthString(par.params().labelWidthString());
749                 tmp->params().depth(hook.params().depth());
750         }
751
752         bool const isempty = (par.allowEmpty() && par.empty());
753
754         if (!isempty && (par.size() > pos || par.empty())) {
755                 tmp->setLayout(par.layout());
756                 tmp->params().align(par.params().align());
757                 tmp->setLabelWidthString(par.params().labelWidthString());
758
759                 tmp->params().depth(par.params().depth());
760                 tmp->params().noindent(par.params().noindent());
761                 tmp->params().spacing(par.params().spacing());
762
763                 // move everything behind the break position
764                 // to the new paragraph
765
766                 /* Note: if !keepempty, empty() == true, then we reach
767                  * here with size() == 0. So pos_end becomes - 1. This
768                  * doesn't cause problems because both loops below
769                  * enforce pos <= pos_end and 0 <= pos
770                  */
771                 pos_type pos_end = par.size() - 1;
772
773                 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
774                         if (moveItem(par, pos, *tmp, j, bparams)) {
775                                 ++j;
776                         }
777                 }
778         }
779
780         // Move over the end-of-par change information
781         tmp->setChange(tmp->size(), par.lookupChange(par.size()));
782         par.setChange(par.size(), Change(bparams.track_changes ?
783                                            Change::INSERTED : Change::UNCHANGED));
784
785         if (pos) {
786                 // Make sure that we keep the language when
787                 // breaking paragraph.
788                 if (tmp->empty()) {
789                         Font changed = tmp->getFirstFontSettings(bparams);
790                         Font const & old = par.getFontSettings(bparams, par.size());
791                         changed.setLanguage(old.language());
792                         tmp->setFont(0, changed);
793                 }
794
795                 return;
796         }
797
798         if (!isempty) {
799                 bool const soa = par.params().startOfAppendix();
800                 par.params().clear();
801                 // do not lose start of appendix marker (bug 4212)
802                 par.params().startOfAppendix(soa);
803                 par.setPlainOrDefaultLayout(bparams.documentClass());
804         }
805
806         if (keep_layout) {
807                 par.setLayout(tmp->layout());
808                 par.setLabelWidthString(tmp->params().labelWidthString());
809                 par.params().depth(tmp->params().depth());
810         }
811 }
812
813
814 void Text::breakParagraph(Cursor & cur, bool inverse_logic)
815 {
816         LBUFERR(this == cur.text());
817
818         Paragraph & cpar = cur.paragraph();
819         pit_type cpit = cur.pit();
820
821         DocumentClass const & tclass = cur.buffer()->params().documentClass();
822         Layout const & layout = cpar.layout();
823
824         if (cur.lastpos() == 0 && !cpar.allowEmpty()) {
825                 if (changeDepthAllowed(cur, DEC_DEPTH)) {
826                         changeDepth(cur, DEC_DEPTH);
827                         pit_type const prev = depthHook(cpit, cpar.getDepth());
828                         docstring const & lay = pars_[prev].layout().name();
829                         if (lay != layout.name())
830                                 setLayout(cur, lay);
831                 } else {
832                         docstring const & lay = cur.paragraph().usePlainLayout()
833                             ? tclass.plainLayoutName() : tclass.defaultLayoutName();
834                         if (lay != layout.name())
835                                 setLayout(cur, lay);
836                 }
837                 return;
838         }
839
840         cur.recordUndo();
841
842         // Always break behind a space
843         // It is better to erase the space (Dekel)
844         if (cur.pos() != cur.lastpos() && cpar.isLineSeparator(cur.pos()))
845                 cpar.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
846
847         // What should the layout for the new paragraph be?
848         bool keep_layout = layout.isEnvironment()
849                 || (layout.isParagraph() && layout.parbreak_is_newline);
850         if (inverse_logic)
851                 keep_layout = !keep_layout;
852
853         // We need to remember this before we break the paragraph, because
854         // that invalidates the layout variable
855         bool sensitive = layout.labeltype == LABEL_SENSITIVE;
856
857         // we need to set this before we insert the paragraph.
858         bool const isempty = cpar.allowEmpty() && cpar.empty();
859
860         lyx::breakParagraph(*this, cpit, cur.pos(), keep_layout);
861
862         // After this, neither paragraph contains any rows!
863
864         cpit = cur.pit();
865         pit_type next_par = cpit + 1;
866
867         // well this is the caption hack since one caption is really enough
868         if (sensitive) {
869                 if (cur.pos() == 0)
870                         // set to standard-layout
871                 //FIXME Check if this should be plainLayout() in some cases
872                         pars_[cpit].applyLayout(tclass.defaultLayout());
873                 else
874                         // set to standard-layout
875                         //FIXME Check if this should be plainLayout() in some cases
876                         pars_[next_par].applyLayout(tclass.defaultLayout());
877         }
878
879         while (!pars_[next_par].empty() && pars_[next_par].isNewline(0)) {
880                 if (!pars_[next_par].eraseChar(0, cur.buffer()->params().track_changes))
881                         break; // the character couldn't be deleted physically due to change tracking
882         }
883
884         // A singlePar update is not enough in this case.
885         cur.screenUpdateFlags(Update::Force);
886         cur.forceBufferUpdate();
887
888         // This check is necessary. Otherwise the new empty paragraph will
889         // be deleted automatically. And it is more friendly for the user!
890         if (cur.pos() != 0 || isempty)
891                 setCursor(cur, cur.pit() + 1, 0);
892         else
893                 setCursor(cur, cur.pit(), 0);
894 }
895
896
897 // needed to insert the selection
898 void Text::insertStringAsLines(Cursor & cur, docstring const & str,
899                 Font const & font)
900 {
901         BufferParams const & bparams = owner_->buffer().params();
902         pit_type pit = cur.pit();
903         pos_type pos = cur.pos();
904
905         // The special chars we handle
906         static map<wchar_t, InsetSpecialChar::Kind> specialchars = {
907                 { 0x200c, InsetSpecialChar::LIGATURE_BREAK },
908                 { 0x200b, InsetSpecialChar::ALLOWBREAK },
909                 { 0x2026, InsetSpecialChar::LDOTS },
910                 { 0x2011, InsetSpecialChar::NOBREAKDASH }
911         };
912
913         // insert the string, don't insert doublespace
914         bool space_inserted = true;
915         for (auto const & ch : str) {
916                 Paragraph & par = pars_[pit];
917                 if (ch == '\n') {
918                         if (inset().allowMultiPar() && (!par.empty() || par.allowEmpty())) {
919                                 lyx::breakParagraph(*this, pit, pos,
920                                         par.layout().isEnvironment());
921                                 ++pit;
922                                 pos = 0;
923                                 space_inserted = true;
924                         } else {
925                                 continue;
926                         }
927                 // do not insert consecutive spaces if !free_spacing
928                 } else if ((ch == ' ' || ch == '\t') &&
929                            space_inserted && !par.isFreeSpacing()) {
930                         continue;
931                 } else if (ch == '\t') {
932                         if (!par.isFreeSpacing()) {
933                                 // tabs are like spaces here
934                                 par.insertChar(pos, ' ', font, bparams.track_changes);
935                                 ++pos;
936                                 space_inserted = true;
937                         } else {
938                                 par.insertChar(pos, ch, font, bparams.track_changes);
939                                 ++pos;
940                                 space_inserted = true;
941                         }
942                 } else if (specialchars.find(ch) != specialchars.end()
943                            && (par.insertInset(pos, new InsetSpecialChar(specialchars.find(ch)->second),
944                                                font, bparams.track_changes
945                                                ? Change(Change::INSERTED)
946                                                : Change(Change::UNCHANGED)))) {
947                         ++pos;
948                         space_inserted = false;
949                 } else if (!isPrintable(ch)) {
950                         // Ignore (other) unprintables
951                         continue;
952                 } else {
953                         // just insert the character
954                         par.insertChar(pos, ch, font, bparams.track_changes);
955                         ++pos;
956                         space_inserted = (ch == ' ');
957                 }
958         }
959         setCursor(cur, pit, pos);
960 }
961
962
963 // turn double CR to single CR, others are converted into one
964 // blank. Then insertStringAsLines is called
965 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str,
966                 Font const & font)
967 {
968         docstring linestr = str;
969         bool newline_inserted = false;
970
971         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
972                 if (linestr[i] == '\n') {
973                         if (newline_inserted) {
974                                 // we know that \r will be ignored by
975                                 // insertStringAsLines. Of course, it is a dirty
976                                 // trick, but it works...
977                                 linestr[i - 1] = '\r';
978                                 linestr[i] = '\n';
979                         } else {
980                                 linestr[i] = ' ';
981                                 newline_inserted = true;
982                         }
983                 } else if (isPrintable(linestr[i])) {
984                         newline_inserted = false;
985                 }
986         }
987         insertStringAsLines(cur, linestr, font);
988 }
989
990
991 namespace {
992
993 bool canInsertChar(Cursor const & cur, char_type c)
994 {
995         Paragraph const & par = cur.paragraph();
996         // If not in free spacing mode, check if there will be two blanks together or a blank at
997         // the beginning of a paragraph.
998         if (!par.isFreeSpacing() && isLineSeparatorChar(c)) {
999                 if (cur.pos() == 0) {
1000                         cur.message(_(
1001                                         "You cannot insert a space at the "
1002                                         "beginning of a paragraph. Please read the Tutorial."));
1003                         return false;
1004                 }
1005                 // If something is wrong, ignore this character.
1006                 LASSERT(cur.pos() > 0, return false);
1007                 if ((par.isLineSeparator(cur.pos() - 1) || par.isNewline(cur.pos() - 1))
1008                                 && !par.isDeleted(cur.pos() - 1)) {
1009                         cur.message(_(
1010                                         "You cannot type two spaces this way. "
1011                                         "Please read the Tutorial."));
1012                         return false;
1013                 }
1014         }
1015
1016         // Prevent to insert uncodable characters in verbatim and ERT.
1017         // The encoding is inherited from the context here.
1018         if (par.isPassThru() && cur.getEncoding()) {
1019                 Encoding const * e = cur.getEncoding();
1020                 if (!e->encodable(c)) {
1021                         cur.message(_("Character is uncodable in this verbatim context."));
1022                         return false;
1023                 }
1024         }
1025         return true;
1026 }
1027
1028 } // namespace
1029
1030
1031 // insert a character, moves all the following breaks in the
1032 // same Paragraph one to the right and make a rebreak
1033 void Text::insertChar(Cursor & cur, char_type c)
1034 {
1035         LBUFERR(this == cur.text());
1036
1037         if (!canInsertChar(cur,c))
1038                 return;
1039
1040         cur.recordUndo(INSERT_UNDO);
1041
1042         TextMetrics const & tm = cur.bv().textMetrics(this);
1043         Buffer const & buffer = *cur.buffer();
1044         Paragraph & par = cur.paragraph();
1045         // try to remove this
1046         pit_type const pit = cur.pit();
1047
1048         if (lyxrc.auto_number) {
1049                 static docstring const number_operators = from_ascii("+-/*");
1050                 static docstring const number_unary_operators = from_ascii("+-");
1051
1052                 // Common Number Separators: comma, dot etc.
1053                 // European Number Terminators: percent, permille, degree, euro etc.
1054                 if (cur.current_font.fontInfo().number() == FONT_ON) {
1055                         if (!isDigitASCII(c) && !contains(number_operators, c) &&
1056                             !(isCommonNumberSeparator(c) &&
1057                               cur.pos() != 0 &&
1058                               cur.pos() != cur.lastpos() &&
1059                               tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1060                               tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON) &&
1061                             !(isEuropeanNumberTerminator(c) &&
1062                               cur.pos() != 0 &&
1063                               tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1064                               tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON)
1065                            )
1066                                 number(cur); // Set current_font.number to OFF
1067                 } else if (isDigitASCII(c) &&
1068                            cur.real_current_font.isVisibleRightToLeft()) {
1069                         number(cur); // Set current_font.number to ON
1070
1071                         if (cur.pos() != 0) {
1072                                 char_type const ch = par.getChar(cur.pos() - 1);
1073                                 if (contains(number_unary_operators, ch) &&
1074                                     (cur.pos() == 1
1075                                      || par.isSeparator(cur.pos() - 2)
1076                                      || par.isEnvSeparator(cur.pos() - 2)
1077                                      || par.isNewline(cur.pos() - 2))
1078                                   ) {
1079                                         setCharFont(pit, cur.pos() - 1, cur.current_font,
1080                                                 tm.font_);
1081                                 } else if (isCommonNumberSeparator(ch)
1082                                      && cur.pos() >= 2
1083                                      && tm.displayFont(pit, cur.pos() - 2).fontInfo().number() == FONT_ON) {
1084                                         setCharFont(pit, cur.pos() - 1, cur.current_font,
1085                                                 tm.font_);
1086                                 }
1087                         }
1088                 }
1089         }
1090
1091         // In Bidi text, we want spaces to be treated in a special way: spaces
1092         // which are between words in different languages should get the
1093         // paragraph's language; otherwise, spaces should keep the language
1094         // they were originally typed in. This is only in effect while typing;
1095         // after the text is already typed in, the user can always go back and
1096         // explicitly set the language of a space as desired. But 99.9% of the
1097         // time, what we're doing here is what the user actually meant.
1098         //
1099         // The following cases are the ones in which the language of the space
1100         // should be changed to match that of the containing paragraph. In the
1101         // depictions, lowercase is LTR, uppercase is RTL, underscore (_)
1102         // represents a space, pipe (|) represents the cursor position (so the
1103         // character before it is the one just typed in). The different cases
1104         // are depicted logically (not visually), from left to right:
1105         //
1106         // 1. A_a|
1107         // 2. a_A|
1108         //
1109         // Theoretically, there are other situations that we should, perhaps, deal
1110         // with (e.g.: a|_A, A|_a). In practice, though, there really isn't any
1111         // point (to understand why, just try to create this situation...).
1112
1113         if ((cur.pos() >= 2) && (par.isLineSeparator(cur.pos() - 1))) {
1114                 // get font in front and behind the space in question. But do NOT
1115                 // use getFont(cur.pos()) because the character c is not inserted yet
1116                 Font const pre_space_font  = tm.displayFont(cur.pit(), cur.pos() - 2);
1117                 Font const & post_space_font = cur.real_current_font;
1118                 bool pre_space_rtl  = pre_space_font.isVisibleRightToLeft();
1119                 bool post_space_rtl = post_space_font.isVisibleRightToLeft();
1120
1121                 if (pre_space_rtl != post_space_rtl) {
1122                         // Set the space's language to match the language of the
1123                         // adjacent character whose direction is the paragraph's
1124                         // direction; don't touch other properties of the font
1125                         Language const * lang =
1126                                 (pre_space_rtl == par.isRTL(buffer.params())) ?
1127                                 pre_space_font.language() : post_space_font.language();
1128
1129                         Font space_font = tm.displayFont(cur.pit(), cur.pos() - 1);
1130                         space_font.setLanguage(lang);
1131                         par.setFont(cur.pos() - 1, space_font);
1132                 }
1133         }
1134
1135         pos_type pos = cur.pos();
1136         if (!cur.paragraph().isPassThru() && owner_->lyxCode() != IPA_CODE &&
1137             cur.real_current_font.fontInfo().family() != TYPEWRITER_FAMILY &&
1138             c == '-' && pos > 0) {
1139                 if (par.getChar(pos - 1) == '-') {
1140                         // convert "--" to endash
1141                         par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1142                         c = 0x2013;
1143                         pos--;
1144                 } else if (par.getChar(pos - 1) == 0x2013) {
1145                         // convert "---" to emdash
1146                         par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1147                         c = 0x2014;
1148                         pos--;
1149                 }
1150         }
1151
1152         par.insertChar(pos, c, cur.current_font,
1153                 cur.buffer()->params().track_changes);
1154         cur.checkBufferStructure();
1155
1156 //              cur.screenUpdateFlags(Update::Force);
1157         bool boundary = cur.boundary()
1158                 || tm.isRTLBoundary(cur.pit(), pos + 1);
1159         setCursor(cur, cur.pit(), pos + 1, false, boundary);
1160         charInserted(cur);
1161 }
1162
1163
1164 void Text::charInserted(Cursor & cur)
1165 {
1166         Paragraph & par = cur.paragraph();
1167
1168         // register word if a non-letter was entered
1169         if (cur.pos() > 1
1170             && !par.isWordSeparator(cur.pos() - 2)
1171             && par.isWordSeparator(cur.pos() - 1)) {
1172                 // get the word in front of cursor
1173                 LBUFERR(this == cur.text());
1174                 par.updateWords();
1175         }
1176 }
1177
1178
1179 // the cursor set functions have a special mechanism. When they
1180 // realize, that you left an empty paragraph, they will delete it.
1181
1182 bool Text::cursorForwardOneWord(Cursor & cur)
1183 {
1184         LBUFERR(this == cur.text());
1185
1186         if (lyxrc.mac_like_cursor_movement) {
1187                 DocIterator dit(cur);
1188                 DocIterator prv(cur);
1189                 bool inword = false;
1190                 bool intext = dit.inTexted();
1191                 while (!dit.atEnd()) {
1192                         if (dit.inTexted()) { // no paragraphs in mathed
1193                                 Paragraph const & par = dit.paragraph();
1194                                 pos_type const pos = dit.pos();
1195
1196                                 if (!par.isDeleted(pos)) {
1197                                         bool wordsep = par.isWordSeparator(pos);
1198                                         if (inword && wordsep)
1199                                                 break; // stop at word end
1200                                         else if (!inword && !wordsep)
1201                                                 inword = true;
1202                                 }
1203                                 intext = true;
1204                         } else if (intext) {
1205                                 // move to end of math
1206                                 while (!dit.inTexted() && !dit.atEnd()) dit.forwardPos();
1207                                 break;
1208                         }
1209                         prv = dit;
1210                         dit.forwardPosIgnoreCollapsed();
1211                 }
1212                 if (dit.atEnd()) dit = prv;
1213                 if (dit == cur) return false; // we didn't move
1214                 Cursor orig(cur);
1215                 cur.setCursor(dit);
1216                 // see comment above
1217                 cur.bv().checkDepm(cur, orig);
1218                 return true;
1219         } else {
1220                 pos_type const lastpos = cur.lastpos();
1221                 pit_type pit = cur.pit();
1222                 pos_type pos = cur.pos();
1223                 Paragraph const & par = cur.paragraph();
1224
1225                 // Paragraph boundary is a word boundary
1226                 if (pos == lastpos || (pos + 1 == lastpos && par.isEnvSeparator(pos))) {
1227                         if (pit != cur.lastpit())
1228                                 return setCursor(cur, pit + 1, 0);
1229                         else
1230                                 return false;
1231                 }
1232
1233                 LASSERT(pos < lastpos, return false); // see above
1234                 if (!par.isWordSeparator(pos))
1235                         while (pos != lastpos && !par.isWordSeparator(pos))
1236                                 ++pos;
1237                 else if (par.isChar(pos))
1238                         while (pos != lastpos && par.isChar(pos))
1239                                 ++pos;
1240                 else if (!par.isSpace(pos)) // non-char inset
1241                         ++pos;
1242
1243                 // Skip over white space
1244                 while (pos != lastpos && par.isSpace(pos))
1245                              ++pos;
1246
1247                 // Don't skip a separator inset at the end of a paragraph
1248                 if (pos == lastpos && pos && par.isEnvSeparator(pos - 1))
1249                         --pos;
1250
1251                 return setCursor(cur, pit, pos);
1252         }
1253 }
1254
1255
1256 bool Text::cursorBackwardOneWord(Cursor & cur)
1257 {
1258         LBUFERR(this == cur.text());
1259
1260         if (lyxrc.mac_like_cursor_movement) {
1261                 DocIterator dit(cur);
1262                 bool inword = false;
1263                 bool intext = dit.inTexted();
1264                 while (!dit.atBegin()) {
1265                         DocIterator prv(dit);
1266                         dit.backwardPosIgnoreCollapsed();
1267                         if (dit.inTexted()) { // no paragraphs in mathed
1268                                 Paragraph const & par = dit.paragraph();
1269                                 pos_type pos = dit.pos();
1270
1271                                 if (!par.isDeleted(pos)) {
1272                                         bool wordsep = par.isWordSeparator(pos);
1273                                         if (inword && wordsep) {
1274                                                 dit = prv;
1275                                                 break; // stop at word begin
1276                                         } else if (!inword && !wordsep)
1277                                                 inword = true;
1278                                 }
1279                                 intext = true;
1280                         } else if (intext) {
1281                                 // move to begin of math
1282                                 while (!dit.inTexted() && !dit.atBegin()) dit.backwardPos();
1283                                 break;
1284                         }
1285                 }
1286                 if (dit == cur) return false; // we didn't move
1287                 Cursor orig(cur);
1288                 cur.setCursor(dit);
1289                 // see comment above cursorForwardOneWord
1290                 cur.bv().checkDepm(cur, orig);
1291                 return true;
1292         } else {
1293                 Paragraph const & par = cur.paragraph();
1294                 pit_type const pit = cur.pit();
1295                 pos_type pos = cur.pos();
1296
1297                 // Paragraph boundary is a word boundary
1298                 if (pos == 0 && pit != 0) {
1299                         Paragraph & prevpar = getPar(pit - 1);
1300                         pos = prevpar.size();
1301                         // Don't stop after an environment separator
1302                         if (pos && prevpar.isEnvSeparator(pos - 1))
1303                                 --pos;
1304                         return setCursor(cur, pit - 1, pos);
1305                 }
1306                 // Skip over white space
1307                 while (pos != 0 && par.isSpace(pos - 1))
1308                         --pos;
1309
1310                 if (pos != 0 && !par.isWordSeparator(pos - 1))
1311                         while (pos != 0 && !par.isWordSeparator(pos - 1))
1312                                 --pos;
1313                 else if (pos != 0 && par.isChar(pos - 1))
1314                         while (pos != 0 && par.isChar(pos - 1))
1315                                 --pos;
1316                 else if (pos != 0 && !par.isSpace(pos - 1)) // non-char inset
1317                         --pos;
1318
1319                 return setCursor(cur, pit, pos);
1320         }
1321 }
1322
1323
1324 bool Text::cursorVisLeftOneWord(Cursor & cur)
1325 {
1326         LBUFERR(this == cur.text());
1327
1328         pos_type left_pos, right_pos;
1329
1330         Cursor temp_cur = cur;
1331
1332         // always try to move at least once...
1333         while (temp_cur.posVisLeft(true /* skip_inset */)) {
1334
1335                 // collect some information about current cursor position
1336                 temp_cur.getSurroundingPos(left_pos, right_pos);
1337                 bool left_is_letter =
1338                         (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1339                 bool right_is_letter =
1340                         (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1341
1342                 // if we're not at a letter/non-letter boundary, continue moving
1343                 if (left_is_letter == right_is_letter)
1344                         continue;
1345
1346                 // we should stop when we have an LTR word on our right or an RTL word
1347                 // on our left
1348                 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1349                                 temp_cur.buffer()->params(), left_pos).isRightToLeft())
1350                         || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1351                                 temp_cur.buffer()->params(), right_pos).isRightToLeft()))
1352                         break;
1353         }
1354
1355         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1356                                          true, temp_cur.boundary());
1357 }
1358
1359
1360 bool Text::cursorVisRightOneWord(Cursor & cur)
1361 {
1362         LBUFERR(this == cur.text());
1363
1364         pos_type left_pos, right_pos;
1365
1366         Cursor temp_cur = cur;
1367
1368         // always try to move at least once...
1369         while (temp_cur.posVisRight(true /* skip_inset */)) {
1370
1371                 // collect some information about current cursor position
1372                 temp_cur.getSurroundingPos(left_pos, right_pos);
1373                 bool left_is_letter =
1374                         (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1375                 bool right_is_letter =
1376                         (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1377
1378                 // if we're not at a letter/non-letter boundary, continue moving
1379                 if (left_is_letter == right_is_letter)
1380                         continue;
1381
1382                 // we should stop when we have an LTR word on our right or an RTL word
1383                 // on our left
1384                 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1385                                 temp_cur.buffer()->params(),
1386                                 left_pos).isRightToLeft())
1387                         || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1388                                 temp_cur.buffer()->params(),
1389                                 right_pos).isRightToLeft()))
1390                         break;
1391         }
1392
1393         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1394                                          true, temp_cur.boundary());
1395 }
1396
1397
1398 void Text::selectWord(Cursor & cur, word_location loc)
1399 {
1400         LBUFERR(this == cur.text());
1401         CursorSlice from = cur.top();
1402         CursorSlice to;
1403         getWord(from, to, loc);
1404         if (cur.top() != from)
1405                 setCursor(cur, from.pit(), from.pos());
1406         if (to == from)
1407                 return;
1408         if (!cur.selection())
1409                 cur.resetAnchor();
1410         setCursor(cur, to.pit(), to.pos());
1411         cur.setSelection();
1412         cur.setWordSelection(true);
1413 }
1414
1415
1416 void Text::expandWordSel(Cursor & cur)
1417 {
1418         // get selection of word around cur
1419         Cursor c = cur;
1420         c.selection(false);
1421         c.text()->selectWord(c, WHOLE_WORD);
1422         // use the correct word boundary, depending on selection direction
1423         if (cur.top() > cur.normalAnchor())
1424                 cur.pos() = c.selEnd().pos();
1425         else
1426                 cur.pos() = c.selBegin().pos();
1427 }
1428
1429
1430 void Text::selectAll(Cursor & cur)
1431 {
1432         LBUFERR(this == cur.text());
1433         if (cur.lastpos() == 0 && cur.lastpit() == 0)
1434                 return;
1435         // If the cursor is at the beginning, make sure the cursor ends there
1436         if (cur.pit() == 0 && cur.pos() == 0) {
1437                 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1438                 cur.resetAnchor();
1439                 setCursor(cur, 0, 0);
1440         } else {
1441                 setCursor(cur, 0, 0);
1442                 cur.resetAnchor();
1443                 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1444         }
1445         cur.setSelection();
1446 }
1447
1448
1449 // Select the word currently under the cursor when no
1450 // selection is currently set
1451 bool Text::selectWordWhenUnderCursor(Cursor & cur, word_location loc)
1452 {
1453         LBUFERR(this == cur.text());
1454         if (cur.selection())
1455                 return false;
1456         selectWord(cur, loc);
1457         return cur.selection();
1458 }
1459
1460
1461 void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
1462 {
1463         LBUFERR(this == cur.text());
1464
1465         if (!cur.selection()) {
1466                 if (!selectChange(cur))
1467                         return;
1468         }
1469
1470         cur.recordUndoSelection();
1471
1472         pit_type begPit = cur.selectionBegin().pit();
1473         pit_type endPit = cur.selectionEnd().pit();
1474
1475         pos_type begPos = cur.selectionBegin().pos();
1476         pos_type endPos = cur.selectionEnd().pos();
1477
1478         // keep selection info, because endPos becomes invalid after the first loop
1479         bool const endsBeforeEndOfPar = (endPos < pars_[endPit].size());
1480
1481         // first, accept/reject changes within each individual paragraph (do not consider end-of-par)
1482         for (pit_type pit = begPit; pit <= endPit; ++pit) {
1483                 pos_type parSize = pars_[pit].size();
1484
1485                 // ignore empty paragraphs; otherwise, an assertion will fail for
1486                 // acceptChanges(bparams, 0, 0) or rejectChanges(bparams, 0, 0)
1487                 if (parSize == 0)
1488                         continue;
1489
1490                 // do not consider first paragraph if the cursor starts at pos size()
1491                 if (pit == begPit && begPos == parSize)
1492                         continue;
1493
1494                 // do not consider last paragraph if the cursor ends at pos 0
1495                 if (pit == endPit && endPos == 0)
1496                         break; // last iteration anyway
1497
1498                 pos_type const left  = (pit == begPit ? begPos : 0);
1499                 pos_type const right = (pit == endPit ? endPos : parSize);
1500
1501                 if (left == right)
1502                         // there is no change here
1503                         continue;
1504
1505                 if (op == ACCEPT) {
1506                         pars_[pit].acceptChanges(left, right);
1507                 } else {
1508                         pars_[pit].rejectChanges(left, right);
1509                 }
1510         }
1511
1512         // next, accept/reject imaginary end-of-par characters
1513
1514         for (pit_type pit = begPit; pit <= endPit; ++pit) {
1515                 pos_type pos = pars_[pit].size();
1516
1517                 // skip if the selection ends before the end-of-par
1518                 if (pit == endPit && endsBeforeEndOfPar)
1519                         break; // last iteration anyway
1520
1521                 // skip if this is not the last paragraph of the document
1522                 // note: the user should be able to accept/reject the par break of the last par!
1523                 if (pit == endPit && pit + 1 != int(pars_.size()))
1524                         break; // last iteration anway
1525
1526                 if (op == ACCEPT) {
1527                         if (pars_[pit].isInserted(pos)) {
1528                                 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1529                         } else if (pars_[pit].isDeleted(pos)) {
1530                                 if (pit + 1 == int(pars_.size())) {
1531                                         // we cannot remove a par break at the end of the last paragraph;
1532                                         // instead, we mark it unchanged
1533                                         pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1534                                 } else {
1535                                         mergeParagraph(cur.buffer()->params(), pars_, pit);
1536                                         --endPit;
1537                                         --pit;
1538                                 }
1539                         }
1540                 } else {
1541                         if (pars_[pit].isDeleted(pos)) {
1542                                 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1543                         } else if (pars_[pit].isInserted(pos)) {
1544                                 if (pit + 1 == int(pars_.size())) {
1545                                         // we mark the par break at the end of the last paragraph unchanged
1546                                         pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1547                                 } else {
1548                                         mergeParagraph(cur.buffer()->params(), pars_, pit);
1549                                         --endPit;
1550                                         --pit;
1551                                 }
1552                         }
1553                 }
1554         }
1555
1556         // finally, invoke the DEPM
1557         deleteEmptyParagraphMechanism(begPit, endPit, begPos, endPos,
1558                                       cur.buffer()->params().track_changes);
1559
1560         cur.finishUndo();
1561         cur.clearSelection();
1562         setCursorIntern(cur, begPit, begPos);
1563         cur.screenUpdateFlags(Update::Force);
1564         cur.forceBufferUpdate();
1565 }
1566
1567
1568 void Text::acceptChanges()
1569 {
1570         BufferParams const & bparams = owner_->buffer().params();
1571         lyx::acceptChanges(pars_, bparams);
1572         deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.track_changes);
1573 }
1574
1575
1576 void Text::rejectChanges()
1577 {
1578         BufferParams const & bparams = owner_->buffer().params();
1579         pit_type pars_size = static_cast<pit_type>(pars_.size());
1580
1581         // first, reject changes within each individual paragraph
1582         // (do not consider end-of-par)
1583         for (pit_type pit = 0; pit < pars_size; ++pit) {
1584                 if (!pars_[pit].empty())   // prevent assertion failure
1585                         pars_[pit].rejectChanges(0, pars_[pit].size());
1586         }
1587
1588         // next, reject imaginary end-of-par characters
1589         for (pit_type pit = 0; pit < pars_size; ++pit) {
1590                 pos_type pos = pars_[pit].size();
1591
1592                 if (pars_[pit].isDeleted(pos)) {
1593                         pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1594                 } else if (pars_[pit].isInserted(pos)) {
1595                         if (pit == pars_size - 1) {
1596                                 // we mark the par break at the end of the last
1597                                 // paragraph unchanged
1598                                 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1599                         } else {
1600                                 mergeParagraph(bparams, pars_, pit);
1601                                 --pit;
1602                                 --pars_size;
1603                         }
1604                 }
1605         }
1606
1607         // finally, invoke the DEPM
1608         deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.track_changes);
1609 }
1610
1611
1612 void Text::deleteWordForward(Cursor & cur, bool const force)
1613 {
1614         LBUFERR(this == cur.text());
1615         if (cur.lastpos() == 0)
1616                 cursorForward(cur);
1617         else {
1618                 cur.resetAnchor();
1619                 cur.selection(true);
1620                 cursorForwardOneWord(cur);
1621                 cur.setSelection();
1622                 if (force || !cur.confirmDeletion()) {
1623                         cutSelection(cur, false);
1624                         cur.checkBufferStructure();
1625                 }
1626         }
1627 }
1628
1629
1630 void Text::deleteWordBackward(Cursor & cur, bool const force)
1631 {
1632         LBUFERR(this == cur.text());
1633         if (cur.lastpos() == 0)
1634                 cursorBackward(cur);
1635         else {
1636                 cur.resetAnchor();
1637                 cur.selection(true);
1638                 cursorBackwardOneWord(cur);
1639                 cur.setSelection();
1640                 if (force || !cur.confirmDeletion()) {
1641                         cutSelection(cur, false);
1642                         cur.checkBufferStructure();
1643                 }
1644         }
1645 }
1646
1647
1648 // Kill to end of line.
1649 void Text::changeCase(Cursor & cur, TextCase action, bool partial)
1650 {
1651         LBUFERR(this == cur.text());
1652         CursorSlice from;
1653         CursorSlice to;
1654
1655         bool const gotsel = cur.selection();
1656         if (gotsel) {
1657                 from = cur.selBegin();
1658                 to = cur.selEnd();
1659         } else {
1660                 from = cur.top();
1661                 getWord(from, to, partial ? PARTIAL_WORD : WHOLE_WORD);
1662                 cursorForwardOneWord(cur);
1663         }
1664
1665         cur.recordUndoSelection();
1666
1667         pit_type begPit = from.pit();
1668         pit_type endPit = to.pit();
1669
1670         pos_type begPos = from.pos();
1671         pos_type endPos = to.pos();
1672
1673         pos_type right = 0; // needed after the for loop
1674
1675         for (pit_type pit = begPit; pit <= endPit; ++pit) {
1676                 Paragraph & par = pars_[pit];
1677                 pos_type const pos = (pit == begPit ? begPos : 0);
1678                 right = (pit == endPit ? endPos : par.size());
1679                 par.changeCase(cur.buffer()->params(), pos, right, action);
1680         }
1681
1682         // the selection may have changed due to logically-only deleted chars
1683         if (gotsel) {
1684                 setCursor(cur, begPit, begPos);
1685                 cur.resetAnchor();
1686                 setCursor(cur, endPit, right);
1687                 cur.setSelection();
1688         } else
1689                 setCursor(cur, endPit, right);
1690
1691         cur.checkBufferStructure();
1692 }
1693
1694
1695 bool Text::handleBibitems(Cursor & cur)
1696 {
1697         if (cur.paragraph().layout().labeltype != LABEL_BIBLIO)
1698                 return false;
1699
1700         if (cur.pos() != 0)
1701                 return false;
1702
1703         BufferParams const & bufparams = cur.buffer()->params();
1704         Paragraph const & par = cur.paragraph();
1705         Cursor prevcur = cur;
1706         if (cur.pit() > 0) {
1707                 --prevcur.pit();
1708                 prevcur.pos() = prevcur.lastpos();
1709         }
1710         Paragraph const & prevpar = prevcur.paragraph();
1711
1712         // if a bibitem is deleted, merge with previous paragraph
1713         // if this is a bibliography item as well
1714         if (cur.pit() > 0 && par.layout() == prevpar.layout()) {
1715                 cur.recordUndo(prevcur.pit());
1716                 mergeParagraph(bufparams, cur.text()->paragraphs(),
1717                                                         prevcur.pit());
1718                 cur.forceBufferUpdate();
1719                 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1720                 cur.screenUpdateFlags(Update::Force);
1721                 return true;
1722         }
1723
1724         // otherwise reset to default
1725         cur.paragraph().setPlainOrDefaultLayout(bufparams.documentClass());
1726         return true;
1727 }
1728
1729
1730 bool Text::erase(Cursor & cur)
1731 {
1732         LASSERT(this == cur.text(), return false);
1733         bool needsUpdate = false;
1734         Paragraph & par = cur.paragraph();
1735
1736         if (cur.pos() != cur.lastpos()) {
1737                 // this is the code for a normal delete, not pasting
1738                 // any paragraphs
1739                 cur.recordUndo(DELETE_UNDO);
1740                 bool const was_inset = cur.paragraph().isInset(cur.pos());
1741                 if(!par.eraseChar(cur.pos(), cur.buffer()->params().track_changes))
1742                         // the character has been logically deleted only => skip it
1743                         cur.top().forwardPos();
1744
1745                 if (was_inset)
1746                         cur.forceBufferUpdate();
1747                 else
1748                         cur.checkBufferStructure();
1749                 needsUpdate = true;
1750         } else {
1751                 if (cur.pit() == cur.lastpit())
1752                         return dissolveInset(cur);
1753
1754                 if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1755                         cur.recordUndo(DELETE_UNDO);
1756                         par.setChange(cur.pos(), Change(Change::DELETED));
1757                         cur.forwardPos();
1758                         needsUpdate = true;
1759                 } else {
1760                         setCursorIntern(cur, cur.pit() + 1, 0);
1761                         needsUpdate = backspacePos0(cur);
1762                 }
1763         }
1764
1765         needsUpdate |= handleBibitems(cur);
1766
1767         if (needsUpdate) {
1768                 // Make sure the cursor is correct. Is this really needed?
1769                 // No, not really... at least not here!
1770                 cur.top().setPitPos(cur.pit(), cur.pos());
1771                 cur.checkBufferStructure();
1772         }
1773
1774         return needsUpdate;
1775 }
1776
1777
1778 bool Text::backspacePos0(Cursor & cur)
1779 {
1780         LBUFERR(this == cur.text());
1781         if (cur.pit() == 0)
1782                 return false;
1783
1784         BufferParams const & bufparams = cur.buffer()->params();
1785         ParagraphList & plist = cur.text()->paragraphs();
1786         Paragraph const & par = cur.paragraph();
1787         Cursor prevcur = cur;
1788         --prevcur.pit();
1789         prevcur.pos() = prevcur.lastpos();
1790         Paragraph const & prevpar = prevcur.paragraph();
1791
1792         // is it an empty paragraph?
1793         if (cur.lastpos() == 0
1794             || (cur.lastpos() == 1 && par.isSeparator(0))) {
1795                 cur.recordUndo(prevcur.pit());
1796                 plist.erase(plist.iterator_at(cur.pit()));
1797         }
1798         // is previous par empty?
1799         else if (prevcur.lastpos() == 0
1800                  || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) {
1801                 cur.recordUndo(prevcur.pit());
1802                 plist.erase(plist.iterator_at(prevcur.pit()));
1803         }
1804         // FIXME: Do we really not want to allow this???
1805         // Pasting is not allowed, if the paragraphs have different
1806         // layouts. I think it is a real bug of all other
1807         // word processors to allow it. It confuses the user.
1808         // Correction: Pasting is always allowed with standard-layout
1809         // or the empty layout.
1810         else {
1811                 cur.recordUndo(prevcur.pit());
1812                 mergeParagraph(bufparams, plist, prevcur.pit());
1813         }
1814
1815         cur.forceBufferUpdate();
1816         setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1817
1818         return true;
1819 }
1820
1821
1822 bool Text::backspace(Cursor & cur)
1823 {
1824         LBUFERR(this == cur.text());
1825         bool needsUpdate = false;
1826         if (cur.pos() == 0) {
1827                 if (cur.pit() == 0)
1828                         return dissolveInset(cur);
1829
1830                 Cursor prev_cur = cur;
1831                 --prev_cur.pit();
1832
1833                 if (!cur.paragraph().empty()
1834                     && !prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1835                         cur.recordUndo(prev_cur.pit(), prev_cur.pit());
1836                         prev_cur.paragraph().setChange(prev_cur.lastpos(), Change(Change::DELETED));
1837                         setCursorIntern(cur, prev_cur.pit(), prev_cur.lastpos());
1838                         return true;
1839                 }
1840                 // The cursor is at the beginning of a paragraph, so
1841                 // the backspace will collapse two paragraphs into one.
1842                 needsUpdate = backspacePos0(cur);
1843
1844         } else {
1845                 // this is the code for a normal backspace, not pasting
1846                 // any paragraphs
1847                 cur.recordUndo(DELETE_UNDO);
1848                 // We used to do cursorBackwardIntern() here, but it is
1849                 // not a good idea since it triggers the auto-delete
1850                 // mechanism. So we do a cursorBackwardIntern()-lite,
1851                 // without the dreaded mechanism. (JMarc)
1852                 setCursorIntern(cur, cur.pit(), cur.pos() - 1,
1853                                 false, cur.boundary());
1854                 bool const was_inset = cur.paragraph().isInset(cur.pos());
1855                 cur.paragraph().eraseChar(cur.pos(), cur.buffer()->params().track_changes);
1856                 if (was_inset)
1857                         cur.forceBufferUpdate();
1858                 else
1859                         cur.checkBufferStructure();
1860         }
1861
1862         if (cur.pos() == cur.lastpos())
1863                 cur.setCurrentFont();
1864
1865         needsUpdate |= handleBibitems(cur);
1866
1867         // A singlePar update is not enough in this case.
1868         // cur.screenUpdateFlags(Update::Force);
1869         cur.top().setPitPos(cur.pit(), cur.pos());
1870
1871         return needsUpdate;
1872 }
1873
1874
1875 bool Text::dissolveInset(Cursor & cur)
1876 {
1877         LASSERT(this == cur.text(), return false);
1878
1879         if (isMainText() || cur.inset().nargs() != 1)
1880                 return false;
1881
1882         cur.recordUndoInset();
1883         cur.setMark(false);
1884         cur.selHandle(false);
1885         // save position inside inset
1886         pos_type spos = cur.pos();
1887         pit_type spit = cur.pit();
1888         bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1889         cur.popBackward();
1890         // update cursor offset
1891         if (spit == 0)
1892                 spos += cur.pos();
1893         spit += cur.pit();
1894         // remember position outside inset to delete inset later
1895         // we do not do it now to avoid memory reuse issues (see #10667).
1896         DocIterator inset_it = cur;
1897         // jump over inset
1898         ++cur.pos();
1899
1900         Buffer & b = *cur.buffer();
1901         // Is there anything in this text?
1902         if (inset_non_empty) {
1903                 // see bug 7319
1904                 // we clear the cache so that we won't get conflicts with labels
1905                 // that get pasted into the buffer. we should update this before
1906                 // its being empty matters. if not (i.e., if we encounter bugs),
1907                 // then this should instead be:
1908                 //        cur.buffer().updateBuffer();
1909                 // but we'll try the cheaper solution here.
1910                 cur.buffer()->clearReferenceCache();
1911
1912                 ParagraphList & plist = paragraphs();
1913                 if (!lyxrc.ct_markup_copied)
1914                         // Do not revive deleted text
1915                         lyx::acceptChanges(plist, b.params());
1916
1917                 // ERT paragraphs have the Language latex_language.
1918                 // This is invalid outside of ERT, so we need to
1919                 // change it to the buffer language.
1920                 for (auto & p : plist)
1921                         p.changeLanguage(b.params(), latex_language, b.language());
1922
1923                 /* If the inset is the only thing in paragraph and the layout
1924                  * is not plain, then the layout of the first paragraph of
1925                  * inset should be remembered.
1926                  * FIXME: this does not work as expected when change tracking
1927                  *   is on However, we do not really know what to do in this
1928                  *   case.
1929                  */
1930                 DocumentClass const & tclass = cur.buffer()->params().documentClass();
1931                 if (inset_it.lastpos() == 1
1932                     && !tclass.isPlainLayout(plist[0].layout())
1933                     && !tclass.isDefaultLayout(plist[0].layout())) {
1934                         // Copy all parameters except depth.
1935                         Paragraph & par = cur.paragraph();
1936                         par.setLayout(plist[0].layout());
1937                         depth_type const dpth = par.getDepth();
1938                         par.params() = plist[0].params();
1939                         par.params().depth(dpth);
1940                 }
1941
1942                 pasteParagraphList(cur, plist, b.params().documentClassPtr(),
1943                                    b.params().authors(),
1944                                    b.errorList("Paste"));
1945         }
1946
1947         // delete the inset now
1948         inset_it.paragraph().eraseChar(inset_it.pos(), b.params().track_changes);
1949
1950         // restore position
1951         cur.pit() = min(cur.lastpit(), spit);
1952         cur.pos() = min(cur.lastpos(), spos);
1953         // Ensure the current language is set correctly (bug 6292)
1954         cur.text()->setCursor(cur, cur.pit(), cur.pos());
1955         cur.clearSelection();
1956         cur.resetAnchor();
1957         cur.forceBufferUpdate();
1958
1959         return true;
1960 }
1961
1962
1963 bool Text::splitInset(Cursor & cur)
1964 {
1965         LASSERT(this == cur.text(), return false);
1966
1967         if (isMainText() || cur.inset().nargs() != 1)
1968                 return false;
1969
1970         cur.recordUndo();
1971         if (cur.selection()) {
1972                 // start from selection begin
1973                 setCursor(cur, cur.selBegin().pit(), cur.selBegin().pos());
1974                 cur.clearSelection();
1975         }
1976         // save split position inside inset
1977         // (we need to copy the whole inset first)
1978         pos_type spos = cur.pos();
1979         pit_type spit = cur.pit();
1980         // some things only need to be done if the inset has content
1981         bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1982
1983         // move right before the inset
1984         cur.popBackward();
1985         cur.resetAnchor();
1986         // remember position outside inset
1987         pos_type ipos = cur.pos();
1988         pit_type ipit = cur.pit();
1989         // select inset ...
1990         ++cur.pos();
1991         cur.setSelection();
1992         // ... and copy
1993         cap::copySelectionToTemp(cur);
1994         cur.clearSelection();
1995         cur.resetAnchor();
1996         // paste copied inset
1997         cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
1998         cur.forceBufferUpdate();
1999
2000         // if the inset has text, cut after split position
2001         // and paste to new inset
2002         if (inset_non_empty) {
2003                 // go back to first inset
2004                 cur.text()->setCursor(cur, ipit, ipos);
2005                 cur.forwardPos();
2006                 setCursor(cur, spit, spos);
2007                 cur.resetAnchor();
2008                 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
2009                 cur.setSelection();
2010                 // Remember whether there was something cut that has to be pasted below
2011                 // (bug #12747)
2012                 bool const hasCut = cur.selection();
2013                 cap::cutSelectionToTemp(cur);
2014                 cur.setMark(false);
2015                 cur.selHandle(false);
2016                 cur.resetAnchor();
2017                 bool atlastpos = false;
2018                 if (cur.pos() == 0 && cur.pit() > 0) {
2019                         // if we are at par start, remove this par
2020                         cur.text()->backspace(cur);
2021                         cur.forceBufferUpdate();
2022                 } else if (cur.pos() == cur.lastpos())
2023                         atlastpos = true;
2024                 // Move out of and jump over inset
2025                 cur.popBackward();
2026                 ++cur.pos();
2027
2028                 // enter new inset
2029                 cur.forwardPos();
2030                 cur.setCursor(cur);
2031                 cur.resetAnchor();
2032                 cur.text()->selectAll(cur);
2033                 cutSelection(cur, false);
2034                 // If there was something cut paste it
2035                 if (hasCut)
2036                         cap::pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
2037                 cur.text()->setCursor(cur, 0, 0);
2038                 if (atlastpos && cur.paragraph().isFreeSpacing() && cur.paragraph().empty()) {
2039                         // We started from par end, remove extra empty par in free spacing insets
2040                         cur.text()->erase(cur);
2041                         cur.forceBufferUpdate();
2042                 }
2043         }
2044
2045         cur.finishUndo();
2046         return true;
2047 }
2048
2049
2050 void Text::getWord(CursorSlice & from, CursorSlice & to,
2051         word_location const loc) const
2052 {
2053         to = from;
2054         pars_[to.pit()].locateWord(from.pos(), to.pos(), loc);
2055 }
2056
2057
2058 void Text::write(ostream & os) const
2059 {
2060         Buffer const & buf = owner_->buffer();
2061         ParagraphList::const_iterator pit = paragraphs().begin();
2062         ParagraphList::const_iterator end = paragraphs().end();
2063         depth_type dth = 0;
2064         for (; pit != end; ++pit)
2065                 pit->write(os, buf.params(), dth);
2066
2067         // Close begin_deeper
2068         for(; dth > 0; --dth)
2069                 os << "\n\\end_deeper";
2070 }
2071
2072
2073 bool Text::read(Lexer & lex,
2074                 ErrorList & errorList, InsetText * insetPtr)
2075 {
2076         Buffer const & buf = owner_->buffer();
2077         depth_type depth = 0;
2078         bool res = true;
2079
2080         while (lex.isOK()) {
2081                 lex.nextToken();
2082                 string const token = lex.getString();
2083
2084                 if (token.empty())
2085                         continue;
2086
2087                 if (token == "\\end_inset")
2088                         break;
2089
2090                 if (token == "\\end_body")
2091                         continue;
2092
2093                 if (token == "\\begin_body")
2094                         continue;
2095
2096                 if (token == "\\end_document") {
2097                         res = false;
2098                         break;
2099                 }
2100
2101                 if (token == "\\begin_layout") {
2102                         lex.pushToken(token);
2103
2104                         Paragraph par;
2105                         par.setInsetOwner(insetPtr);
2106                         par.params().depth(depth);
2107                         par.setFont(0, Font(inherit_font, buf.params().language));
2108                         pars_.push_back(par);
2109                         readParagraph(pars_.back(), lex, errorList);
2110
2111                         // register the words in the global word list
2112                         pars_.back().updateWords();
2113                 } else if (token == "\\begin_deeper") {
2114                         ++depth;
2115                 } else if (token == "\\end_deeper") {
2116                         if (!depth)
2117                                 lex.printError("\\end_deeper: " "depth is already null");
2118                         else
2119                                 --depth;
2120                 } else {
2121                         LYXERR0("Handling unknown body token: `" << token << '\'');
2122                 }
2123         }
2124
2125         // avoid a crash on weird documents (bug 4859)
2126         if (pars_.empty()) {
2127                 Paragraph par;
2128                 par.setInsetOwner(insetPtr);
2129                 par.params().depth(depth);
2130                 par.setFont(0, Font(inherit_font,
2131                                     buf.params().language));
2132                 par.setPlainOrDefaultLayout(buf.params().documentClass());
2133                 pars_.push_back(par);
2134         }
2135
2136         return res;
2137 }
2138
2139
2140 // Returns the current state (font, depth etc.) as a message for status bar.
2141 docstring Text::currentState(CursorData const & cur, bool devel_mode) const
2142 {
2143         LBUFERR(this == cur.text());
2144         Buffer & buf = *cur.buffer();
2145         Paragraph const & par = cur.paragraph();
2146         odocstringstream os;
2147
2148         if (buf.params().track_changes)
2149                 os << _("[Change Tracking] ");
2150
2151         Change change = par.lookupChange(cur.pos());
2152
2153         if (change.changed()) {
2154                 docstring const author =
2155                         buf.params().authors().get(change.author).nameAndEmail();
2156                 docstring const date = formatted_datetime(change.changetime);
2157                 os << bformat(_("Changed by %1$s[[author]] on %2$s[[date]]. "),
2158                               author, date);
2159         }
2160
2161         // I think we should only show changes from the default
2162         // font. (Asger)
2163         // No, from the document font (MV)
2164         Font font = cur.real_current_font;
2165         font.fontInfo().reduce(buf.params().getFont().fontInfo());
2166
2167         os << bformat(_("Font: %1$s"), font.stateText(&buf.params()));
2168
2169         // The paragraph depth
2170         int depth = par.getDepth();
2171         if (depth > 0)
2172                 os << bformat(_(", Depth: %1$d"), depth);
2173
2174         // The paragraph spacing, but only if different from
2175         // buffer spacing.
2176         Spacing const & spacing = par.params().spacing();
2177         if (!spacing.isDefault()) {
2178                 os << _(", Spacing: ");
2179                 switch (spacing.getSpace()) {
2180                 case Spacing::Single:
2181                         os << _("Single");
2182                         break;
2183                 case Spacing::Onehalf:
2184                         os << _("OneHalf");
2185                         break;
2186                 case Spacing::Double:
2187                         os << _("Double");
2188                         break;
2189                 case Spacing::Other:
2190                         os << _("Other (") << from_ascii(spacing.getValueAsString()) << ')';
2191                         break;
2192                 case Spacing::Default:
2193                         // should never happen, do nothing
2194                         break;
2195                 }
2196         }
2197
2198         // Custom text style
2199         InsetLayout const & layout = cur.inset().getLayout();
2200         if (layout.lyxtype() == InsetLyXType::CHARSTYLE)
2201                 os << _(", Style: ") << translateIfPossible(layout.labelstring());
2202
2203         if (devel_mode) {
2204                 os << _(", Inset: ") << &cur.inset();
2205                 if (cur.lastidx() > 0)
2206                         os << _(", Cell: ") << cur.idx();
2207                 os << _(", Paragraph: ") << cur.pit();
2208                 os << _(", Id: ") << par.id();
2209                 os << _(", Position: ") << cur.pos();
2210                 // FIXME: Why is the check for par.size() needed?
2211                 // We are called with cur.pos() == par.size() quite often.
2212                 if (!par.empty() && cur.pos() < par.size()) {
2213                         // Force output of code point, not character
2214                         size_t const c = par.getChar(cur.pos());
2215                         if (c == META_INSET)
2216                                 os << ", Char: INSET";
2217                         else
2218                                 os << _(", Char: 0x") << hex << c;
2219                 }
2220                 os << _(", Boundary: ") << cur.boundary();
2221 //              Row & row = cur.textRow();
2222 //              os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos());
2223         }
2224         return os.str();
2225 }
2226
2227
2228 docstring Text::getPossibleLabel(DocIterator const & cur) const
2229 {
2230         pit_type textpit = cur.pit();
2231         Layout const * layout = &(pars_[textpit].layout());
2232
2233         // Will contain the label prefix.
2234         docstring name;
2235
2236         // For captions, we just take the caption type
2237         Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE);
2238         if (caption_inset) {
2239                 string const & ftype = static_cast<InsetCaption *>(caption_inset)->floattype();
2240                 FloatList const & fl = cur.buffer()->params().documentClass().floats();
2241                 if (fl.typeExist(ftype)) {
2242                         Floating const & flt = fl.getType(ftype);
2243                         name = from_utf8(flt.refPrefix());
2244                 }
2245                 if (name.empty())
2246                         name = from_utf8(ftype.substr(0,3));
2247         } else {
2248                 // For section, subsection, etc...
2249                 if (layout->latextype == LATEX_PARAGRAPH && textpit != 0) {
2250                         Layout const * layout2 = &(pars_[textpit - 1].layout());
2251                         if (layout2->latextype != LATEX_PARAGRAPH) {
2252                                 --textpit;
2253                                 layout = layout2;
2254                         }
2255                 }
2256                 if (layout->latextype != LATEX_PARAGRAPH)
2257                         name = layout->refprefix;
2258
2259                 // If none of the above worked, see if the inset knows.
2260                 if (name.empty()) {
2261                         InsetLayout const & il = cur.inset().getLayout();
2262                         name = il.refprefix();
2263                 }
2264         }
2265
2266         docstring text;
2267         docstring par_text = pars_[textpit].asString(AS_STR_SKIPDELETE);
2268
2269         // The return string of math matrices might contain linebreaks
2270         par_text = subst(par_text, '\n', '-');
2271         int const numwords = 3;
2272         for (int i = 0; i < numwords; ++i) {
2273                 if (par_text.empty())
2274                         break;
2275                 docstring head;
2276                 par_text = split(par_text, head, ' ');
2277                 // Is it legal to use spaces in labels ?
2278                 if (i > 0)
2279                         text += '-';
2280                 text += head;
2281         }
2282
2283         // Make sure it isn't too long
2284         unsigned int const max_label_length = 32;
2285         if (text.size() > max_label_length)
2286                 text.resize(max_label_length);
2287
2288         if (!name.empty())
2289                 text = name + ':' + text;
2290
2291         // We need a unique label
2292         docstring label = text;
2293         int i = 1;
2294         while (cur.buffer()->activeLabel(label)) {
2295                         label = text + '-' + convert<docstring>(i);
2296                         ++i;
2297                 }
2298
2299         return label;
2300 }
2301
2302
2303 docstring Text::asString(int options) const
2304 {
2305         return asString(0, pars_.size(), options);
2306 }
2307
2308
2309 docstring Text::asString(pit_type beg, pit_type end, int options) const
2310 {
2311         size_t i = size_t(beg);
2312         docstring str = pars_[i].asString(options);
2313         for (++i; i != size_t(end); ++i) {
2314                 str += '\n';
2315                 str += pars_[i].asString(options);
2316         }
2317         return str;
2318 }
2319
2320
2321 void Text::shortenForOutliner(docstring & str, size_t const maxlen)
2322 {
2323         support::truncateWithEllipsis(str, maxlen);
2324         for (char_type & c : str)
2325                 if (c == L'\n' || c == L'\t')
2326                         c = L' ';
2327 }
2328
2329
2330 void Text::forOutliner(docstring & os, size_t const maxlen,
2331                        bool const shorten) const
2332 {
2333         pit_type end = pars_.size() - 1;
2334         if (0 <= end && !pars_[0].labelString().empty())
2335                 os += pars_[0].labelString() + ' ';
2336         forOutliner(os, maxlen, 0, end, shorten);
2337 }
2338
2339
2340 void Text::forOutliner(docstring & os, size_t const maxlen,
2341                        pit_type pit_start, pit_type pit_end,
2342                        bool const shorten) const
2343 {
2344         size_t tmplen = shorten ? maxlen + 1 : maxlen;
2345         pit_type end = min(size_t(pit_end), pars_.size() - 1);
2346         bool first = true;
2347         for (pit_type i = pit_start; i <= end && os.length() < tmplen; ++i) {
2348                 if (!first)
2349                         os += ' ';
2350                 // This function lets the first label be treated separately
2351                 pars_[i].forOutliner(os, tmplen, false, !first);
2352                 first = false;
2353         }
2354         if (shorten)
2355                 shortenForOutliner(os, maxlen);
2356 }
2357
2358
2359 void Text::charsTranspose(Cursor & cur)
2360 {
2361         LBUFERR(this == cur.text());
2362
2363         pos_type pos = cur.pos();
2364
2365         // If cursor is at beginning or end of paragraph, do nothing.
2366         if (pos == cur.lastpos() || pos == 0)
2367                 return;
2368
2369         Paragraph & par = cur.paragraph();
2370
2371         // Get the positions of the characters to be transposed.
2372         pos_type pos1 = pos - 1;
2373         pos_type pos2 = pos;
2374
2375         // In change tracking mode, ignore deleted characters.
2376         while (pos2 < cur.lastpos() && par.isDeleted(pos2))
2377                 ++pos2;
2378         if (pos2 == cur.lastpos())
2379                 return;
2380
2381         while (pos1 >= 0 && par.isDeleted(pos1))
2382                 --pos1;
2383         if (pos1 < 0)
2384                 return;
2385
2386         // Don't do anything if one of the "characters" is not regular text.
2387         if (par.isInset(pos1) || par.isInset(pos2))
2388                 return;
2389
2390         // Store the characters to be transposed (including font information).
2391         char_type const char1 = par.getChar(pos1);
2392         Font const font1 =
2393                 par.getFontSettings(cur.buffer()->params(), pos1);
2394
2395         char_type const char2 = par.getChar(pos2);
2396         Font const font2 =
2397                 par.getFontSettings(cur.buffer()->params(), pos2);
2398
2399         // And finally, we are ready to perform the transposition.
2400         // Track the changes if Change Tracking is enabled.
2401         bool const trackChanges = cur.buffer()->params().track_changes;
2402
2403         cur.recordUndo();
2404
2405         par.eraseChar(pos2, trackChanges);
2406         par.eraseChar(pos1, trackChanges);
2407         par.insertChar(pos1, char2, font2, trackChanges);
2408         par.insertChar(pos2, char1, font1, trackChanges);
2409
2410         cur.checkBufferStructure();
2411
2412         // After the transposition, move cursor to after the transposition.
2413         setCursor(cur, cur.pit(), pos2);
2414         cur.forwardPos();
2415 }
2416
2417
2418 DocIterator Text::macrocontextPosition() const
2419 {
2420         return macrocontext_position_;
2421 }
2422
2423
2424 void Text::setMacrocontextPosition(DocIterator const & pos)
2425 {
2426         macrocontext_position_ = pos;
2427 }
2428
2429
2430 bool Text::completionSupported(Cursor const & cur) const
2431 {
2432         Paragraph const & par = cur.paragraph();
2433         return !cur.buffer()->isReadonly()
2434                 && !cur.selection()
2435                 && cur.pos() > 0
2436                 && (cur.pos() >= par.size() || par.isWordSeparator(cur.pos()))
2437                 && !par.isWordSeparator(cur.pos() - 1);
2438 }
2439
2440
2441 CompletionList const * Text::createCompletionList(Cursor const & cur) const
2442 {
2443         WordList const & list = theWordList(cur.getFont().language()->lang());
2444         return new TextCompletionList(cur, list);
2445 }
2446
2447
2448 bool Text::insertCompletion(Cursor & cur, docstring const & s)
2449 {
2450         LBUFERR(cur.bv().cursor() == cur);
2451         if (cur.buffer()->isReadonly())
2452                 return false;
2453         cur.recordUndo();
2454         cur.insert(s);
2455         cur.bv().cursor() = cur;
2456         if (!(cur.result().screenUpdate() & Update::Force))
2457                 cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
2458         return true;
2459 }
2460
2461
2462 docstring Text::completionPrefix(Cursor const & cur) const
2463 {
2464         CursorSlice from = cur.top();
2465         CursorSlice to = from;
2466         getWord(from, to, PREVIOUS_WORD);
2467
2468         return cur.paragraph().asString(from.pos(), to.pos());
2469 }
2470
2471 bool Text::isMainText() const
2472 {
2473         return &owner_->buffer().text() == this;
2474 }
2475
2476
2477 // Note that this is supposed to return a fully realized font.
2478 FontInfo Text::layoutFont(pit_type const pit) const
2479 {
2480         Layout const & layout = pars_[pit].layout();
2481
2482         if (!pars_[pit].getDepth())  {
2483                 FontInfo lf = layout.resfont;
2484                 // In case the default family has been customized
2485                 if (layout.font.family() == INHERIT_FAMILY)
2486                         lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
2487                 FontInfo icf = (!isMainText())
2488                                   // inside insets, we call the getFont() method
2489                                 ? owner_->getFont()
2490                                   // outside, we access the layout font directly
2491                                 : owner_->getLayout().font();
2492                 icf.realize(lf);
2493                 return icf;
2494         }
2495
2496         FontInfo font = layout.font;
2497         // Realize with the fonts of lesser depth.
2498         //font.realize(outerFont(pit));
2499         font.realize(owner_->buffer().params().getFont().fontInfo());
2500
2501         return font;
2502 }
2503
2504
2505 // Note that this is supposed to return a fully realized font.
2506 FontInfo Text::labelFont(Paragraph const & par) const
2507 {
2508         Buffer const & buffer = owner_->buffer();
2509         Layout const & layout = par.layout();
2510
2511         if (!par.getDepth()) {
2512                 FontInfo lf = layout.reslabelfont;
2513                 // In case the default family has been customized
2514                 if (layout.labelfont.family() == INHERIT_FAMILY)
2515                         lf.setFamily(buffer.params().getFont().fontInfo().family());
2516                 return lf;
2517         }
2518
2519         FontInfo font = layout.labelfont;
2520         // Realize with the fonts of lesser depth.
2521         font.realize(buffer.params().getFont().fontInfo());
2522
2523         return font;
2524 }
2525
2526
2527 void Text::setCharFont(pit_type pit,
2528                 pos_type pos, Font const & fnt, Font const & display_font)
2529 {
2530         Buffer const & buffer = owner_->buffer();
2531         Font font = fnt;
2532         Layout const & layout = pars_[pit].layout();
2533
2534         // Get concrete layout font to reduce against
2535         FontInfo layoutfont;
2536
2537         if (pos < pars_[pit].beginOfBody())
2538                 layoutfont = layout.labelfont;
2539         else
2540                 layoutfont = layout.font;
2541
2542         // Realize against environment font information
2543         if (pars_[pit].getDepth()) {
2544                 pit_type tp = pit;
2545                 while (!layoutfont.resolved() &&
2546                        tp != pit_type(paragraphs().size()) &&
2547                        pars_[tp].getDepth()) {
2548                         tp = outerHook(tp);
2549                         if (tp != pit_type(paragraphs().size()))
2550                                 layoutfont.realize(pars_[tp].layout().font);
2551                 }
2552         }
2553
2554         // Inside inset, apply the inset's font attributes if any
2555         // (charstyle!)
2556         if (!isMainText())
2557                 layoutfont.realize(display_font.fontInfo());
2558
2559         layoutfont.realize(buffer.params().getFont().fontInfo());
2560
2561         // Now, reduce font against full layout font
2562         font.fontInfo().reduce(layoutfont);
2563
2564         pars_[pit].setFont(pos, font);
2565 }
2566
2567
2568 void Text::setInsetFont(BufferView const & bv, pit_type pit,
2569                 pos_type pos, Font const & font)
2570 {
2571         Inset * const inset = pars_[pit].getInset(pos);
2572         LASSERT(inset && inset->resetFontEdit(), return);
2573
2574         idx_type endidx = inset->nargs();
2575         for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
2576                 Text * text = cs.text();
2577                 if (text) {
2578                         // last position of the cell
2579                         CursorSlice cellend = cs;
2580                         cellend.pit() = cellend.lastpit();
2581                         cellend.pos() = cellend.lastpos();
2582                         text->setFont(bv, cs, cellend, font);
2583                 }
2584         }
2585 }
2586
2587
2588 void Text::setLayout(pit_type start, pit_type end,
2589                      docstring const & layout)
2590 {
2591         // FIXME: make this work in multicell selection case
2592         LASSERT(start != end, return);
2593
2594         Buffer const & buffer = owner_->buffer();
2595         BufferParams const & bp = buffer.params();
2596         Layout const & lyxlayout = bp.documentClass()[layout];
2597
2598         for (pit_type pit = start; pit != end; ++pit) {
2599                 Paragraph & par = pars_[pit];
2600                 // Is this a separating paragraph? If so,
2601                 // this needs to be standard layout
2602                 bool const is_separator = par.size() == 1
2603                                 && par.isEnvSeparator(0);
2604                 par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
2605                 if (lyxlayout.margintype == MARGIN_MANUAL)
2606                         par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
2607         }
2608
2609         deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
2610 }
2611
2612
2613 // set layout over selection and make a total rebreak of those paragraphs
2614 void Text::setLayout(Cursor & cur, docstring const & layout)
2615 {
2616         LBUFERR(this == cur.text());
2617
2618         pit_type start = cur.selBegin().pit();
2619         pit_type end = cur.selEnd().pit() + 1;
2620         cur.recordUndoSelection();
2621         setLayout(start, end, layout);
2622         cur.fixIfBroken();
2623         cur.setCurrentFont();
2624         cur.forceBufferUpdate();
2625 }
2626
2627
2628 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
2629                         Paragraph const & par, int max_depth)
2630 {
2631         int const depth = par.params().depth();
2632         if (type == Text::INC_DEPTH && depth < max_depth)
2633                 return true;
2634         if (type == Text::DEC_DEPTH && depth > 0)
2635                 return true;
2636         return false;
2637 }
2638
2639
2640 bool Text::changeDepthAllowed(Cursor const & cur, DEPTH_CHANGE type) const
2641 {
2642         LBUFERR(this == cur.text());
2643         // this happens when selecting several cells in tabular (bug 2630)
2644         if (cur.selBegin().idx() != cur.selEnd().idx())
2645                 return false;
2646
2647         pit_type const beg = cur.selBegin().pit();
2648         pit_type const end = cur.selEnd().pit() + 1;
2649         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2650
2651         for (pit_type pit = beg; pit != end; ++pit) {
2652                 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
2653                         return true;
2654                 max_depth = pars_[pit].getMaxDepthAfter();
2655         }
2656         return false;
2657 }
2658
2659
2660 void Text::changeDepth(Cursor const & cur, DEPTH_CHANGE type)
2661 {
2662         LBUFERR(this == cur.text());
2663         pit_type const beg = cur.selBegin().pit();
2664         pit_type const end = cur.selEnd().pit() + 1;
2665         cur.recordUndoSelection();
2666         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
2667
2668         for (pit_type pit = beg; pit != end; ++pit) {
2669                 Paragraph & par = pars_[pit];
2670                 if (lyx::changeDepthAllowed(type, par, max_depth)) {
2671                         int const depth = par.params().depth();
2672                         if (type == INC_DEPTH)
2673                                 par.params().depth(depth + 1);
2674                         else
2675                                 par.params().depth(depth - 1);
2676                 }
2677                 max_depth = par.getMaxDepthAfter();
2678         }
2679         // this handles the counter labels, and also fixes up
2680         // depth values for follow-on (child) paragraphs
2681         cur.forceBufferUpdate();
2682 }
2683
2684
2685 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
2686 {
2687         LASSERT(this == cur.text(), return);
2688
2689         // If there is a selection, record undo before the cursor font is changed.
2690         if (cur.selection())
2691                 cur.recordUndoSelection();
2692
2693         // Set the current_font
2694         // Determine basis font
2695         FontInfo layoutfont;
2696         pit_type pit = cur.pit();
2697         if (cur.pos() < pars_[pit].beginOfBody())
2698                 layoutfont = labelFont(pars_[pit]);
2699         else
2700                 layoutfont = layoutFont(pit);
2701
2702         // Update current font
2703         cur.real_current_font.update(font,
2704                                         cur.buffer()->params().language,
2705                                         toggleall);
2706
2707         // Reduce to implicit settings
2708         cur.current_font = cur.real_current_font;
2709         cur.current_font.fontInfo().reduce(layoutfont);
2710         // And resolve it completely
2711         cur.real_current_font.fontInfo().realize(layoutfont);
2712
2713         // if there is no selection that's all we need to do
2714         if (!cur.selection())
2715                 return;
2716
2717         // Ok, we have a selection.
2718         Font newfont = font;
2719
2720         if (toggleall) {
2721                 // Toggling behaves as follows: We check the first character of the
2722                 // selection. If it's (say) got EMPH on, then we set to off; if off,
2723                 // then to on. With families and the like, we set it to INHERIT, if
2724                 // we already have it.
2725                 CursorSlice const & sl = cur.selBegin();
2726                 Text const & text = *sl.text();
2727                 Paragraph const & par = text.getPar(sl.pit());
2728
2729                 // get font at the position
2730                 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
2731                         text.outerFont(sl.pit()));
2732                 FontInfo const & oldfi = oldfont.fontInfo();
2733
2734                 FontInfo & newfi = newfont.fontInfo();
2735
2736                 FontFamily newfam = newfi.family();
2737                 if (newfam !=   INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
2738                                 newfam == oldfi.family())
2739                         newfi.setFamily(INHERIT_FAMILY);
2740
2741                 FontSeries newser = newfi.series();
2742                 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
2743                         newfi.setSeries(INHERIT_SERIES);
2744
2745                 FontShape newshp = newfi.shape();
2746                 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
2747                                 newshp == oldfi.shape())
2748                         newfi.setShape(INHERIT_SHAPE);
2749
2750                 ColorCode newcol = newfi.color();
2751                 if (newcol != Color_none && newcol != Color_inherit
2752                     && newcol != Color_ignore && newcol == oldfi.color())
2753                         newfi.setColor(Color_none);
2754
2755                 // ON/OFF ones
2756                 if (newfi.emph() == FONT_TOGGLE)
2757                         newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
2758                 if (newfi.underbar() == FONT_TOGGLE)
2759                         newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
2760                 if (newfi.strikeout() == FONT_TOGGLE)
2761                         newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
2762                 if (newfi.xout() == FONT_TOGGLE)
2763                         newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
2764                 if (newfi.uuline() == FONT_TOGGLE)
2765                         newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
2766                 if (newfi.uwave() == FONT_TOGGLE)
2767                         newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
2768                 if (newfi.noun() == FONT_TOGGLE)
2769                         newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
2770                 if (newfi.number() == FONT_TOGGLE)
2771                         newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
2772                 if (newfi.nospellcheck() == FONT_TOGGLE)
2773                         newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
2774         }
2775
2776         setFont(cur.bv(), cur.selectionBegin().top(),
2777                 cur.selectionEnd().top(), newfont);
2778 }
2779
2780
2781 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
2782                 CursorSlice const & end, Font const & font)
2783 {
2784         Buffer const & buffer = bv.buffer();
2785
2786         // Don't use forwardChar here as ditend might have
2787         // pos() == lastpos() and forwardChar would miss it.
2788         // Can't use forwardPos either as this descends into
2789         // nested insets.
2790         Language const * language = buffer.params().language;
2791         for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
2792                 if (dit.pos() == dit.lastpos())
2793                         continue;
2794                 pit_type const pit = dit.pit();
2795                 pos_type const pos = dit.pos();
2796                 Inset * inset = pars_[pit].getInset(pos);
2797                 if (inset && inset->resetFontEdit()) {
2798                         // We need to propagate the font change to all
2799                         // text cells of the inset (bugs 1973, 6919).
2800                         setInsetFont(bv, pit, pos, font);
2801                 }
2802                 TextMetrics const & tm = bv.textMetrics(this);
2803                 Font f = tm.displayFont(pit, pos);
2804                 f.update(font, language);
2805                 setCharFont(pit, pos, f, tm.font_);
2806                 // font change may change language...
2807                 // spell checker has to know that
2808                 pars_[pit].requestSpellCheck(pos);
2809         }
2810 }
2811
2812
2813 bool Text::cursorTop(Cursor & cur)
2814 {
2815         LBUFERR(this == cur.text());
2816         return setCursor(cur, 0, 0);
2817 }
2818
2819
2820 bool Text::cursorBottom(Cursor & cur)
2821 {
2822         LBUFERR(this == cur.text());
2823         return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
2824 }
2825
2826
2827 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
2828 {
2829         LBUFERR(this == cur.text());
2830         // If the mask is completely neutral, tell user
2831         if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
2832                 // Could only happen with user style
2833                 cur.message(_("No font change defined."));
2834                 return;
2835         }
2836
2837         // Try implicit word selection
2838         // If there is a change in the language the implicit word selection
2839         // is disabled.
2840         CursorSlice const resetCursor = cur.top();
2841         bool const implicitSelection =
2842                 font.language() == ignore_language
2843                 && font.fontInfo().number() == FONT_IGNORE
2844                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
2845
2846         // Set font
2847         setFont(cur, font, toggleall);
2848
2849         // Implicit selections are cleared afterwards
2850         // and cursor is set to the original position.
2851         if (implicitSelection) {
2852                 cur.clearSelection();
2853                 cur.top() = resetCursor;
2854                 cur.resetAnchor();
2855         }
2856
2857         // if there was no selection at all, the point was to change cursor font.
2858         // Otherwise, we want to reset it to local text font.
2859         if (cur.selection() || implicitSelection)
2860                 cur.setCurrentFont();
2861 }
2862
2863
2864 docstring Text::getStringForDialog(Cursor & cur)
2865 {
2866         LBUFERR(this == cur.text());
2867
2868         if (cur.selection())
2869                 return cur.selectionAsString(false);
2870
2871         // Try implicit word selection. If there is a change
2872         // in the language the implicit word selection is
2873         // disabled.
2874         selectWordWhenUnderCursor(cur, WHOLE_WORD);
2875         docstring const & retval = cur.selectionAsString(false);
2876         cur.clearSelection();
2877         return retval;
2878 }
2879
2880
2881 void Text::setLabelWidthStringToSequence(Cursor const & cur,
2882                 docstring const & s)
2883 {
2884         Cursor c = cur;
2885         // Find first of same layout in sequence
2886         while (!isFirstInSequence(c.pit())) {
2887                 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
2888         }
2889
2890         // now apply label width string to every par
2891         // in sequence
2892         depth_type const depth = c.paragraph().getDepth();
2893         Layout const & layout = c.paragraph().layout();
2894         for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
2895                 while (c.paragraph().getDepth() > depth) {
2896                         ++c.pit();
2897                         if (c.pit() > c.lastpit())
2898                                 return;
2899                 }
2900                 if (c.paragraph().getDepth() < depth)
2901                         return;
2902                 if (c.paragraph().layout() != layout)
2903                         return;
2904                 c.recordUndo();
2905                 c.paragraph().setLabelWidthString(s);
2906         }
2907 }
2908
2909
2910 void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
2911 {
2912         LBUFERR(cur.text());
2913
2914         //FIXME UNICODE
2915         string const argument = to_utf8(arg);
2916         depth_type priordepth = -1;
2917         Layout priorlayout;
2918         Cursor c(cur.bv());
2919         c.setCursor(cur.selectionBegin());
2920         for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
2921                 Paragraph & par = c.paragraph();
2922                 ParagraphParameters params = par.params();
2923                 params.read(argument, merge);
2924                 // Changes to label width string apply to all paragraphs
2925                 // with same layout in a sequence.
2926                 // Do this only once for a selected range of paragraphs
2927                 // of the same layout and depth.
2928                 c.recordUndo();
2929                 par.params().apply(params, par.layout());
2930                 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2931                         setLabelWidthStringToSequence(c, params.labelWidthString());
2932                 priordepth = par.getDepth();
2933                 priorlayout = par.layout();
2934         }
2935 }
2936
2937
2938 void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
2939 {
2940         LBUFERR(cur.text());
2941
2942         depth_type priordepth = -1;
2943         Layout priorlayout;
2944         Cursor c(cur.bv());
2945         c.setCursor(cur.selectionBegin());
2946         for ( ; c < cur.selectionEnd() ; ++c.pit()) {
2947                 Paragraph & par = c.paragraph();
2948                 // Changes to label width string apply to all paragraphs
2949                 // with same layout in a sequence.
2950                 // Do this only once for a selected range of paragraphs
2951                 // of the same layout and depth.
2952                 cur.recordUndo();
2953                 par.params().apply(p, par.layout());
2954                 if (par.getDepth() != priordepth || par.layout() != priorlayout)
2955                         setLabelWidthStringToSequence(c,
2956                                 par.params().labelWidthString());
2957                 priordepth = par.getDepth();
2958                 priorlayout = par.layout();
2959         }
2960 }
2961
2962
2963 // this really should just insert the inset and not move the cursor.
2964 void Text::insertInset(Cursor & cur, Inset * inset)
2965 {
2966         LBUFERR(this == cur.text());
2967         LBUFERR(inset);
2968         cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
2969                 Change(cur.buffer()->params().track_changes
2970                 ? Change::INSERTED : Change::UNCHANGED));
2971 }
2972
2973
2974 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
2975                         bool setfont, bool boundary)
2976 {
2977         TextMetrics const & tm = cur.bv().textMetrics(this);
2978         bool const update_needed = !tm.contains(pit);
2979         Cursor old = cur;
2980         setCursorIntern(cur, pit, pos, setfont, boundary);
2981         return cur.bv().checkDepm(cur, old) || update_needed;
2982 }
2983
2984
2985 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
2986                            bool setfont, bool boundary)
2987 {
2988         LBUFERR(this == cur.text());
2989         cur.boundary(boundary);
2990         cur.top().setPitPos(pit, pos);
2991         if (setfont)
2992                 cur.setCurrentFont();
2993 }
2994
2995
2996 bool Text::checkAndActivateInset(Cursor & cur, bool front)
2997 {
2998         if (front && cur.pos() == cur.lastpos())
2999                 return false;
3000         if (!front && cur.pos() == 0)
3001                 return false;
3002         Inset * inset = front ? cur.nextInset() : cur.prevInset();
3003         if (!inset || !inset->editable())
3004                 return false;
3005         if (cur.selection() && cur.realAnchor().find(inset) == -1)
3006                 return false;
3007         /*
3008          * Apparently, when entering an inset we are expected to be positioned
3009          * *before* it in the containing paragraph, regardless of the direction
3010          * from which we are entering. Otherwise, cursor placement goes awry,
3011          * and when we exit from the beginning, we'll be placed *after* the
3012          * inset.
3013          */
3014         if (!front)
3015                 --cur.pos();
3016         inset->edit(cur, front);
3017         cur.setCurrentFont();
3018         cur.boundary(false);
3019         return true;
3020 }
3021
3022
3023 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
3024 {
3025         if (cur.pos() == -1)
3026                 return false;
3027         if (cur.pos() == cur.lastpos())
3028                 return false;
3029         Paragraph & par = cur.paragraph();
3030         Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
3031         if (!inset || !inset->editable())
3032                 return false;
3033         if (cur.selection() && cur.realAnchor().find(inset) == -1)
3034                 return false;
3035         inset->edit(cur, movingForward,
3036                 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
3037         cur.setCurrentFont();
3038         cur.boundary(false);
3039         return true;
3040 }
3041
3042
3043 bool Text::cursorBackward(Cursor & cur)
3044 {
3045         // Tell BufferView to test for FitCursor in any case!
3046         cur.screenUpdateFlags(Update::FitCursor);
3047
3048         // not at paragraph start?
3049         if (cur.pos() > 0) {
3050                 // if on right side of boundary (i.e. not at paragraph end, but line end)
3051                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
3052                 // there are some exceptions to ignore this: lineseps, newlines, spaces
3053 #if 0
3054                 // some effectless debug code to see the values in the debugger
3055                 bool bound = cur.boundary();
3056                 int rowpos = cur.textRow().pos();
3057                 int pos = cur.pos();
3058                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
3059                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
3060                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
3061 #endif
3062                 if (!cur.boundary() &&
3063                                 cur.textRow().pos() == cur.pos() &&
3064                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
3065                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
3066                                 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
3067                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
3068                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
3069                 }
3070
3071                 // go left and try to enter inset
3072                 if (checkAndActivateInset(cur, false))
3073                         return false;
3074
3075                 // normal character left
3076                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
3077         }
3078
3079         // move to the previous paragraph or do nothing
3080         if (cur.pit() > 0) {
3081                 Paragraph & par = getPar(cur.pit() - 1);
3082                 pos_type lastpos = par.size();
3083                 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
3084                         return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
3085                 else
3086                         return setCursor(cur, cur.pit() - 1, lastpos, true, false);
3087         }
3088         return false;
3089 }
3090
3091
3092 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
3093 {
3094         Cursor temp_cur = cur;
3095         temp_cur.posVisLeft(skip_inset);
3096         if (temp_cur.depth() > cur.depth()) {
3097                 cur = temp_cur;
3098                 return false;
3099         }
3100         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3101                 true, temp_cur.boundary());
3102 }
3103
3104
3105 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
3106 {
3107         Cursor temp_cur = cur;
3108         temp_cur.posVisRight(skip_inset);
3109         if (temp_cur.depth() > cur.depth()) {
3110                 cur = temp_cur;
3111                 return false;
3112         }
3113         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
3114                 true, temp_cur.boundary());
3115 }
3116
3117
3118 bool Text::cursorForward(Cursor & cur)
3119 {
3120         // Tell BufferView to test for FitCursor in any case!
3121         cur.screenUpdateFlags(Update::FitCursor);
3122
3123         // not at paragraph end?
3124         if (cur.pos() != cur.lastpos()) {
3125                 // in front of editable inset, i.e. jump into it?
3126                 if (checkAndActivateInset(cur, true))
3127                         return false;
3128
3129                 TextMetrics const & tm = cur.bv().textMetrics(this);
3130                 // if left of boundary -> just jump to right side
3131                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
3132                 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
3133                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
3134
3135                 // next position is left of boundary,
3136                 // but go to next line for special cases like space, newline, linesep
3137 #if 0
3138                 // some effectless debug code to see the values in the debugger
3139                 int endpos = cur.textRow().endpos();
3140                 int lastpos = cur.lastpos();
3141                 int pos = cur.pos();
3142                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
3143                 bool newline = cur.paragraph().isNewline(cur.pos());
3144                 bool sep = cur.paragraph().isSeparator(cur.pos());
3145                 if (cur.pos() != cur.lastpos()) {
3146                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
3147                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
3148                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
3149                 }
3150 #endif
3151                 if (cur.textRow().endpos() == cur.pos() + 1) {
3152                         if (cur.paragraph().isEnvSeparator(cur.pos()) &&
3153                             cur.pos() + 1 == cur.lastpos() &&
3154                             cur.pit() != cur.lastpit()) {
3155                                 // move to next paragraph
3156                                 return setCursor(cur, cur.pit() + 1, 0, true, false);
3157                         } else if (cur.textRow().endpos() != cur.lastpos() &&
3158                                    !cur.paragraph().isNewline(cur.pos()) &&
3159                                    !cur.paragraph().isEnvSeparator(cur.pos()) &&
3160                                    !cur.paragraph().isLineSeparator(cur.pos()) &&
3161                                    !cur.paragraph().isSeparator(cur.pos())) {
3162                                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3163                         }
3164                 }
3165
3166                 // in front of RTL boundary? Stay on this side of the boundary because:
3167                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
3168                 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
3169                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
3170
3171                 // move right
3172                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
3173         }
3174
3175         // move to next paragraph
3176         if (cur.pit() != cur.lastpit())
3177                 return setCursor(cur, cur.pit() + 1, 0, true, false);
3178         return false;
3179 }
3180
3181
3182 bool Text::cursorUpParagraph(Cursor & cur)
3183 {
3184         bool updated = false;
3185         if (cur.pos() > 0)
3186                 updated = setCursor(cur, cur.pit(), 0);
3187         else if (cur.pit() != 0)
3188                 updated = setCursor(cur, cur.pit() - 1, 0);
3189         return updated;
3190 }
3191
3192
3193 bool Text::cursorDownParagraph(Cursor & cur)
3194 {
3195         bool updated = false;
3196         if (cur.pit() != cur.lastpit())
3197                 if (lyxrc.mac_like_cursor_movement)
3198                         if (cur.pos() == cur.lastpos())
3199                                 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
3200                         else
3201                                 updated = setCursor(cur, cur.pit(), cur.lastpos());
3202                 else
3203                         updated = setCursor(cur, cur.pit() + 1, 0);
3204         else
3205                 updated = setCursor(cur, cur.pit(), cur.lastpos());
3206         return updated;
3207 }
3208
3209 namespace {
3210
3211 /** delete num_spaces characters between from and to. Return the
3212  * number of spaces that got physically deleted (not marked as
3213  * deleted) */
3214 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
3215                                   int num_spaces, bool const trackChanges)
3216 {
3217         if (num_spaces <= 0)
3218                 return 0;
3219
3220         // First, delete spaces marked as inserted
3221         int pos = from;
3222         while (pos < to && num_spaces > 0) {
3223                 Change const & change = par.lookupChange(pos);
3224                 if (change.inserted() && change.currentAuthor()) {
3225                         par.eraseChar(pos, trackChanges);
3226                         --num_spaces;
3227                         --to;
3228                 } else
3229                         ++pos;
3230         }
3231
3232         // Then remove remaining spaces
3233         int const psize = par.size();
3234         par.eraseChars(from, from + num_spaces, trackChanges);
3235         return psize - par.size();
3236 }
3237
3238 }
3239
3240
3241 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
3242                 Cursor & old, bool & need_anchor_change)
3243 {
3244         //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
3245
3246         Paragraph & oldpar = old.paragraph();
3247         bool const trackChanges = cur.buffer()->params().track_changes;
3248         bool result = false;
3249
3250         // We do nothing if cursor did not move
3251         if (cur.top() == old.top())
3252                 return false;
3253
3254         // We do not do anything on read-only documents
3255         if (cur.buffer()->isReadonly())
3256                 return false;
3257
3258         // Whether a common inset is found and whether the cursor is still in
3259         // the same paragraph (possibly nested).
3260         int const depth = cur.find(&old.inset());
3261         bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
3262                 && old.pit() == cur[depth].pit();
3263
3264         /*
3265          * (1) If the chars around the old cursor were spaces and the
3266          * paragraph is not in free spacing mode, delete some of them, but
3267          * only if the cursor has really moved.
3268          */
3269
3270         /* There are still some small problems that can lead to
3271            double spaces stored in the document file or space at
3272            the beginning of paragraphs(). This happens if you have
3273            the cursor between two spaces and then save. Or if you
3274            cut and paste and the selection has a space at the
3275            beginning and then save right after the paste. (Lgb)
3276         */
3277         if (!oldpar.isFreeSpacing()) {
3278                 // find range of spaces around cursors
3279                 pos_type from = old.pos();
3280                 while (from > 0
3281                            && oldpar.isLineSeparator(from - 1)
3282                            && !oldpar.isDeleted(from - 1))
3283                         --from;
3284                 pos_type to = old.pos();
3285                 while (to < old.lastpos()
3286                            && oldpar.isLineSeparator(to)
3287                            && !oldpar.isDeleted(to))
3288                         ++to;
3289
3290                 int num_spaces = to - from;
3291                 // If we are not at the start of the paragraph, keep one space
3292                 if (from != to && from > 0)
3293                         --num_spaces;
3294
3295                 // If cursor is inside range, keep one additional space
3296                 if (same_par && cur.pos() > from && cur.pos() < to)
3297                         --num_spaces;
3298
3299                 // Remove spaces and adapt cursor.
3300                 if (num_spaces > 0) {
3301                         old.recordUndo();
3302                         int const deleted =
3303                                 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
3304                         // correct cur position
3305                         // FIXME: there can be other cursors pointing there, we should update them
3306                         if (same_par) {
3307                                 if (cur[depth].pos() >= to)
3308                                         cur[depth].pos() -= deleted;
3309                                 else if (cur[depth].pos() > from)
3310                                         cur[depth].pos() = min(from + 1, old.lastpos());
3311                                 need_anchor_change = true;
3312                         }
3313                         result = true;
3314                 }
3315         }
3316
3317         /*
3318          * (2) If the paragraph where the cursor was is empty, delete it
3319          */
3320
3321         // only do our other magic if we changed paragraph
3322         if (same_par)
3323                 return result;
3324
3325         // only do our magic if the paragraph is empty
3326         if (!oldpar.empty())
3327                 return result;
3328
3329         // don't delete anything if this is the ONLY paragraph!
3330         if (old.lastpit() == 0)
3331                 return result;
3332
3333         // Do not delete empty paragraphs with keepempty set.
3334         if (oldpar.allowEmpty())
3335                 return result;
3336
3337         // Delete old par.
3338         old.recordUndo(max(old.pit() - 1, pit_type(0)),
3339                        min(old.pit() + 1, old.lastpit()));
3340         ParagraphList & plist = old.text()->paragraphs();
3341         bool const soa = oldpar.params().startOfAppendix();
3342         plist.erase(plist.iterator_at(old.pit()));
3343         // do not lose start of appendix marker (bug 4212)
3344         if (soa && old.pit() < pit_type(plist.size()))
3345                 plist[old.pit()].params().startOfAppendix(true);
3346
3347         // see #warning (FIXME?) above
3348         if (cur.depth() >= old.depth()) {
3349                 CursorSlice & curslice = cur[old.depth() - 1];
3350                 if (&curslice.inset() == &old.inset()
3351                     && curslice.idx() == old.idx()
3352                     && curslice.pit() > old.pit()) {
3353                         --curslice.pit();
3354                         // since a paragraph has been deleted, all the
3355                         // insets after `old' have been copied and
3356                         // their address has changed. Therefore we
3357                         // need to `regenerate' cur. (JMarc)
3358                         cur.updateInsets(&(cur.bottom().inset()));
3359                         need_anchor_change = true;
3360                 }
3361         }
3362
3363         return true;
3364 }
3365
3366
3367 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
3368 {
3369         pos_type last_pos = pars_[last].size() - 1;
3370         deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
3371 }
3372
3373
3374 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
3375                                          pos_type first_pos, pos_type last_pos,
3376                                          bool trackChanges)
3377 {
3378         LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
3379
3380         for (pit_type pit = first; pit <= last; ++pit) {
3381                 Paragraph & par = pars_[pit];
3382
3383                 /*
3384                  * (1) Delete consecutive spaces
3385                  */
3386                 if (!par.isFreeSpacing()) {
3387                         pos_type from = (pit == first) ? first_pos : 0;
3388                         pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
3389                         while (from < to_pos) {
3390                                 // skip non-spaces
3391                                 while (from < par.size()
3392                                            && (!par.isLineSeparator(from) || par.isDeleted(from)))
3393                                         ++from;
3394                                 // find string of spaces
3395                                 pos_type to = from;
3396                                 while (to < par.size()
3397                                            && par.isLineSeparator(to) && !par.isDeleted(to))
3398                                         ++to;
3399                                 // empty? We are done
3400                                 if (from == to)
3401                                         break;
3402
3403                                 int num_spaces = to - from;
3404
3405                                 // If we are not at the extremity of the paragraph, keep one space
3406                                 if (from != to && from > 0 && to < par.size())
3407                                         --num_spaces;
3408
3409                                 // Remove spaces if needed
3410                                 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
3411                                 from = to - deleted;
3412                         }
3413                 }
3414
3415                 /*
3416                  * (2) Delete empty pragraphs
3417                  */
3418
3419                 // don't delete anything if this is the only remaining paragraph
3420                 // within the given range. Note: Text::acceptOrRejectChanges()
3421                 // sets the cursor to 'first' after calling DEPM
3422                 if (first == last)
3423                         continue;
3424
3425                 // don't delete empty paragraphs with keepempty set
3426                 if (par.allowEmpty())
3427                         continue;
3428
3429                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
3430                         pars_.erase(pars_.iterator_at(pit));
3431                         --pit;
3432                         --last;
3433                         continue;
3434                 }
3435         }
3436 }
3437
3438
3439 namespace {
3440
3441 // globals...
3442 typedef limited_stack<pair<docstring, Font>> FontStack;
3443 static FontStack freeFonts(15);
3444 static bool toggleall = false;
3445
3446 void toggleAndShow(Cursor & cur, Text * text,
3447         Font const & font, bool togall = true)
3448 {
3449         text->toggleFree(cur, font, togall);
3450
3451         if (font.language() != ignore_language ||
3452             font.fontInfo().number() != FONT_IGNORE) {
3453                 TextMetrics const & tm = cur.bv().textMetrics(text);
3454                 if (cur.boundary() != tm.isRTLBoundary(cur.pit(), cur.pos(),
3455                                                        cur.real_current_font))
3456                         text->setCursor(cur, cur.pit(), cur.pos(),
3457                                         false, !cur.boundary());
3458                 if (font.language() != ignore_language)
3459                         // We need a buffer update if we change the language
3460                         // (e.g., with info insets or if the selection contains
3461                         // a par label)
3462                         cur.forceBufferUpdate();
3463         }
3464 }
3465
3466
3467 void moveCursor(Cursor & cur, bool selecting)
3468 {
3469         if (selecting || cur.mark())
3470                 cur.setSelection();
3471 }
3472
3473
3474 void finishChange(Cursor & cur, bool selecting)
3475 {
3476         cur.finishUndo();
3477         moveCursor(cur, selecting);
3478 }
3479
3480
3481 void mathDispatch(Cursor & cur, FuncRequest const & cmd)
3482 {
3483         cur.recordUndo();
3484         docstring sel = cur.selectionAsString(false);
3485
3486         // It may happen that sel is empty but there is a selection
3487         replaceSelection(cur);
3488
3489         // Is this a valid formula?
3490         bool valid = true;
3491
3492         if (sel.empty()) {
3493 #ifdef ENABLE_ASSERTIONS
3494                 const int old_pos = cur.pos();
3495 #endif
3496                 cur.insert(new InsetMathHull(cur.buffer(), hullSimple));
3497 #ifdef ENABLE_ASSERTIONS
3498                 LATTEST(old_pos == cur.pos());
3499 #endif
3500                 cur.nextInset()->edit(cur, true);
3501                 if (cmd.action() != LFUN_MATH_MODE)
3502                         // LFUN_MATH_MODE has a different meaning in math mode
3503                         cur.dispatch(cmd);
3504         } else {
3505                 InsetMathHull * formula = new InsetMathHull(cur.buffer());
3506                 string const selstr = to_utf8(sel);
3507                 istringstream is(selstr);
3508                 Lexer lex;
3509                 lex.setStream(is);
3510                 if (!formula->readQuiet(lex)) {
3511                         // No valid formula, let's try with delims
3512                         is.str("$" + selstr + "$");
3513                         lex.setStream(is);
3514                         if (!formula->readQuiet(lex)) {
3515                                 // Still not valid, leave it as is
3516                                 valid = false;
3517                                 delete formula;
3518                                 cur.insert(sel);
3519                         }
3520                 }
3521                 if (valid) {
3522                         cur.insert(formula);
3523                         cur.nextInset()->edit(cur, true);
3524                         LASSERT(cur.inMathed(), return);
3525                         cur.pos() = 0;
3526                         cur.resetAnchor();
3527                         cur.selection(true);
3528                         cur.pos() = cur.lastpos();
3529                         if (cmd.action() != LFUN_MATH_MODE)
3530                                 // LFUN_MATH_MODE has a different meaning in math mode
3531                                 cur.dispatch(cmd);
3532                         cur.clearSelection();
3533                         cur.pos() = cur.lastpos();
3534                 }
3535         }
3536         if (valid)
3537                 cur.message(from_utf8(N_("Math editor mode")));
3538         else
3539                 cur.message(from_utf8(N_("No valid math formula")));
3540 }
3541
3542
3543 void regexpDispatch(Cursor & cur, FuncRequest const & cmd)
3544 {
3545         LASSERT(cmd.action() == LFUN_REGEXP_MODE, return);
3546         if (cur.inRegexped()) {
3547                 cur.message(_("Already in regular expression mode"));
3548                 return;
3549         }
3550         cur.recordUndo();
3551         docstring sel = cur.selectionAsString(false);
3552
3553         // It may happen that sel is empty but there is a selection
3554         replaceSelection(cur);
3555
3556         cur.insert(new InsetMathHull(cur.buffer(), hullRegexp));
3557         cur.nextInset()->edit(cur, true);
3558         cur.niceInsert(sel);
3559
3560         cur.message(_("Regexp editor mode"));
3561 }
3562
3563
3564 void specialChar(Cursor & cur, InsetSpecialChar::Kind kind)
3565 {
3566         cur.recordUndo();
3567         cap::replaceSelection(cur);
3568         cur.insert(new InsetSpecialChar(kind));
3569         cur.posForward();
3570 }
3571
3572
3573 void ipaChar(Cursor & cur, InsetIPAChar::Kind kind)
3574 {
3575         cur.recordUndo();
3576         cap::replaceSelection(cur);
3577         cur.insert(new InsetIPAChar(kind));
3578         cur.posForward();
3579 }
3580
3581
3582 bool doInsertInset(Cursor & cur, Text * text,
3583                           FuncRequest const & cmd, bool edit,
3584                           bool pastesel, bool resetfont = false)
3585 {
3586         Buffer & buffer = cur.bv().buffer();
3587         BufferParams const & bparams = buffer.params();
3588         Inset * inset = createInset(&buffer, cmd);
3589         if (!inset)
3590                 return false;
3591
3592         if (InsetCollapsible * ci = inset->asInsetCollapsible())
3593                 ci->setButtonLabel();
3594
3595         cur.recordUndo();
3596         if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3597                 bool cotextinsert = false;
3598                 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3599                 Layout const & lay = cur.paragraph().layout();
3600                 Layout::LaTeXArgMap args = lay.args();
3601                 Layout::LaTeXArgMap::const_iterator const lait = args.find(ia->name());
3602                 if (lait != args.end())
3603                         cotextinsert = (*lait).second.insertcotext;
3604                 else {
3605                         InsetLayout const & il = cur.inset().getLayout();
3606                         args = il.args();
3607                         Layout::LaTeXArgMap::const_iterator const ilait = args.find(ia->name());
3608                         if (ilait != args.end())
3609                                 cotextinsert = (*ilait).second.insertcotext;
3610                 }
3611                 // The argument requests to insert a copy of the co-text to the inset
3612                 if (cotextinsert) {
3613                         docstring ds;
3614                         // If we have a selection within a paragraph, use this
3615                         if (cur.selection() && cur.selBegin().pit() == cur.selEnd().pit())
3616                                 ds = cur.selectionAsString(false);
3617                         // else use the whole paragraph
3618                         else
3619                                 ds = cur.paragraph().asString();
3620                         text->insertInset(cur, inset);
3621                         ia->init(cur.paragraph());
3622                         if (edit)
3623                                 inset->edit(cur, true);
3624                         // Now put co-text into inset
3625                         Font const f(inherit_font, cur.current_font.language());
3626                         if (!ds.empty()) {
3627                                 cur.text()->insertStringAsLines(cur, ds, f);
3628                                 cur.leaveInset(*inset);
3629                         }
3630                         return true;
3631                 }
3632         }
3633
3634         bool gotsel = false;
3635         bool move_layout = false;
3636         if (cur.selection()) {
3637                 if (cmd.action() == LFUN_INDEX_INSERT)
3638                         copySelectionToTemp(cur);
3639                 else {
3640                         cutSelectionToTemp(cur, pastesel);
3641                         /* Move layout information inside the inset if the whole
3642                          * paragraph and the inset allows setting layout
3643                          * FIXME: this does not work as expected when change tracking is on
3644                          *   However, we do not really know what to do in this case.
3645                          * FIXME: figure out a good test in the environment case (see #12251).
3646                          */
3647                         if (cur.paragraph().layout().isCommand()
3648                              && cur.paragraph().empty()
3649                              && !inset->forcePlainLayout()) {
3650                                 cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3651                                 move_layout = true;
3652                         }
3653                 }
3654                 cur.clearSelection();
3655                 gotsel = true;
3656         } else if (cmd.action() == LFUN_INDEX_INSERT) {
3657                 gotsel = text->selectWordWhenUnderCursor(cur, WHOLE_WORD);
3658                 copySelectionToTemp(cur);
3659                 cur.clearSelection();
3660         }
3661         text->insertInset(cur, inset);
3662
3663         InsetText * inset_text = inset->asInsetText();
3664         if (inset_text) {
3665                 Font const & font = inset->inheritFont()
3666                         ? cur.bv().textMetrics(text).displayFont(cur.pit(), cur.pos())
3667                         : bparams.getFont();
3668                 inset_text->setOuterFont(cur.bv(), font.fontInfo());
3669         }
3670
3671         if (cmd.action() == LFUN_ARGUMENT_INSERT) {
3672                 InsetArgument * const ia = static_cast<InsetArgument *>(inset);
3673                 ia->init(cur.paragraph());
3674         }
3675
3676         if (edit)
3677                 inset->edit(cur, true);
3678
3679         if (!gotsel || !pastesel)
3680                 return true;
3681
3682         pasteFromTemp(cur, cur.buffer()->errorList("Paste"));
3683         cur.buffer()->errors("Paste");
3684         cur.clearSelection(); // bug 393
3685         cur.finishUndo();
3686         if (inset_text) {
3687                 if (resetfont) {
3688                         // Reset of font (not language) is requested.
3689                         // Used by InsetIndex (#11961).
3690                         Language const * lang = cur.getFont().language();
3691                         Font font(bparams.getFont().fontInfo(), lang);
3692                         cur.paragraph().resetFonts(font);
3693                 }
3694                 inset_text->fixParagraphsFont();
3695                 cur.pos() = 0;
3696                 cur.pit() = 0;
3697                 /* If the containing paragraph has kept its layout, reset the
3698                  * layout of the first paragraph of the inset.
3699                  */
3700                 if (!move_layout)
3701                         cur.paragraph().setPlainOrDefaultLayout(bparams.documentClass());
3702                 // FIXME: what does this do?
3703                 if (cmd.action() == LFUN_FLEX_INSERT)
3704                         return true;
3705                 Cursor old = cur;
3706                 cur.leaveInset(*inset);
3707                 if (cmd.action() == LFUN_PREVIEW_INSERT
3708                         || cmd.action() == LFUN_IPA_INSERT)
3709                         // trigger preview
3710                         notifyCursorLeavesOrEnters(old, cur);
3711         } else {
3712                 cur.leaveInset(*inset);
3713                 // reset surrounding par to default
3714                 DocumentClass const & dc = bparams.documentClass();
3715                 docstring const layoutname = inset->usePlainLayout()
3716                         ? dc.plainLayoutName()
3717                         : dc.defaultLayoutName();
3718                 text->setLayout(cur, layoutname);
3719         }
3720         return true;
3721 }
3722
3723
3724 /// the type of outline operation
3725 enum OutlineOp {
3726         OutlineUp, // Move this header with text down
3727         OutlineDown,   // Move this header with text up
3728         OutlineIn, // Make this header deeper
3729         OutlineOut // Make this header shallower
3730 };
3731
3732
3733 void insertSeparator(Cursor const & cur, depth_type const depth)
3734 {
3735         Buffer & buf = *cur.buffer();
3736         lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
3737         DocumentClass const & tc = buf.params().documentClass();
3738         lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
3739                                   + from_ascii("\" ignoreautonests")));
3740         // FIXME: Bibitem mess!
3741         if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
3742                 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
3743         lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
3744         while (cur.paragraph().params().depth() > depth)
3745                 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
3746 }
3747
3748
3749 void outline(OutlineOp mode, Cursor & cur)
3750 {
3751         Buffer & buf = *cur.buffer();
3752         Text & text = *cur.text();
3753         pit_type & pit = cur.pit();
3754         ParagraphList & pars = text.paragraphs();
3755         ParagraphList::iterator const bgn = pars.begin();
3756         // The first paragraph of the area to be copied:
3757         ParagraphList::iterator start = pars.iterator_at(pit);
3758         // The final paragraph of area to be copied:
3759         ParagraphList::iterator finish = start;
3760         ParagraphList::iterator const end = pars.end();
3761         depth_type const current_depth = cur.paragraph().params().depth();
3762
3763         int const thistoclevel = text.getTocLevel(distance(bgn, start));
3764         int toclevel;
3765
3766         // Move out (down) from this section header
3767         if (finish != end)
3768                 ++finish;
3769
3770         // Seek the one (on same level) below
3771         for (; finish != end; ++finish) {
3772                 toclevel = text.getTocLevel(distance(bgn, finish));
3773                 if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
3774                         break;
3775         }
3776
3777         switch (mode) {
3778                 case OutlineUp: {
3779                         if (start == pars.begin())
3780                                 // Nothing to move.
3781                                 return;
3782                         ParagraphList::iterator dest = start;
3783                         // Move out (up) from this header
3784                         if (dest == bgn)
3785                                 return;
3786                         // Search previous same-level header above
3787                         do {
3788                                 --dest;
3789                                 toclevel = text.getTocLevel(distance(bgn, dest));
3790                         } while(dest != bgn
3791                                 && (toclevel == Layout::NOT_IN_TOC
3792                                     || toclevel > thistoclevel));
3793                         // Not found; do nothing
3794                         if (toclevel == Layout::NOT_IN_TOC || toclevel > thistoclevel)
3795                                 return;
3796                         pit_type newpit = distance(bgn, dest);
3797                         pit_type const len = distance(start, finish);
3798                         pit_type const deletepit = pit + len;
3799                         buf.undo().recordUndo(cur, newpit, deletepit - 1);
3800                         // If we move an environment upwards, make sure it is
3801                         // separated from its new neighbour below:
3802                         // If an environment of the same layout follows, and the moved
3803                         // paragraph sequence does not end with a separator, insert one.
3804                         ParagraphList::iterator lastmoved = finish;
3805                         --lastmoved;
3806                         if (start->layout().isEnvironment()
3807                             && dest->layout() == start->layout()
3808                             && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3809                                 cur.pit() = distance(bgn, lastmoved);
3810                                 cur.pos() = cur.lastpos();
3811                                 insertSeparator(cur, current_depth);
3812                                 cur.pit() = pit;
3813                         }
3814                         // Likewise, if we moved an environment upwards, make sure it
3815                         // is separated from its new neighbour above.
3816                         // The paragraph before the target of movement
3817                         if (dest != bgn) {
3818                                 ParagraphList::iterator before = dest;
3819                                 --before;
3820                                 // Get the parent paragraph (outer in nested context)
3821                                 pit_type const parent =
3822                                         before->params().depth() > current_depth
3823                                                 ? text.depthHook(distance(bgn, before), current_depth)
3824                                                 : distance(bgn, before);
3825                                 // If a environment with same layout preceeds the moved one in the new
3826                                 // position, and there is no separator yet, insert one.
3827                                 if (start->layout().isEnvironment()
3828                                     && pars[parent].layout() == start->layout()
3829                                     && !before->isEnvSeparator(before->beginOfBody())) {
3830                                         cur.pit() = distance(bgn, before);
3831                                         cur.pos() = cur.lastpos();
3832                                         insertSeparator(cur, current_depth);
3833                                         cur.pit() = pit;
3834                                 }
3835                         }
3836                         newpit = distance(bgn, dest);
3837                         pars.splice(dest, start, finish);
3838                         cur.pit() = newpit;
3839                         break;
3840                 }
3841                 case OutlineDown: {
3842                         if (finish == end)
3843                                 // Nothing to move.
3844                                 return;
3845                         // Go one down from *this* header:
3846                         ParagraphList::iterator dest = next(finish, 1);
3847                         // Go further down to find header to insert in front of:
3848                         for (; dest != end; ++dest) {
3849                                 toclevel = text.getTocLevel(distance(bgn, dest));
3850                                 if (toclevel != Layout::NOT_IN_TOC
3851                                       && toclevel <= thistoclevel)
3852                                         break;
3853                         }
3854                         // One such was found, so go on...
3855                         // If we move an environment downwards, make sure it is
3856                         // separated from its new neighbour above.
3857                         pit_type newpit = distance(bgn, dest);
3858                         buf.undo().recordUndo(cur, pit, newpit - 1);
3859                         // The paragraph before the target of movement
3860                         ParagraphList::iterator before = dest;
3861                         --before;
3862                         // Get the parent paragraph (outer in nested context)
3863                         pit_type const parent =
3864                                 before->params().depth() > current_depth
3865                                         ? text.depthHook(distance(bgn, before), current_depth)
3866                                         : distance(bgn, before);
3867                         // If a environment with same layout preceeds the moved one in the new
3868                         // position, and there is no separator yet, insert one.
3869                         if (start->layout().isEnvironment()
3870                             && pars[parent].layout() == start->layout()
3871                             && !before->isEnvSeparator(before->beginOfBody())) {
3872                                 cur.pit() = distance(bgn, before);
3873                                 cur.pos() = cur.lastpos();
3874                                 insertSeparator(cur, current_depth);
3875                                 cur.pit() = pit;
3876                         }
3877                         // Likewise, make sure moved environments are separated
3878                         // from their new neighbour below:
3879                         // If an environment of the same layout follows, and the moved
3880                         // paragraph sequence does not end with a separator, insert one.
3881                         ParagraphList::iterator lastmoved = finish;
3882                         --lastmoved;
3883                         if (dest != end
3884                             && start->layout().isEnvironment()
3885                             && dest->layout() == start->layout()
3886                             && !lastmoved->isEnvSeparator(lastmoved->beginOfBody())) {
3887                                 cur.pit() = distance(bgn, lastmoved);
3888                                 cur.pos() = cur.lastpos();
3889                                 insertSeparator(cur, current_depth);
3890                                 cur.pit() = pit;
3891                         }
3892                         newpit = distance(bgn, dest);
3893                         pit_type const len = distance(start, finish);
3894                         pars.splice(dest, start, finish);
3895                         cur.pit() = newpit - len;
3896                         break;
3897                 }
3898                 case OutlineIn:
3899                 case OutlineOut: {
3900                         // We first iterate without actually doing something
3901                         // in order to check whether the action flattens the structure.
3902                         // If so, warn (#11178).
3903                         ParagraphList::iterator cstart = start;
3904                         bool strucchange = false;
3905                         for (; cstart != finish; ++cstart) {
3906                                 toclevel = text.getTocLevel(distance(bgn, cstart));
3907                                 if (toclevel == Layout::NOT_IN_TOC)
3908                                         continue;
3909
3910                                 DocumentClass const & tc = buf.params().documentClass();
3911                                 int const newtoclevel =
3912                                         (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3913
3914                                 bool found = false;
3915                                 for (auto const & lay : tc) {
3916                                         if (lay.toclevel == newtoclevel
3917                                             && lay.isNumHeadingLabelType()
3918                                             && cstart->layout().isNumHeadingLabelType()) {
3919                                                 found = true;
3920                                                 break;
3921                                         }
3922                                 }
3923                                 if (!found) {
3924                                         strucchange = true;
3925                                         break;
3926                                 }
3927                         }
3928                         if (strucchange
3929                             && frontend::Alert::prompt(_("Action flattens document structure"),
3930                                                        _("This action will cause some headings that have been "
3931                                                          "on different level before to be on the same level "
3932                                                          "since there is no more lower or higher heading level. "
3933                                                          "Continue still?"),
3934                                                        1, 1,
3935                                                        _("&Yes, continue nonetheless"),
3936                                                        _("&No, quit operation")) == 1)
3937                                 break;
3938
3939                         pit_type const len = distance(start, finish);
3940                         buf.undo().recordUndo(cur, pit, pit + len - 1);
3941                         for (; start != finish; ++start) {
3942                                 toclevel = text.getTocLevel(distance(bgn, start));
3943                                 if (toclevel == Layout::NOT_IN_TOC)
3944                                         continue;
3945
3946                                 DocumentClass const & tc = buf.params().documentClass();
3947                                 int const newtoclevel =
3948                                         (mode == OutlineIn ? toclevel + 1 : toclevel - 1);
3949
3950                                 for (auto const & lay : tc) {
3951                                         if (lay.toclevel == newtoclevel
3952                                             && lay.isNumHeadingLabelType()
3953                                             && start->layout().isNumHeadingLabelType()) {
3954                                                 start->setLayout(lay);
3955                                                 break;
3956                                         }
3957                                 }
3958                         }
3959                         break;
3960                 }
3961         }
3962 }
3963
3964
3965 } // namespace
3966
3967
3968 void Text::number(Cursor & cur)
3969 {
3970         FontInfo font = ignore_font;
3971         font.setNumber(FONT_TOGGLE);
3972         toggleAndShow(cur, this, Font(font, ignore_language));
3973 }
3974
3975
3976 bool Text::isRTL(pit_type const pit) const
3977 {
3978         Buffer const & buffer = owner_->buffer();
3979         return pars_[pit].isRTL(buffer.params());
3980 }
3981
3982
3983 namespace {
3984
3985 Language const * getLanguage(Cursor const & cur, string const & lang)
3986 {
3987         return lang.empty() ? cur.getFont().language() : languages.getLanguage(lang);
3988 }
3989
3990
3991 docstring resolveLayout(docstring layout, DocIterator const & dit)
3992 {
3993         Paragraph const & par = dit.paragraph();
3994         DocumentClass const & tclass = dit.buffer()->params().documentClass();
3995
3996         if (layout.empty())
3997                 layout = tclass.defaultLayoutName();
3998
3999         if (dit.inset().forcePlainLayout(dit.idx()))
4000                 // in this case only the empty layout is allowed
4001                 layout = tclass.plainLayoutName();
4002         else if (par.usePlainLayout()) {
4003                 // in this case, default layout maps to empty layout
4004                 if (layout == tclass.defaultLayoutName())
4005                         layout = tclass.plainLayoutName();
4006         } else {
4007                 // otherwise, the empty layout maps to the default
4008                 if (layout == tclass.plainLayoutName())
4009                         layout = tclass.defaultLayoutName();
4010         }
4011
4012         // If the entry is obsolete, use the new one instead.
4013         if (tclass.hasLayout(layout)) {
4014                 docstring const & obs = tclass[layout].obsoleted_by();
4015                 if (!obs.empty())
4016                         layout = obs;
4017         }
4018         if (!tclass.hasLayout(layout))
4019                 layout.clear();
4020         return layout;
4021 }
4022
4023
4024 bool isAlreadyLayout(docstring const & layout, CursorData const & cur)
4025 {
4026         ParagraphList const & pars = cur.text()->paragraphs();
4027
4028         pit_type pit = cur.selBegin().pit();
4029         pit_type const epit = cur.selEnd().pit() + 1;
4030         for ( ; pit != epit; ++pit)
4031                 if (pars[pit].layout().name() != layout)
4032                         return false;
4033
4034         return true;
4035 }
4036
4037
4038 } // namespace
4039
4040
4041 void Text::dispatch(Cursor & cur, FuncRequest & cmd)
4042 {
4043         LYXERR(Debug::ACTION, "Text::dispatch: cmd: " << cmd);
4044
4045         // Dispatch if the cursor is inside the text. It is not the
4046         // case for context menus (bug 5797).
4047         if (cur.text() != this) {
4048                 cur.undispatched();
4049                 return;
4050         }
4051
4052         BufferView * bv = &cur.bv();
4053         TextMetrics * tm = &bv->textMetrics(this);
4054         if (!tm->contains(cur.pit())) {
4055                 lyx::dispatch(FuncRequest(LFUN_SCREEN_SHOW_CURSOR));
4056                 tm = &bv->textMetrics(this);
4057         }
4058
4059         // FIXME: We use the update flag to indicates wether a singlePar or a
4060         // full screen update is needed. We reset it here but shall we restore it
4061         // at the end?
4062         cur.noScreenUpdate();
4063
4064         LBUFERR(this == cur.text());
4065
4066         // NOTE: This should NOT be a reference. See commit 94a5481a.
4067         CursorSlice const oldTopSlice = cur.top();
4068         bool const oldBoundary = cur.boundary();
4069         bool const oldSelection = cur.selection();
4070         // Signals that, even if needsUpdate == false, an update of the
4071         // cursor paragraph is required
4072         bool singleParUpdate = lyxaction.funcHasFlag(cmd.action(),
4073                 LyXAction::SingleParUpdate);
4074         // Signals that a full-screen update is required
4075         bool needsUpdate = !(lyxaction.funcHasFlag(cmd.action(),
4076                 LyXAction::NoUpdate) || singleParUpdate);
4077         bool const last_misspelled = lyxrc.spellcheck_continuously
4078                 && cur.paragraph().isMisspelled(cur.pos(), true);
4079
4080         FuncCode const act = cmd.action();
4081         switch (act) {
4082
4083         case LFUN_PARAGRAPH_MOVE_DOWN: {
4084                 pit_type const pit = cur.pit();
4085                 cur.recordUndo(pit, pit + 1);
4086                 pars_.swap(pit, pit + 1);
4087                 needsUpdate = true;
4088                 cur.forceBufferUpdate();
4089                 ++cur.pit();
4090                 break;
4091         }
4092
4093         case LFUN_PARAGRAPH_MOVE_UP: {
4094                 pit_type const pit = cur.pit();
4095                 cur.recordUndo(pit - 1, pit);
4096                 cur.finishUndo();
4097                 pars_.swap(pit, pit - 1);
4098                 --cur.pit();
4099                 needsUpdate = true;
4100                 cur.forceBufferUpdate();
4101                 break;
4102         }
4103
4104         case LFUN_APPENDIX: {
4105                 Paragraph & par = cur.paragraph();
4106                 bool start = !par.params().startOfAppendix();
4107
4108 // FIXME: The code below only makes sense at top level.
4109 // Should LFUN_APPENDIX be restricted to top-level paragraphs?
4110                 // ensure that we have only one start_of_appendix in this document
4111                 // FIXME: this don't work for multipart document!
4112                 for (pit_type tmp = 0, end = pars_.size(); tmp != end; ++tmp) {
4113                         if (pars_[tmp].params().startOfAppendix()) {
4114                                 cur.recordUndo(tmp, tmp);
4115                                 pars_[tmp].params().startOfAppendix(false);
4116                                 break;
4117                         }
4118                 }
4119
4120                 cur.recordUndo();
4121                 par.params().startOfAppendix(start);
4122
4123                 // we can set the refreshing parameters now
4124                 cur.forceBufferUpdate();
4125                 break;
4126         }
4127
4128         case LFUN_WORD_DELETE_FORWARD:
4129                 if (cur.selection())
4130                         cutSelection(cur, false);
4131                 else
4132                         deleteWordForward(cur, cmd.getArg(0) != "confirm");
4133                 finishChange(cur, false);
4134                 break;
4135
4136         case LFUN_WORD_DELETE_BACKWARD:
4137                 if (cur.selection())
4138                         cutSelection(cur, false);
4139                 else
4140                         deleteWordBackward(cur, cmd.getArg(0) != "confirm");
4141                 finishChange(cur, false);
4142                 break;
4143
4144         case LFUN_LINE_DELETE_FORWARD:
4145                 if (cur.selection())
4146                         cutSelection(cur, false);
4147                 else
4148                         tm->deleteLineForward(cur);
4149                 finishChange(cur, false);
4150                 break;
4151
4152         case LFUN_BUFFER_BEGIN:
4153         case LFUN_BUFFER_BEGIN_SELECT:
4154                 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_BEGIN_SELECT);
4155                 if (cur.depth() == 1)
4156                         needsUpdate |= cursorTop(cur);
4157                 else
4158                         cur.undispatched();
4159                 cur.screenUpdateFlags(Update::FitCursor);
4160                 break;
4161
4162         case LFUN_BUFFER_END:
4163         case LFUN_BUFFER_END_SELECT:
4164                 needsUpdate |= cur.selHandle(act == LFUN_BUFFER_END_SELECT);
4165                 if (cur.depth() == 1)
4166                         needsUpdate |= cursorBottom(cur);
4167                 else
4168                         cur.undispatched();
4169                 cur.screenUpdateFlags(Update::FitCursor);
4170                 break;
4171
4172         case LFUN_INSET_BEGIN:
4173         case LFUN_INSET_BEGIN_SELECT:
4174                 needsUpdate |= cur.selHandle(act == LFUN_INSET_BEGIN_SELECT);
4175                 if (cur.depth() == 1 || !cur.top().at_begin())
4176                         needsUpdate |= cursorTop(cur);
4177                 else
4178                         cur.undispatched();
4179                 cur.screenUpdateFlags(Update::FitCursor);
4180                 break;
4181
4182         case LFUN_INSET_END:
4183         case LFUN_INSET_END_SELECT:
4184                 needsUpdate |= cur.selHandle(act == LFUN_INSET_END_SELECT);
4185                 if (cur.depth() == 1 || !cur.top().at_end())
4186                         needsUpdate |= cursorBottom(cur);
4187                 else
4188                         cur.undispatched();
4189                 cur.screenUpdateFlags(Update::FitCursor);
4190                 break;
4191
4192         case LFUN_CHAR_FORWARD:
4193         case LFUN_CHAR_FORWARD_SELECT: {
4194                 //LYXERR0(" LFUN_CHAR_FORWARD[SEL]:\n" << cur);
4195                 needsUpdate |= cur.selHandle(act == LFUN_CHAR_FORWARD_SELECT);
4196                 bool const cur_moved = cursorForward(cur);
4197                 needsUpdate |= cur_moved;
4198
4199                 if (!cur_moved && cur.depth() > 1
4200                      && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4201                         cur.undispatched();
4202                         cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4203
4204                         // we will be moving out the inset, so we should execute
4205                         // the depm-mechanism.
4206                         // The cursor hasn't changed yet. To give the DEPM the
4207                         // possibility of doing something we must provide it with
4208                         // two different cursors.
4209                         Cursor dummy = cur;
4210                         dummy.pos() = dummy.pit() = 0;
4211                         if (cur.bv().checkDepm(dummy, cur))
4212                                 cur.forceBufferUpdate();
4213                 }
4214                 break;
4215         }
4216
4217         case LFUN_CHAR_BACKWARD:
4218         case LFUN_CHAR_BACKWARD_SELECT: {
4219                 //lyxerr << "handle LFUN_CHAR_BACKWARD[_SELECT]:\n" << cur << endl;
4220                 needsUpdate |= cur.selHandle(act == LFUN_CHAR_BACKWARD_SELECT);
4221                 bool const cur_moved = cursorBackward(cur);
4222                 needsUpdate |= cur_moved;
4223
4224                 if (!cur_moved && cur.depth() > 1
4225                      && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4226                         cur.undispatched();
4227                         cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4228
4229                         // we will be moving out the inset, so we should execute
4230                         // the depm-mechanism.
4231                         // The cursor hasn't changed yet. To give the DEPM the
4232                         // possibility of doing something we must provide it with
4233                         // two different cursors.
4234                         Cursor dummy = cur;
4235                         dummy.pos() = cur.lastpos();
4236                         dummy.pit() = cur.lastpit();
4237                         if (cur.bv().checkDepm(dummy, cur))
4238                                 cur.forceBufferUpdate();
4239                 }
4240                 break;
4241         }
4242
4243         case LFUN_CHAR_LEFT:
4244         case LFUN_CHAR_LEFT_SELECT:
4245                 if (lyxrc.visual_cursor) {
4246                         needsUpdate |= cur.selHandle(act == LFUN_CHAR_LEFT_SELECT);
4247                         bool const cur_moved = cursorVisLeft(cur);
4248                         needsUpdate |= cur_moved;
4249                         if (!cur_moved && cur.depth() > 1
4250                              && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4251                                 cur.undispatched();
4252                                 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4253                         }
4254                 } else {
4255                         if (cur.reverseDirectionNeeded()) {
4256                                 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4257                                         LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4258                         } else {
4259                                 cmd.setAction(cmd.action() == LFUN_CHAR_LEFT_SELECT ?
4260                                         LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4261                         }
4262                         dispatch(cur, cmd);
4263                         return;
4264                 }
4265                 break;
4266
4267         case LFUN_CHAR_RIGHT:
4268         case LFUN_CHAR_RIGHT_SELECT:
4269                 if (lyxrc.visual_cursor) {
4270                         needsUpdate |= cur.selHandle(cmd.action() == LFUN_CHAR_RIGHT_SELECT);
4271                         bool const cur_moved = cursorVisRight(cur);
4272                         needsUpdate |= cur_moved;
4273                         if (!cur_moved && cur.depth() > 1
4274                              && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4275                                 cur.undispatched();
4276                                 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4277                         }
4278                 } else {
4279                         if (cur.reverseDirectionNeeded()) {
4280                                 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4281                                         LFUN_CHAR_BACKWARD_SELECT : LFUN_CHAR_BACKWARD);
4282                         } else {
4283                                 cmd.setAction(cmd.action() == LFUN_CHAR_RIGHT_SELECT ?
4284                                         LFUN_CHAR_FORWARD_SELECT : LFUN_CHAR_FORWARD);
4285                         }
4286                         dispatch(cur, cmd);
4287                         return;
4288                 }
4289                 break;
4290
4291
4292         case LFUN_UP_SELECT:
4293         case LFUN_DOWN_SELECT:
4294         case LFUN_UP:
4295         case LFUN_DOWN: {
4296                 // stop/start the selection
4297                 bool select = cmd.action() == LFUN_DOWN_SELECT ||
4298                         cmd.action() == LFUN_UP_SELECT;
4299
4300                 // move cursor up/down
4301                 bool up = cmd.action() == LFUN_UP_SELECT || cmd.action() == LFUN_UP;
4302                 bool const atFirstOrLastRow = cur.atFirstOrLastRow(up);
4303
4304                 if (!atFirstOrLastRow) {
4305                         needsUpdate |= cur.selHandle(select);
4306                         cur.upDownInText(up, needsUpdate);
4307                         needsUpdate |= cur.beforeDispatchCursor().inMathed();
4308                 } else {
4309                         pos_type newpos = up ? 0 : cur.lastpos();
4310                         if (lyxrc.mac_like_cursor_movement && cur.pos() != newpos) {
4311                                 needsUpdate |= cur.selHandle(select);
4312                                 // we do not reset the targetx of the cursor
4313                                 cur.pos() = newpos;
4314                                 needsUpdate |= bv->checkDepm(cur, bv->cursor());
4315                                 cur.updateTextTargetOffset();
4316                                 if (needsUpdate)
4317                                         cur.forceBufferUpdate();
4318                                 break;
4319                         }
4320
4321                         // if the cursor cannot be moved up or down do not remove
4322                         // the selection right now, but wait for the next dispatch.
4323                         if (select)
4324                                 needsUpdate |= cur.selHandle(select);
4325                         cur.upDownInText(up, needsUpdate);
4326                         cur.undispatched();
4327                 }
4328
4329                 break;
4330         }
4331
4332         case LFUN_PARAGRAPH_SELECT:
4333                 if (cur.pos() > 0)
4334                         needsUpdate |= setCursor(cur, cur.pit(), 0);
4335                 needsUpdate |= cur.selHandle(true);
4336                 if (cur.pos() < cur.lastpos())
4337                         needsUpdate |= setCursor(cur, cur.pit(), cur.lastpos());
4338                 break;
4339
4340         case LFUN_PARAGRAPH_UP:
4341         case LFUN_PARAGRAPH_UP_SELECT:
4342                 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_UP_SELECT);
4343                 needsUpdate |= cursorUpParagraph(cur);
4344                 break;
4345
4346         case LFUN_PARAGRAPH_DOWN:
4347         case LFUN_PARAGRAPH_DOWN_SELECT:
4348                 needsUpdate |= cur.selHandle(cmd.action() == LFUN_PARAGRAPH_DOWN_SELECT);
4349                 needsUpdate |= cursorDownParagraph(cur);
4350                 break;
4351
4352         case LFUN_LINE_BEGIN:
4353         case LFUN_LINE_BEGIN_SELECT:
4354                 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_BEGIN_SELECT);
4355                 needsUpdate |= tm->cursorHome(cur);
4356                 break;
4357
4358         case LFUN_LINE_END:
4359         case LFUN_LINE_END_SELECT:
4360                 needsUpdate |= cur.selHandle(cmd.action() == LFUN_LINE_END_SELECT);
4361                 needsUpdate |= tm->cursorEnd(cur);
4362                 break;
4363
4364         case LFUN_SECTION_SELECT: {
4365                 Buffer const & buf = *cur.buffer();
4366                 pit_type const pit = cur.pit();
4367                 ParagraphList & pars = buf.text().paragraphs();
4368                 ParagraphList::iterator bgn = pars.begin();
4369                 // The first paragraph of the area to be selected:
4370                 ParagraphList::iterator start = pars.iterator_at(pit);
4371                 // The final paragraph of area to be selected:
4372                 ParagraphList::iterator finish = start;
4373                 ParagraphList::iterator end = pars.end();
4374
4375                 int const thistoclevel = buf.text().getTocLevel(distance(bgn, start));
4376                 if (thistoclevel == Layout::NOT_IN_TOC)
4377                         break;
4378
4379                 cur.pos() = 0;
4380                 Cursor const old_cur = cur;
4381                 needsUpdate |= cur.selHandle(true);
4382
4383                 // Move out (down) from this section header
4384                 if (finish != end)
4385                         ++finish;
4386
4387                 // Seek the one (on same level) below
4388                 for (; finish != end; ++finish, ++cur.pit()) {
4389                         int const toclevel = buf.text().getTocLevel(distance(bgn, finish));
4390                         if (toclevel != Layout::NOT_IN_TOC && toclevel <= thistoclevel)
4391                                 break;
4392                 }
4393                 cur.pos() = cur.lastpos();
4394                 cur.boundary(false);
4395                 cur.setCurrentFont();
4396
4397                 needsUpdate |= cur != old_cur;
4398                 break;
4399         }
4400
4401         case LFUN_WORD_RIGHT:
4402         case LFUN_WORD_RIGHT_SELECT:
4403                 if (lyxrc.visual_cursor) {
4404                         needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_RIGHT_SELECT);
4405                         bool const cur_moved = cursorVisRightOneWord(cur);
4406                         needsUpdate |= cur_moved;
4407                         if (!cur_moved && cur.depth() > 1
4408                              && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4409                                 cur.undispatched();
4410                                 cmd = FuncRequest(LFUN_FINISHED_RIGHT);
4411                         }
4412                 } else {
4413                         if (cur.reverseDirectionNeeded()) {
4414                                 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4415                                                 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4416                         } else {
4417                                 cmd.setAction(cmd.action() == LFUN_WORD_RIGHT_SELECT ?
4418                                                 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4419                         }
4420                         dispatch(cur, cmd);
4421                         return;
4422                 }
4423                 break;
4424
4425         case LFUN_WORD_FORWARD:
4426         case LFUN_WORD_FORWARD_SELECT: {
4427                 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_FORWARD_SELECT);
4428                 bool const cur_moved = cursorForwardOneWord(cur);
4429                 needsUpdate |= cur_moved;
4430
4431                 if (!cur_moved && cur.depth() > 1
4432                      && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4433                         cur.undispatched();
4434                         cmd = FuncRequest(LFUN_FINISHED_FORWARD);
4435
4436                         // we will be moving out the inset, so we should execute
4437                         // the depm-mechanism.
4438                         // The cursor hasn't changed yet. To give the DEPM the
4439                         // possibility of doing something we must provide it with
4440                         // two different cursors.
4441                         Cursor dummy = cur;
4442                         dummy.pos() = dummy.pit() = 0;
4443                         if (cur.bv().checkDepm(dummy, cur))
4444                                 cur.forceBufferUpdate();
4445                 }
4446                 break;
4447         }
4448
4449         case LFUN_WORD_LEFT:
4450         case LFUN_WORD_LEFT_SELECT:
4451                 if (lyxrc.visual_cursor) {
4452                         needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_LEFT_SELECT);
4453                         bool const cur_moved = cursorVisLeftOneWord(cur);
4454                         needsUpdate |= cur_moved;
4455                         if (!cur_moved && cur.depth() > 1
4456                              && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4457                                 cur.undispatched();
4458                                 cmd = FuncRequest(LFUN_FINISHED_LEFT);
4459                         }
4460                 } else {
4461                         if (cur.reverseDirectionNeeded()) {
4462                                 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4463                                                 LFUN_WORD_FORWARD_SELECT : LFUN_WORD_FORWARD);
4464                         } else {
4465                                 cmd.setAction(cmd.action() == LFUN_WORD_LEFT_SELECT ?
4466                                                 LFUN_WORD_BACKWARD_SELECT : LFUN_WORD_BACKWARD);
4467                         }
4468                         dispatch(cur, cmd);
4469                         return;
4470                 }
4471                 break;
4472
4473         case LFUN_WORD_BACKWARD:
4474         case LFUN_WORD_BACKWARD_SELECT: {
4475                 needsUpdate |= cur.selHandle(cmd.action() == LFUN_WORD_BACKWARD_SELECT);
4476                 bool const cur_moved = cursorBackwardOneWord(cur);
4477                 needsUpdate |= cur_moved;
4478
4479                 if (!cur_moved && cur.depth() > 1
4480                      && oldTopSlice == cur.top() && cur.boundary() == oldBoundary) {
4481                         cur.undispatched();
4482                         cmd = FuncRequest(LFUN_FINISHED_BACKWARD);
4483
4484                         // we will be moving out the inset, so we should execute
4485                         // the depm-mechanism.
4486                         // The cursor hasn't changed yet. To give the DEPM the
4487                         // possibility of doing something we must provide it with
4488                         // two different cursors.
4489                         Cursor dummy = cur;
4490                         dummy.pos() = cur.lastpos();
4491                         dummy.pit() = cur.lastpit();
4492                         if (cur.bv().checkDepm(dummy, cur))
4493                                 cur.forceBufferUpdate();
4494                 }
4495                 break;
4496         }
4497
4498         case LFUN_WORD_SELECT: {
4499                 selectWord(cur, WHOLE_WORD);
4500                 finishChange(cur, true);
4501                 break;
4502         }
4503
4504         case LFUN_NEWLINE_INSERT: {
4505                 InsetNewlineParams inp;
4506                 docstring const & arg = cmd.argument();
4507                 if (arg == "linebreak")
4508                         inp.kind = InsetNewlineParams::LINEBREAK;
4509                 else
4510                         inp.kind = InsetNewlineParams::NEWLINE;
4511                 cap::replaceSelection(cur);
4512                 cur.recordUndo();
4513                 cur.insert(new InsetNewline(inp));
4514                 cur.posForward();
4515                 moveCursor(cur, false);
4516                 break;
4517         }
4518
4519         case LFUN_TAB_INSERT: {
4520                 bool const multi_par_selection = cur.selection() &&
4521                         cur.selBegin().pit() != cur.selEnd().pit();
4522                 if (multi_par_selection) {
4523                         // If there is a multi-paragraph selection, a tab is inserted
4524                         // at the beginning of each paragraph.
4525                         cur.recordUndoSelection();
4526                         pit_type const pit_end = cur.selEnd().pit();
4527                         for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4528                                 pars_[pit].insertChar(0, '\t',
4529                                                       bv->buffer().params().track_changes);
4530                                 // Update the selection pos to make sure the selection does not
4531                                 // change as the inserted tab will increase the logical pos.
4532                                 if (cur.realAnchor().pit() == pit)
4533                                         cur.realAnchor().forwardPos();
4534                                 if (cur.pit() == pit)
4535                                         cur.forwardPos();
4536                         }
4537                         cur.finishUndo();
4538                 } else {
4539                         // Maybe we shouldn't allow tabs within a line, because they
4540                         // are not (yet) aligned as one might do expect.
4541                         FuncRequest ncmd(LFUN_SELF_INSERT, from_ascii("\t"));
4542                         dispatch(cur, ncmd);
4543                 }
4544                 break;
4545         }
4546
4547         case LFUN_TAB_DELETE: {
4548                 bool const tc = bv->buffer().params().track_changes;
4549                 if (cur.selection()) {
4550                         // If there is a selection, a tab (if present) is removed from
4551                         // the beginning of each paragraph.
4552                         cur.recordUndoSelection();
4553                         pit_type const pit_end = cur.selEnd().pit();
4554                         for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
4555                                 Paragraph & par = paragraphs()[pit];
4556                                 if (par.empty())
4557                                         continue;
4558                                 char_type const c = par.getChar(0);
4559                                 if (c == '\t' || c == ' ') {
4560                                         // remove either 1 tab or 4 spaces.
4561                                         int const n = (c == ' ' ? 4 : 1);
4562                                         for (int i = 0; i < n
4563                                                   && !par.empty() && par.getChar(0) == c; ++i) {
4564                                                 if (cur.pit() == pit)
4565                                                         cur.posBackward();
4566                                                 if (cur.realAnchor().pit() == pit
4567                                                           && cur.realAnchor().pos() > 0 )
4568                                                         cur.realAnchor().backwardPos();
4569                                                 par.eraseChar(0, tc);
4570                                         }
4571                                 }
4572                         }
4573                         cur.finishUndo();
4574                 } else {
4575                         // If there is no selection, try to remove a tab or some spaces
4576                         // before the position of the cursor.
4577                         Paragraph & par = paragraphs()[cur.pit()];
4578                         pos_type const pos = cur.pos();
4579
4580                         if (pos == 0)
4581                                 break;
4582
4583                         char_type const c = par.getChar(pos - 1);
4584                         cur.recordUndo();
4585                         if (c == '\t') {
4586                                 cur.posBackward();
4587                                 par.eraseChar(cur.pos(), tc);
4588                         } else
4589                                 for (int n_spaces = 0;
4590                                      cur.pos() > 0
4591                                              && par.getChar(cur.pos() - 1) == ' '
4592                                              && n_spaces < 4;
4593                                      ++n_spaces) {
4594                                         cur.posBackward();
4595                                         par.eraseChar(cur.pos(), tc);
4596                                 }
4597                         cur.finishUndo();
4598                 }
4599                 break;
4600         }
4601
4602         case LFUN_CHAR_DELETE_FORWARD:
4603                 if (!cur.selection()) {
4604                         if (cur.pos() == cur.paragraph().size())
4605                                 // Par boundary, force full-screen update
4606                                 singleParUpdate = false;
4607                         else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion()) {
4608                                 cur.resetAnchor();
4609                                 cur.selection(true);
4610                                 cur.posForward();
4611                                 cur.setSelection();
4612                                 break;
4613                         }
4614                         needsUpdate |= erase(cur);
4615                         cur.resetAnchor();
4616                 } else {
4617                         cutSelection(cur, false);
4618                         cur.setCurrentFont();
4619                         singleParUpdate = false;
4620                 }
4621                 moveCursor(cur, false);
4622                 break;
4623
4624         case LFUN_CHAR_DELETE_BACKWARD:
4625                 if (!cur.selection()) {
4626                         if (bv->getIntl().getTransManager().backspace()) {
4627                                 bool par_boundary = cur.pos() == 0;
4628                                 bool first_par = cur.pit() == 0;
4629                                 // Par boundary, full-screen update
4630                                 if (par_boundary)
4631                                         singleParUpdate = false;
4632                                 else if (cmd.getArg(0) == "confirm" && cur.confirmDeletion(true)) {
4633                                         cur.resetAnchor();
4634                                         cur.selection(true);
4635                                         cur.posBackward();
4636                                         cur.setSelection();
4637                                         break;
4638                                 }
4639                                 needsUpdate |= backspace(cur);
4640                                 cur.resetAnchor();
4641                                 if (par_boundary && !first_par && cur.pos() > 0
4642                                     && cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
4643                                         needsUpdate |= backspace(cur);
4644                                         cur.resetAnchor();
4645                                 }
4646                         }
4647                 } else {
4648                         DocIterator const dit = cur.selectionBegin();
4649                         cutSelection(cur, false);
4650                         if (cur.buffer()->params().track_changes)
4651                                 // since we're doing backwards deletion,
4652                                 // and the selection is not really cut,
4653                                 // move cursor before selection (#11630)
4654                                 cur.setCursor(dit);
4655                         cur.setCurrentFont();
4656                         singleParUpdate = false;
4657                 }
4658                 break;
4659
4660         case LFUN_PARAGRAPH_BREAK: {
4661                 cap::replaceSelection(cur);
4662                 pit_type pit = cur.pit();
4663                 Paragraph const & par = pars_[pit];
4664                 bool lastpar = (pit == pit_type(pars_.size() - 1));
4665                 Paragraph const & nextpar = lastpar ? par : pars_[pit + 1];
4666                 pit_type prev = pit > 0 ? depthHook(pit, par.getDepth()) : pit;
4667                 if (prev < pit && cur.pos() == par.beginOfBody()
4668                     && par.empty() && !par.isEnvSeparator(cur.pos())
4669                     && !par.layout().keepempty
4670                     && !par.layout().isCommand()
4671                     && pars_[prev].layout() != par.layout()
4672                     && pars_[prev].layout().isEnvironment()
4673                     && !nextpar.isEnvSeparator(nextpar.beginOfBody())) {
4674                         if (par.layout().isEnvironment()
4675                             && pars_[prev].getDepth() == par.getDepth()) {
4676                                 docstring const layout = par.layout().name();
4677                                 DocumentClass const & tc = bv->buffer().params().documentClass();
4678                                 lyx::dispatch(FuncRequest(LFUN_LAYOUT, tc.plainLayout().name()));
4679                                 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4680                                 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
4681                                 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
4682                         } else {
4683                                 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
4684                                 breakParagraph(cur);
4685                         }
4686                         Font const f(inherit_font, cur.current_font.language());
4687                         pars_[cur.pit() - 1].resetFonts(f);
4688                 } else {
4689                         if (par.isEnvSeparator(cur.pos()) && cmd.getArg(1) != "ignoresep")
4690                                 cur.posForward();
4691                         breakParagraph(cur, cmd.getArg(0) == "inverse");
4692                 }
4693                 cur.resetAnchor();
4694                 // If we have a list and autoinsert item insets,
4695                 // insert them now.
4696                 Layout::LaTeXArgMap args = par.layout().args();
4697                 for (auto const & thearg : args) {
4698                         Layout::latexarg arg = thearg.second;
4699                         if (arg.autoinsert && prefixIs(thearg.first, "item:")) {
4700                                 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, thearg.first);
4701                                 lyx::dispatch(cmd2);
4702                         }
4703                 }
4704                 break;
4705         }
4706
4707         case LFUN_INSET_INSERT: {
4708                 cur.recordUndo();
4709
4710                 // We have to avoid triggering InstantPreview loading
4711                 // before inserting into the document. See bug #5626.
4712                 bool loaded = bv->buffer().isFullyLoaded();
4713                 bv->buffer().setFullyLoaded(false);
4714                 Inset * inset = createInset(&bv->buffer(), cmd);
4715                 bv->buffer().setFullyLoaded(loaded);
4716
4717                 if (inset) {
4718                         // FIXME (Abdel 01/02/2006):
4719                         // What follows would be a partial fix for bug 2154:
4720                         //   http://www.lyx.org/trac/ticket/2154
4721                         // This automatically put the label inset _after_ a
4722                         // numbered section. It should be possible to extend the mechanism
4723                         // to any kind of LateX environement.
4724                         // The correct way to fix that bug would be at LateX generation.
4725                         // I'll let the code here for reference as it could be used for some
4726                         // other feature like "automatic labelling".
4727                         /*
4728                         Paragraph & par = pars_[cur.pit()];
4729                         if (inset->lyxCode() == LABEL_CODE
4730                                 && !par.layout().counter.empty()) {
4731                                 // Go to the end of the paragraph
4732                                 // Warning: Because of Change-Tracking, the last
4733                                 // position is 'size()' and not 'size()-1':
4734                                 cur.pos() = par.size();
4735                                 // Insert a new paragraph
4736                                 FuncRequest fr(LFUN_PARAGRAPH_BREAK);
4737                                 dispatch(cur, fr);
4738                         }
4739                         */
4740                         if (cur.selection())
4741                                 cutSelection(cur, false);
4742                         cur.insert(inset);
4743                         cur.forceBufferUpdate();
4744                         if (inset->editable() && inset->asInsetText())
4745                                 inset->edit(cur, true);
4746                         else
4747                                 cur.posForward();
4748
4749                         // trigger InstantPreview now
4750                         if (inset->lyxCode() == EXTERNAL_CODE) {
4751                                 InsetExternal & ins =
4752                                         static_cast<InsetExternal &>(*inset);
4753                                 ins.updatePreview();
4754                         }
4755                 }
4756
4757                 break;
4758         }
4759
4760         case LFUN_INSET_DISSOLVE: {
4761                 if (dissolveInset(cur)) {
4762                         needsUpdate = true;
4763                         cur.forceBufferUpdate();
4764                 }
4765                 break;
4766         }
4767
4768         case LFUN_INSET_SPLIT: {
4769                 if (splitInset(cur)) {
4770                         needsUpdate = true;
4771                         cur.forceBufferUpdate();
4772                 }
4773                 break;
4774         }
4775
4776         case LFUN_GRAPHICS_SET_GROUP: {
4777                 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
4778                 if (!ins)
4779                         break;
4780
4781                 cur.recordUndo();
4782
4783                 string id = to_utf8(cmd.argument());
4784                 string grp = graphics::getGroupParams(bv->buffer(), id);
4785                 InsetGraphicsParams tmp, inspar = ins->getParams();
4786
4787                 if (id.empty())
4788                         inspar.groupId = to_utf8(cmd.argument());
4789                 else {
4790                         InsetGraphics::string2params(grp, bv->buffer(), tmp);
4791                         tmp.filename = inspar.filename;
4792                         inspar = tmp;
4793                 }
4794
4795                 ins->setParams(inspar);
4796                 break;
4797         }
4798
4799         case LFUN_SPACE_INSERT:
4800                 if (cur.paragraph().layout().free_spacing)
4801                         insertChar(cur, ' ');
4802                 else {
4803                         doInsertInset(cur, this, cmd, false, false);
4804                         cur.posForward();
4805                 }
4806                 moveCursor(cur, false);
4807                 break;
4808
4809         case LFUN_SPECIALCHAR_INSERT: {
4810                 string const name = to_utf8(cmd.argument());
4811                 if (name == "hyphenation")
4812                         specialChar(cur, InsetSpecialChar::HYPHENATION);
4813                 else if (name == "allowbreak")
4814                         specialChar(cur, InsetSpecialChar::ALLOWBREAK);
4815                 else if (name == "ligature-break")
4816                         specialChar(cur, InsetSpecialChar::LIGATURE_BREAK);
4817                 else if (name == "slash")
4818                         specialChar(cur, InsetSpecialChar::SLASH);
4819                 else if (name == "nobreakdash")
4820                         specialChar(cur, InsetSpecialChar::NOBREAKDASH);
4821                 else if (name == "dots")
4822                         specialChar(cur, InsetSpecialChar::LDOTS);
4823                 else if (name == "end-of-sentence")
4824                         specialChar(cur, InsetSpecialChar::END_OF_SENTENCE);
4825                 else if (name == "menu-separator")
4826                         specialChar(cur, InsetSpecialChar::MENU_SEPARATOR);
4827                 else if (name == "lyx")
4828                         specialChar(cur, InsetSpecialChar::PHRASE_LYX);
4829                 else if (name == "tex")
4830                         specialChar(cur, InsetSpecialChar::PHRASE_TEX);
4831                 else if (name == "latex")
4832                         specialChar(cur, InsetSpecialChar::PHRASE_LATEX);
4833                 else if (name == "latex2e")
4834                         specialChar(cur, InsetSpecialChar::PHRASE_LATEX2E);
4835                 else if (name.empty())
4836                         lyxerr << "LyX function 'specialchar-insert' needs an argument." << endl;
4837                 else
4838                         lyxerr << "Wrong argument for LyX function 'specialchar-insert'." << endl;
4839                 break;
4840         }
4841
4842         case LFUN_IPAMACRO_INSERT: {
4843                 string const arg = cmd.getArg(0);
4844                 if (arg == "deco") {
4845                         // Open the inset, and move the current selection
4846                         // inside it.
4847                         doInsertInset(cur, this, cmd, true, true);
4848                         cur.posForward();
4849                         // Some insets are numbered, others are shown in the outline pane so
4850                         // let's update the labels and the toc backend.
4851                         cur.forceBufferUpdate();
4852                         break;
4853                 }
4854                 if (arg == "tone-falling")
4855                         ipaChar(cur, InsetIPAChar::TONE_FALLING);
4856                 else if (arg == "tone-rising")
4857                         ipaChar(cur, InsetIPAChar::TONE_RISING);
4858                 else if (arg == "tone-high-rising")
4859                         ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING);
4860                 else if (arg == "tone-low-rising")
4861                         ipaChar(cur, InsetIPAChar::TONE_LOW_RISING);
4862                 else if (arg == "tone-high-rising-falling")
4863                         ipaChar(cur, InsetIPAChar::TONE_HIGH_RISING_FALLING);
4864                 else if (arg.empty())
4865                         lyxerr << "LyX function 'ipamacro-insert' needs an argument." << endl;
4866                 else
4867                         lyxerr << "Wrong argument for LyX function 'ipamacro-insert'." << endl;
4868                 break;
4869         }
4870
4871         case LFUN_WORD_UPCASE:
4872                 changeCase(cur, text_uppercase, cmd.getArg(0) == "partial");
4873                 break;
4874
4875         case LFUN_WORD_LOWCASE:
4876                 changeCase(cur, text_lowercase, cmd.getArg(0) == "partial");
4877                 break;
4878
4879         case LFUN_WORD_CAPITALIZE:
4880                 changeCase(cur, text_capitalization, cmd.getArg(0) == "partial");
4881                 break;
4882
4883         case LFUN_CHARS_TRANSPOSE:
4884                 charsTranspose(cur);
4885                 break;
4886
4887         case LFUN_PASTE: {
4888                 cur.message(_("Paste"));
4889                 LASSERT(cur.selBegin().idx() == cur.selEnd().idx(), break);
4890                 cap::replaceSelection(cur);
4891
4892                 // without argument?
4893                 string const arg = to_utf8(cmd.argument());
4894                 if (arg.empty()) {
4895                         bool tryGraphics = true;
4896                         if (theClipboard().isInternal())
4897                                 pasteFromStack(cur, bv->buffer().errorList("Paste"), 0);
4898                         else if (theClipboard().hasTextContents()) {
4899                                 if (pasteClipboardText(cur, bv->buffer().errorList("Paste"),
4900                                                        !cur.paragraph().parbreakIsNewline(),
4901                                                            Clipboard::AnyTextType))
4902                                         tryGraphics = false;
4903                         }
4904                         if (tryGraphics && theClipboard().hasGraphicsContents())
4905                                 pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"));
4906                 } else if (isStrUnsignedInt(arg)) {
4907                         // we have a numerical argument
4908                         pasteFromStack(cur, bv->buffer().errorList("Paste"),
4909                                        convert<unsigned int>(arg));
4910                 } else if (arg == "html" || arg == "latex") {
4911                         Clipboard::TextType type = (arg == "html") ?
4912                                 Clipboard::HtmlTextType : Clipboard::LaTeXTextType;
4913                         pasteClipboardText(cur, bv->buffer().errorList("Paste"), true, type);
4914                 } else {
4915                         Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
4916                         if (arg == "pdf")
4917                                 type = Clipboard::PdfGraphicsType;
4918                         else if (arg == "png")
4919                                 type = Clipboard::PngGraphicsType;
4920                         else if (arg == "jpeg")
4921                                 type = Clipboard::JpegGraphicsType;
4922                         else if (arg == "linkback")
4923                                 type = Clipboard::LinkBackGraphicsType;
4924                         else if (arg == "emf")
4925                                 type = Clipboard::EmfGraphicsType;
4926                         else if (arg == "wmf")
4927                                 type = Clipboard::WmfGraphicsType;
4928                         else
4929                                 // we also check in getStatus()
4930                                 LYXERR0("Unrecognized graphics type: " << arg);
4931
4932                         pasteClipboardGraphics(cur, bv->buffer().errorList("Paste"), type);
4933                 }
4934
4935                 bv->buffer().errors("Paste");
4936                 bv->buffer().updatePreviews(); // bug 11619
4937                 cur.clearSelection(); // bug 393
4938                 cur.finishUndo();
4939                 break;
4940         }
4941
4942         case LFUN_CUT:
4943                 cutSelection(cur, true);
4944                 cur.message(_("Cut"));
4945                 break;
4946
4947         case LFUN_SERVER_GET_XY:
4948                 cur.message(from_utf8(
4949                         convert<string>(tm->cursorX(cur.top(), cur.boundary()))
4950                         + ' ' + convert<string>(tm->cursorY(cur.top(), cur.boundary()))));
4951                 break;
4952
4953         case LFUN_SERVER_SET_XY: {
4954                 int x = 0;
4955                 int y = 0;
4956                 istringstream is(to_utf8(cmd.argument()));
4957                 is >> x >> y;
4958                 if (!is)
4959                         lyxerr << "SETXY: Could not parse coordinates in '"
4960                                << to_utf8(cmd.argument()) << endl;
4961                 else
4962                         tm->setCursorFromCoordinates(cur, x, y);
4963                 break;
4964         }
4965
4966         case LFUN_SERVER_GET_LAYOUT:
4967                 cur.message(cur.paragraph().layout().name());
4968                 break;
4969
4970         case LFUN_LAYOUT:
4971         case LFUN_LAYOUT_TOGGLE: {
4972                 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
4973                 docstring req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
4974                 LYXERR(Debug::INFO, "LFUN_LAYOUT: (arg) " << to_utf8(req_layout));
4975
4976                 docstring layout = resolveLayout(req_layout, cur);
4977                 if (layout.empty()) {
4978                         cur.errorMessage(from_utf8(N_("Layout ")) + req_layout +
4979                                 from_utf8(N_(" not known")));
4980                         break;
4981                 }
4982
4983                 docstring const old_layout = cur.paragraph().layout().name();
4984                 bool change_layout = !isAlreadyLayout(layout, cur);
4985
4986                 if (cmd.action() == LFUN_LAYOUT_TOGGLE && !change_layout) {
4987                         change_layout = true;
4988                         layout = resolveLayout(docstring(), cur);
4989                 }
4990
4991                 if (change_layout) {
4992                         setLayout(cur, layout);
4993                         if (cur.pit() > 0 && !ignoreautonests) {
4994                                 pit_type prev_pit = cur.pit() - 1;
4995                                 depth_type const cur_depth = pars_[cur.pit()].getDepth();
4996                                 // Scan for the previous par on same nesting level
4997                                 while (prev_pit > 0 && pars_[prev_pit].getDepth() > cur_depth)
4998                                         --prev_pit;
4999                                 set<docstring> const & autonests =
5000                                                 pars_[prev_pit].layout().autonests();
5001                                 set<docstring> const & autonested =
5002                                                 pars_[cur.pit()].layout().isAutonestedBy();
5003                                 if (autonests.find(layout) != autonests.end()
5004                                                 || autonested.find(old_layout) != autonested.end())
5005                                         lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5006                         }
5007                 }
5008
5009                 DocumentClass const & tclass = bv->buffer().params().documentClass();
5010                 bool inautoarg = false;
5011                 for (auto const & la_pair : tclass[layout].args()) {
5012                         Layout::latexarg const & arg = la_pair.second;
5013                         if (arg.autoinsert) {
5014                                 // If we had already inserted an arg automatically,
5015                                 // leave this now in order to insert the next one.
5016                                 if (inautoarg) {
5017                                         cur.leaveInset(cur.inset());
5018                                         cur.posForward();
5019                                 }
5020                                 FuncRequest const cmd2(LFUN_ARGUMENT_INSERT, la_pair.first);
5021                                 lyx::dispatch(cmd2);
5022                                 inautoarg = true;
5023                         }
5024                 }
5025
5026                 break;
5027         }
5028
5029         case LFUN_ENVIRONMENT_SPLIT: {
5030                 bool const outer = cmd.argument() == "outer";
5031                 bool const previous = cmd.argument() == "previous";
5032                 bool const before = cmd.argument() == "before";
5033                 bool const normal = cmd.argument().empty();
5034                 Paragraph const & para = cur.paragraph();
5035                 docstring layout;
5036                 if (para.layout().isEnvironment())
5037                         layout = para.layout().name();
5038                 depth_type split_depth = cur.paragraph().params().depth();
5039                 vector<depth_type> nextpars_depth;
5040                 if (outer || previous) {
5041                         // check if we have an environment in our scope
5042                         pit_type pit = cur.pit();
5043                         Paragraph cpar = pars_[pit];
5044                         while (true) {
5045                                 if (pit == 0)
5046                                         break;
5047                                 --pit;
5048                                 cpar = pars_[pit];
5049                                 if (layout.empty() && previous
5050                                     && cpar.layout().isEnvironment()
5051                                     && cpar.params().depth() <= split_depth)
5052                                         layout = cpar.layout().name();
5053                                 if (cpar.params().depth() < split_depth
5054                                     && cpar.layout().isEnvironment()) {
5055                                                 if (!previous)
5056                                                         layout = cpar.layout().name();
5057                                                 split_depth = cpar.params().depth();
5058                                 }
5059                                 if (cpar.params().depth() == 0)
5060                                         break;
5061                         }
5062                 }
5063                 if ((outer || normal) && cur.pit() < cur.lastpit()) {
5064                         // save nesting of following paragraphs if they are deeper
5065                         // or same depth
5066                         pit_type offset = 1;
5067                         depth_type cur_depth = pars_[cur.pit()].params().depth();
5068                         while (cur.pit() + offset <= cur.lastpit()) {
5069                                 Paragraph cpar = pars_[cur.pit() + offset];
5070                                 depth_type nextpar_depth = cpar.params().depth();
5071                                 if (cur_depth <= nextpar_depth && nextpar_depth > 0) {
5072                                         nextpars_depth.push_back(nextpar_depth);
5073                                         cur_depth = nextpar_depth;
5074                                         ++offset;
5075                                 } else
5076                                         break;
5077                         }
5078                 }
5079                 if (before)
5080                         cur.top().setPitPos(cur.pit(), 0);
5081                 if (before || cur.pos() > 0)
5082                         lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK));
5083                 else if (previous && cur.nextInset() && cur.nextInset()->lyxCode() == SEPARATOR_CODE)
5084                         lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5085                 if (outer) {
5086                         while (cur.paragraph().params().depth() > split_depth)
5087                                 lyx::dispatch(FuncRequest(LFUN_DEPTH_DECREMENT));
5088                 }
5089                 DocumentClass const & tc = bv->buffer().params().documentClass();
5090                 lyx::dispatch(FuncRequest(LFUN_LAYOUT, from_ascii("\"") + tc.plainLayout().name()
5091                                           + from_ascii("\" ignoreautonests")));
5092                 // FIXME: Bibitem mess!
5093                 if (cur.prevInset() && cur.prevInset()->lyxCode() == BIBITEM_CODE)
5094                         lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_BACKWARD));
5095                 lyx::dispatch(FuncRequest(LFUN_SEPARATOR_INSERT, "plain"));
5096                 if (before) {
5097                         cur.backwardPos();
5098                         lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse ignoresep"));
5099                         while (cur.paragraph().params().depth() < split_depth)
5100                                 lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5101                 }
5102                 else
5103                         lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_BREAK, "inverse"));
5104                 lyx::dispatch(FuncRequest(LFUN_LAYOUT, layout));
5105                 if ((outer || normal) && !nextpars_depth.empty()) {
5106                         // restore nesting of following paragraphs
5107                         DocIterator scur = cur;
5108                         depth_type max_depth = cur.paragraph().params().depth() + 1;
5109                         for (auto nextpar_depth : nextpars_depth) {
5110                                 cur.forwardPar();
5111                                 while (cur.paragraph().params().depth() < min(nextpar_depth, max_depth)) {
5112                                         depth_type const olddepth = cur.paragraph().params().depth();
5113                                         lyx::dispatch(FuncRequest(LFUN_DEPTH_INCREMENT));
5114                                         if (olddepth == cur.paragraph().params().depth())
5115                                                 // leave loop if no incrementation happens
5116                                                 break;
5117                                 }
5118                                 max_depth = cur.paragraph().params().depth() + 1;
5119                         }
5120                         cur.setCursor(scur);
5121                 }
5122
5123                 break;
5124         }
5125
5126         case LFUN_CLIPBOARD_PASTE:
5127                 cap::replaceSelection(cur);
5128                 pasteClipboardText(cur, bv->buffer().errorList("Paste"),
5129                                cmd.argument() == "paragraph");
5130                 bv->buffer().errors("Paste");
5131                 break;
5132
5133         case LFUN_CLIPBOARD_PASTE_SIMPLE:
5134                 cap::replaceSelection(cur);
5135                 pasteSimpleText(cur, cmd.argument() == "paragraph");
5136                 break;
5137
5138         case LFUN_PRIMARY_SELECTION_PASTE:
5139                 cap::replaceSelection(cur);
5140                 pasteString(cur, theSelection().get(),
5141                             cmd.argument() == "paragraph");
5142                 break;
5143
5144         case LFUN_SELECTION_PASTE:
5145                 // Copy the selection buffer to the clipboard stack,
5146                 // because we want it to appear in the "Edit->Paste
5147                 // recent" menu.
5148                 cap::replaceSelection(cur);
5149                 cap::copySelectionToStack();
5150                 cap::pasteSelection(bv->cursor(), bv->buffer().errorList("Paste"));
5151                 bv->buffer().errors("Paste");
5152                 break;
5153
5154         case LFUN_QUOTE_INSERT: {
5155                 cap::replaceSelection(cur);
5156                 cur.recordUndo();
5157
5158                 Paragraph const & par = cur.paragraph();
5159                 pos_type pos = cur.pos();
5160                 // Ignore deleted text before cursor
5161                 while (pos > 0 && par.isDeleted(pos - 1))
5162                         --pos;
5163
5164                 bool const inner = (cmd.getArg(0) == "single" || cmd.getArg(0) == "inner");
5165
5166                 // Guess quote side.
5167                 // A space triggers an opening quote. This is passed if the preceding
5168                 // char/inset is a space or at paragraph start.
5169                 char_type c = ' ';
5170                 if (pos > 0 && !par.isSpace(pos - 1)) {
5171                         if (cur.prevInset() && cur.prevInset()->lyxCode() == QUOTE_CODE) {
5172                                 // If an opening double quotation mark precedes, and this
5173                                 // is a single quote, make it opening as well
5174                                 InsetQuotes & ins =
5175                                         static_cast<InsetQuotes &>(*cur.prevInset());
5176                                 string const type = ins.getType();
5177                                 if (!suffixIs(type, "ld") || !inner)
5178                                         c = par.getChar(pos - 1);
5179                         }
5180                         else if (!cur.prevInset()
5181                             || (cur.prevInset() && cur.prevInset()->isChar()))
5182                                 // If a char precedes, pass that and let InsetQuote decide
5183                                 c = par.getChar(pos - 1);
5184                         else {
5185                                 while (pos > 0) {
5186                                         if (par.getInset(pos - 1)
5187                                             && !par.getInset(pos - 1)->isPartOfTextSequence()) {
5188                                                 // skip "invisible" insets
5189                                                 --pos;
5190                                                 continue;
5191                                         }
5192                                         c = par.getChar(pos - 1);
5193                                         break;
5194                                 }
5195                         }
5196                 }
5197                 QuoteLevel const quote_level = inner
5198                                 ? QuoteLevel::Secondary : QuoteLevel::Primary;
5199                 cur.insert(new InsetQuotes(cur.buffer(), c, quote_level, cmd.getArg(1), cmd.getArg(2)));
5200                 cur.buffer()->updateBuffer();
5201                 cur.posForward();
5202                 break;
5203         }
5204
5205         case LFUN_MOUSE_TRIPLE:
5206                 if (cmd.button() == mouse_button::button1) {
5207                         if (cur.pos() > 0)
5208                                 setCursor(cur, cur.pit(), 0);
5209                         bv->cursor() = cur;
5210                         cur.resetAnchor();
5211                         if (cur.pos() < cur.lastpos())
5212                                 setCursor(cur, cur.pit(), cur.lastpos());
5213                         cur.setSelection();
5214                         bv->cursor() = cur;
5215                 }
5216                 break;
5217
5218         case LFUN_MOUSE_DOUBLE:
5219                 if (cmd.button() == mouse_button::button1) {
5220                         selectWord(cur, WHOLE_WORD);
5221                         bv->cursor() = cur;
5222                 }
5223                 break;
5224
5225         // Single-click on work area
5226         case LFUN_MOUSE_PRESS: {
5227                 // We are not marking a selection with the keyboard in any case.
5228                 Cursor & bvcur = cur.bv().cursor();
5229                 bvcur.setMark(false);
5230                 switch (cmd.button()) {
5231                 case mouse_button::button1:
5232                         if (!bvcur.selection())
5233                                 // Set the cursor
5234                                 bvcur.resetAnchor();
5235                         if (!bv->mouseSetCursor(cur, cmd.modifier() == ShiftModifier))
5236                                 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5237                         // FIXME: move this to mouseSetCursor?
5238                         if (bvcur.wordSelection() && bvcur.inTexted())
5239                                 expandWordSel(bvcur);
5240                         break;
5241
5242                 case mouse_button::button2:
5243                         if (lyxrc.mouse_middlebutton_paste) {
5244                                 // Middle mouse pasting.
5245                                 bv->mouseSetCursor(cur);
5246                                 lyx::dispatch(
5247                                         FuncRequest(LFUN_COMMAND_ALTERNATIVES,
5248                                                     "selection-paste ; primary-selection-paste paragraph"));
5249                         }
5250                         cur.noScreenUpdate();
5251                         break;
5252
5253                 case mouse_button::button3: {
5254                         // Don't do anything if we right-click a
5255                         // selection, a context menu will popup.
5256                         if (bvcur.selection() && cur >= bvcur.selectionBegin()
5257                             && cur <= bvcur.selectionEnd()) {
5258                                 cur.noScreenUpdate();
5259                                 return;
5260                         }
5261                         if (!bv->mouseSetCursor(cur, false))
5262                                 cur.screenUpdateFlags(Update::FitCursor);
5263                         break;
5264                 }
5265
5266                 default:
5267                         break;
5268                 } // switch (cmd.button())
5269                 break;
5270         }
5271         case LFUN_MOUSE_MOTION: {
5272                 // Mouse motion with right or middle mouse do nothing for now.
5273                 if (cmd.button() != mouse_button::button1) {
5274                         cur.noScreenUpdate();
5275                         return;
5276                 }
5277                 // ignore motions deeper nested than the real anchor
5278                 Cursor & bvcur = cur.bv().cursor();
5279                 if (!bvcur.realAnchor().hasPart(cur)) {
5280                         cur.undispatched();
5281                         break;
5282                 }
5283                 CursorSlice old = bvcur.top();
5284
5285                 int const wh = bv->workHeight();
5286                 int const y = max(0, min(wh - 1, cmd.y()));
5287
5288                 tm->setCursorFromCoordinates(cur, cmd.x(), y);
5289                 cur.setTargetX(cmd.x());
5290                 // Don't allow selecting a separator inset
5291                 if (cur.pos() && cur.paragraph().isEnvSeparator(cur.pos() - 1))
5292                         cur.posBackward();
5293                 if (cmd.y() >= wh)
5294                         lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5295                 else if (cmd.y() < 0)
5296                         lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5297                 // This is to allow jumping over large insets
5298                 if (cur.top() == old) {
5299                         if (cmd.y() >= wh)
5300                                 lyx::dispatch(FuncRequest(LFUN_DOWN_SELECT));
5301                         else if (cmd.y() < 0)
5302                                 lyx::dispatch(FuncRequest(LFUN_UP_SELECT));
5303                 }
5304                 // We continue with our existing selection or start a new one, so don't
5305                 // reset the anchor.
5306                 bvcur.setCursor(cur);
5307                 if (bvcur.wordSelection() && bvcur.inTexted())
5308                         expandWordSel(bvcur);
5309                 bvcur.selection(true);
5310                 bvcur.setCurrentFont();
5311                 if (cur.top() == old) {
5312                         // We didn't move one iota, so no need to update the screen.
5313                         cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5314                         //cur.noScreenUpdate();
5315                         return;
5316                 }
5317                 break;
5318         }
5319
5320         case LFUN_MOUSE_RELEASE:
5321                 switch (cmd.button()) {
5322                 case mouse_button::button1:
5323                         // Cursor was set at LFUN_MOUSE_PRESS or LFUN_MOUSE_MOTION time.
5324                         // If there is a new selection, update persistent selection;
5325                         // otherwise, single click does not clear persistent selection
5326                         // buffer.
5327                         if (cur.selection()) {
5328                                 // Finish selection. If double click,
5329                                 // cur is moved to the end of word by
5330                                 // selectWord but bvcur is current
5331                                 // mouse position.
5332                                 cur.bv().cursor().setSelection();
5333                                 // We might have removed an empty but drawn selection
5334                                 // (probably a margin)
5335                                 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
5336                         } else
5337                                 cur.noScreenUpdate();
5338                         // FIXME: We could try to handle drag and drop of selection here.
5339                         return;
5340
5341                 case mouse_button::button2:
5342                         // Middle mouse pasting is handled at mouse press time,
5343                         // see LFUN_MOUSE_PRESS.
5344                         cur.noScreenUpdate();
5345                         return;
5346
5347                 case mouse_button::button3:
5348                         // Cursor was set at LFUN_MOUSE_PRESS time.
5349                         // FIXME: If there is a selection we could try to handle a special
5350                         // drag & drop context menu.
5351                         cur.noScreenUpdate();
5352                         return;
5353
5354                 case mouse_button::none:
5355                 case mouse_button::button4:
5356                 case mouse_button::button5:
5357                         break;
5358                 } // switch (cmd.button())
5359
5360                 break;
5361
5362         case LFUN_SELF_INSERT: {
5363                 if (cmd.argument().empty())
5364                         break;
5365
5366                 // Automatically delete the currently selected
5367                 // text and replace it with what is being
5368                 // typed in now. Depends on lyxrc settings
5369                 // "auto_region_delete", which defaults to
5370                 // true (on).
5371
5372                 if (lyxrc.auto_region_delete && cur.selection()) {
5373                         cutSelection(cur, false);
5374                         cur.setCurrentFont();
5375                 }
5376                 cur.clearSelection();
5377
5378                 for (char_type c : cmd.argument())
5379                         bv->translateAndInsert(c, this, cur);
5380
5381                 cur.resetAnchor();
5382                 moveCursor(cur, false);
5383                 cur.markNewWordPosition();
5384                 bv->bookmarkEditPosition();
5385                 break;
5386         }
5387
5388         case LFUN_HREF_INSERT: {
5389                 docstring content = cmd.argument();
5390                 if (content.empty() && cur.selection())
5391                         content = cur.selectionAsString(false);
5392
5393                 InsetCommandParams p(HYPERLINK_CODE);
5394                 if (!content.empty()){
5395                         // if it looks like a link, we'll put it as target,
5396                         // otherwise as name (bug #8792).
5397
5398                         // We can't do:
5399                         //   regex_match(to_utf8(content), matches, link_re)
5400                         // because smatch stores pointers to the substrings rather
5401                         // than making copies of them. And those pointers become
5402                         // invalid after regex_match returns, since it is then
5403                         // being given a temporary object. (Thanks to Georg for
5404                         // figuring that out.)
5405                         regex const link_re("^(([a-z]+):|www\\.).*");
5406                         smatch matches;
5407                         string const c = to_utf8(lowercase(content));
5408
5409                         if (c.substr(0,7) == "mailto:") {
5410                                 p["target"] = content;
5411                                 p["type"] = from_ascii("mailto:");
5412                         } else if (regex_match(c, matches, link_re)) {
5413                                 p["target"] = content;
5414                                 string protocol = matches.str(1);
5415                                 if (protocol == "file")
5416                                         p["type"] = from_ascii("file:");
5417                         } else
5418                                 p["name"] = content;
5419                 }
5420                 string const data = InsetCommand::params2string(p);
5421
5422                 // we need to have a target. if we already have one, then
5423                 // that gets used at the default for the name, too, which
5424                 // is probably what is wanted.
5425                 if (p["target"].empty()) {
5426                         bv->showDialog("href", data);
5427                 } else {
5428                         FuncRequest fr(LFUN_INSET_INSERT, data);
5429                         dispatch(cur, fr);
5430                 }
5431                 break;
5432         }
5433
5434         case LFUN_LABEL_INSERT: {
5435                 InsetCommandParams p(LABEL_CODE);
5436                 // Try to generate a valid label
5437                 p["name"] = (cmd.argument().empty()) ?
5438                         cur.getPossibleLabel() :
5439                         cmd.argument();
5440                 string const data = InsetCommand::params2string(p);
5441
5442                 if (cmd.argument().empty()) {
5443                         bv->showDialog("label", data);
5444                 } else {
5445                         FuncRequest fr(LFUN_INSET_INSERT, data);
5446                         dispatch(cur, fr);
5447                 }
5448                 break;
5449         }
5450
5451         case LFUN_INFO_INSERT: {
5452                 if (cmd.argument().empty()) {
5453                         bv->showDialog("info", cur.current_font.language()->lang());
5454                 } else {
5455                         Inset * inset;
5456                         inset = createInset(cur.buffer(), cmd);
5457                         if (!inset)
5458                                 break;
5459                         cur.recordUndo();
5460                         insertInset(cur, inset);
5461                         cur.forceBufferUpdate();
5462                         cur.posForward();
5463                 }
5464                 break;
5465         }
5466         case LFUN_CAPTION_INSERT:
5467         case LFUN_FOOTNOTE_INSERT:
5468         case LFUN_NOTE_INSERT:
5469         case LFUN_BOX_INSERT:
5470         case LFUN_BRANCH_INSERT:
5471         case LFUN_PHANTOM_INSERT:
5472         case LFUN_ERT_INSERT:
5473         case LFUN_INDEXMACRO_INSERT:
5474         case LFUN_LISTING_INSERT:
5475         case LFUN_MARGINALNOTE_INSERT:
5476         case LFUN_ARGUMENT_INSERT:
5477         case LFUN_INDEX_INSERT:
5478         case LFUN_PREVIEW_INSERT:
5479         case LFUN_SCRIPT_INSERT:
5480         case LFUN_IPA_INSERT: {
5481                 // Indexes reset font formatting (#11961)
5482                 bool const resetfont = cmd.action() == LFUN_INDEX_INSERT;
5483                 // Open the inset, and move the current selection
5484                 // inside it.
5485                 doInsertInset(cur, this, cmd, true, true, resetfont);
5486                 cur.posForward();
5487                 cur.setCurrentFont();
5488                 // Some insets are numbered, others are shown in the outline pane so
5489                 // let's update the labels and the toc backend.
5490                 cur.forceBufferUpdate();
5491                 break;
5492         }
5493
5494         case LFUN_FLEX_INSERT: {
5495                 // Open the inset, and move the current selection
5496                 // inside it.
5497                 bool const sel = cur.selection();
5498                 doInsertInset(cur, this, cmd, true, true);
5499                 // Insert auto-insert arguments
5500                 bool autoargs = false, inautoarg = false;
5501                 Layout::LaTeXArgMap args = cur.inset().getLayout().args();
5502                 for (auto const & argt : args) {
5503                         Layout::latexarg arg = argt.second;
5504                         if (!inautoarg && arg.insertonnewline && cur.pos() > 0) {
5505                                 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5506                                 lyx::dispatch(cmd2);
5507                         }
5508                         if (arg.autoinsert) {
5509                                 // The cursor might have been invalidated by the replaceSelection.
5510                                 cur.buffer()->changed(true);
5511                                 // If we had already inserted an arg automatically,
5512                                 // leave this now in order to insert the next one.
5513                                 if (inautoarg) {
5514                                         cur.leaveInset(cur.inset());
5515                                         cur.setCurrentFont();
5516                                         cur.posForward();
5517                                         if (arg.insertonnewline && cur.pos() > 0) {
5518                                                 FuncRequest cmd2(LFUN_PARAGRAPH_BREAK);
5519                                                 lyx::dispatch(cmd2);
5520                                         }
5521                                 }
5522                                 FuncRequest cmd2(LFUN_ARGUMENT_INSERT, argt.first);
5523                                 lyx::dispatch(cmd2);
5524                                 autoargs = true;
5525                                 inautoarg = true;
5526                         }
5527                 }
5528                 if (!autoargs) {
5529                         if (sel)
5530                                 cur.leaveInset(cur.inset());
5531                         cur.posForward();
5532                 }
5533                 // Some insets are numbered, others are shown in the outline pane so
5534                 // let's update the labels and the toc backend.
5535                 cur.forceBufferUpdate();
5536                 break;
5537         }
5538
5539         case LFUN_TABULAR_INSERT: {
5540                 // if there were no arguments, just open the dialog
5541                 if (cmd.argument().empty()) {
5542                         bv->showDialog("tabularcreate");
5543                         break;
5544                 } else if (cur.buffer()->masterParams().tablestyle != "default"
5545                            || bv->buffer().params().documentClass().tablestyle() != "default") {
5546                         string tabstyle = cur.buffer()->masterParams().tablestyle;
5547                         if (tabstyle == "default")
5548                                 tabstyle = bv->buffer().params().documentClass().tablestyle();
5549                         if (!libFileSearch("tabletemplates", tabstyle + ".lyx").empty()) {
5550                                 FuncRequest fr(LFUN_TABULAR_STYLE_INSERT,
5551                                                tabstyle + " " + to_ascii(cmd.argument()));
5552                                 lyx::dispatch(fr);
5553                                 break;
5554                         } else
5555                                 // Unknown style. Report and fall back to default.
5556                                 cur.errorMessage(from_utf8(N_("Table Style ")) + from_utf8(tabstyle) +
5557                                                      from_utf8(N_(" not known")));
5558                 }
5559                 if (doInsertInset(cur, this, cmd, false, true))
5560                         cur.posForward();
5561                 break;
5562         }
5563
5564         case LFUN_TABULAR_STYLE_INSERT: {
5565                 string const style = cmd.getArg(0);
5566                 string const rows = cmd.getArg(1);
5567                 string const cols = cmd.getArg(2);
5568                 if (cols.empty() || !isStrInt(cols)
5569                     || rows.empty() || !isStrInt(rows))
5570                         break;
5571                 int const r = convert<int>(rows);
5572                 int const c = convert<int>(cols);
5573
5574                 string suffix;
5575                 if (r == 1)
5576                         suffix = "_1x1";
5577                 else if (r == 2)
5578                         suffix = "_1x2";
5579                 FileName const tabstyle = libFileSearch("tabletemplates",
5580                                                         style + suffix + ".lyx", "lyx");
5581                 if (tabstyle.empty())
5582                             break;
5583                 UndoGroupHelper ugh(cur.buffer());
5584                 cur.recordUndo();
5585                 FuncRequest cmd2(LFUN_FILE_INSERT, tabstyle.absFileName() + " ignorelang");
5586                 lyx::dispatch(cmd2);
5587                 // go into table
5588                 cur.backwardPos();
5589                 if (r > 2) {
5590                         // move one cell up to middle cell
5591                         cur.up();
5592                         // add the missing rows
5593                         int const addrows = r - 3;
5594                         for (int i = 0 ; i < addrows ; ++i) {
5595                                 FuncRequest fr(LFUN_TABULAR_FEATURE, "append-row");
5596                                 lyx::dispatch(fr);
5597                         }
5598                 }
5599                 // add the missing columns
5600                 int const addcols = c - 1;
5601                 for (int i = 0 ; i < addcols ; ++i) {
5602                         FuncRequest fr(LFUN_TABULAR_FEATURE, "append-column");
5603                         lyx::dispatch(fr);
5604                 }
5605                 if (r > 1)
5606                         // go to first cell
5607                         cur.up();
5608                 break;
5609         }
5610
5611         case LFUN_FLOAT_INSERT:
5612         case LFUN_FLOAT_WIDE_INSERT:
5613         case LFUN_WRAP_INSERT: {
5614                 // will some content be moved into the inset?
5615                 bool const content = cur.selection();
5616                 // does the content consist of multiple paragraphs?
5617                 bool const singlepar = (cur.selBegin().pit() == cur.selEnd().pit());
5618
5619                 doInsertInset(cur, this, cmd, true, true);
5620                 cur.posForward();
5621
5622                 // If some single-par content is moved into the inset,
5623                 // doInsertInset puts the cursor outside the inset.
5624                 // To insert the caption we put it back into the inset.
5625                 // FIXME cleanup doInsertInset to avoid such dances!
5626                 if (content && singlepar)
5627                         cur.backwardPos();
5628
5629                 ParagraphList & pars = cur.text()->paragraphs();
5630
5631                 DocumentClass const & tclass = bv->buffer().params().documentClass();
5632
5633                 // add a separate paragraph for the caption inset
5634                 pars.push_back(Paragraph());
5635                 pars.back().setInsetOwner(&cur.text()->inset());
5636                 pars.back().setPlainOrDefaultLayout(tclass);
5637                 int cap_pit = pars.size() - 1;
5638
5639                 // if an empty inset was created, we create an additional empty
5640                 // paragraph at the bottom so that the user can choose where to put
5641                 // the graphics (or table).
5642                 if (!content) {
5643                         pars.push_back(Paragraph());
5644                         pars.back().setInsetOwner(&cur.text()->inset());
5645                         pars.back().setPlainOrDefaultLayout(tclass);
5646                 }
5647
5648                 // reposition the cursor to the caption
5649                 cur.pit() = cap_pit;
5650                 cur.pos() = 0;
5651                 // FIXME: This Text/Cursor dispatch handling is a mess!
5652                 // We cannot use Cursor::dispatch here it needs access to up to
5653                 // date metrics.
5654                 FuncRequest cmd_caption(LFUN_CAPTION_INSERT);
5655                 doInsertInset(cur, cur.text(), cmd_caption, true, false);
5656                 cur.forceBufferUpdate();
5657                 cur.screenUpdateFlags(Update::Force);
5658                 // FIXME: When leaving the Float (or Wrap) inset we should
5659                 // delete any empty paragraph left above or below the
5660                 // caption.
5661                 break;
5662         }
5663
5664         case LFUN_NOMENCL_INSERT: {
5665                 InsetCommandParams p(NOMENCL_CODE);
5666                 if (cmd.argument().empty()) {
5667                         p["symbol"] =
5668                                 bv->cursor().innerText()->getStringForDialog(bv->cursor());
5669                         cur.clearSelection();
5670                 } else
5671                         p["symbol"] = cmd.argument();
5672                 string const data = InsetCommand::params2string(p);
5673                 bv->showDialog("nomenclature", data);
5674                 break;
5675         }
5676
5677         case LFUN_INDEX_PRINT: {
5678                 InsetCommandParams p(INDEX_PRINT_CODE);
5679                 if (cmd.argument().empty())
5680                         p["type"] = from_ascii("idx");
5681                 else
5682                         p["type"] = cmd.argument();
5683                 string const data = InsetCommand::params2string(p);
5684                 FuncRequest fr(LFUN_INSET_INSERT, data);
5685                 dispatch(cur, fr);
5686                 break;
5687         }
5688
5689         case LFUN_NOMENCL_PRINT:
5690         case LFUN_NEWPAGE_INSERT:
5691                 // do nothing fancy
5692                 doInsertInset(cur, this, cmd, false, false);
5693                 cur.posForward();
5694                 break;
5695
5696         case LFUN_SEPARATOR_INSERT: {
5697                 doInsertInset(cur, this, cmd, false, false);
5698                 cur.posForward();
5699                 // remove a following space
5700                 Paragraph & par = cur.paragraph();
5701                 if (cur.pos() != cur.lastpos() && par.isLineSeparator(cur.pos()))
5702                     par.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
5703                 break;
5704         }
5705
5706         case LFUN_DEPTH_DECREMENT:
5707                 changeDepth(cur, DEC_DEPTH);
5708                 break;
5709
5710         case LFUN_DEPTH_INCREMENT:
5711                 changeDepth(cur, INC_DEPTH);
5712                 break;
5713
5714         case LFUN_REGEXP_MODE:
5715                 regexpDispatch(cur, cmd);
5716                 break;
5717
5718         case LFUN_MATH_MODE: {
5719                 if (cmd.argument() == "on" || cmd.argument() == "") {
5720                         // don't pass "on" as argument
5721                         // (it would appear literally in the first cell)
5722                         docstring sel = cur.selectionAsString(false);
5723                         InsetMathMacroTemplate * macro = new InsetMathMacroTemplate(cur.buffer());
5724                         // create a macro template if we see "\\newcommand" somewhere, and
5725                         // an ordinary formula otherwise
5726                         if (!sel.empty()
5727                                 && (sel.find(from_ascii("\\newcommand")) != string::npos
5728                                         || sel.find(from_ascii("\\newlyxcommand")) != string::npos
5729                                         || sel.find(from_ascii("\\def")) != string::npos)
5730                                 && macro->fromString(sel)) {
5731                                 cur.recordUndo();
5732                                 replaceSelection(cur);
5733                                 cur.insert(macro);
5734                         } else {
5735                                 // no meaningful macro template was found
5736                                 delete macro;
5737                                 mathDispatch(cur,FuncRequest(LFUN_MATH_MODE));
5738                         }
5739                 } else
5740                         // The argument is meaningful
5741                         // We replace cmd with LFUN_MATH_INSERT because LFUN_MATH_MODE
5742                         // has a different meaning in math mode
5743                         mathDispatch(cur, FuncRequest(LFUN_MATH_INSERT,cmd.argument()));
5744                 break;
5745         }
5746
5747         case LFUN_MATH_MACRO:
5748                 if (cmd.argument().empty())
5749                         cur.errorMessage(from_utf8(N_("Missing argument")));
5750                 else {
5751                         cur.recordUndo();
5752                         string s = to_utf8(cmd.argument());
5753                         string const s1 = token(s, ' ', 1);
5754                         int const nargs = s1.empty() ? 0 : convert<int>(s1);
5755                         string const s2 = token(s, ' ', 2);
5756                         MacroType type = MacroTypeNewcommand;
5757                         if (s2 == "def")
5758                                 type = MacroTypeDef;
5759                         InsetMathMacroTemplate * inset = new InsetMathMacroTemplate(cur.buffer(),
5760                                 from_utf8(token(s, ' ', 0)), nargs, false, type);
5761                         inset->setBuffer(bv->buffer());
5762                         insertInset(cur, inset);
5763
5764                         // enter macro inset and select the name
5765                         cur.push(*inset);
5766                         cur.top().pos() = cur.top().lastpos();
5767                         cur.resetAnchor();
5768                         cur.selection(true);
5769                         cur.top().pos() = 0;
5770                 }
5771                 break;
5772
5773         case LFUN_MATH_DISPLAY:
5774         case LFUN_MATH_SUBSCRIPT:
5775         case LFUN_MATH_SUPERSCRIPT:
5776         case LFUN_MATH_INSERT:
5777         case LFUN_MATH_AMS_MATRIX:
5778         case LFUN_MATH_MATRIX:
5779         case LFUN_MATH_DELIM:
5780         case LFUN_MATH_BIGDELIM:
5781                 mathDispatch(cur, cmd);
5782                 break;
5783
5784         case LFUN_FONT_EMPH: {
5785                 Font font(ignore_font, ignore_language);
5786                 font.fontInfo().setEmph(FONT_TOGGLE);
5787                 toggleAndShow(cur, this, font);
5788                 break;
5789         }
5790
5791         case LFUN_FONT_ITAL: {
5792                 Font font(ignore_font, ignore_language);
5793                 font.fontInfo().setShape(ITALIC_SHAPE);
5794                 toggleAndShow(cur, this, font);
5795                 break;
5796         }
5797
5798         case LFUN_FONT_BOLD:
5799         case LFUN_FONT_BOLDSYMBOL: {
5800                 Font font(ignore_font, ignore_language);
5801                 font.fontInfo().setSeries(BOLD_SERIES);
5802                 toggleAndShow(cur, this, font);
5803                 break;
5804         }
5805
5806         case LFUN_FONT_NOUN: {
5807                 Font font(ignore_font, ignore_language);
5808                 font.fontInfo().setNoun(FONT_TOGGLE);
5809                 toggleAndShow(cur, this, font);
5810                 break;
5811         }
5812
5813         case LFUN_FONT_TYPEWRITER: {
5814                 Font font(ignore_font, ignore_language);
5815                 font.fontInfo().setFamily(TYPEWRITER_FAMILY); // no good
5816                 toggleAndShow(cur, this, font);
5817                 break;
5818         }
5819
5820         case LFUN_FONT_SANS: {
5821                 Font font(ignore_font, ignore_language);
5822                 font.fontInfo().setFamily(SANS_FAMILY);
5823                 toggleAndShow(cur, this, font);
5824                 break;
5825         }
5826
5827         case LFUN_FONT_ROMAN: {
5828                 Font font(ignore_font, ignore_language);
5829                 font.fontInfo().setFamily(ROMAN_FAMILY);
5830                 toggleAndShow(cur, this, font);
5831                 break;
5832         }
5833
5834         case LFUN_FONT_DEFAULT: {
5835                 Font font(inherit_font, ignore_language);
5836                 toggleAndShow(cur, this, font);
5837                 break;
5838         }
5839
5840         case LFUN_FONT_STRIKEOUT: {
5841                 Font font(ignore_font, ignore_language);
5842                 font.fontInfo().setStrikeout(FONT_TOGGLE);
5843                 toggleAndShow(cur, this, font);
5844                 break;
5845         }
5846
5847         case LFUN_FONT_CROSSOUT: {
5848                 Font font(ignore_font, ignore_language);
5849                 font.fontInfo().setXout(FONT_TOGGLE);
5850                 toggleAndShow(cur, this, font);
5851                 break;
5852         }
5853
5854         case LFUN_FONT_UNDERUNDERLINE: {
5855                 Font font(ignore_font, ignore_language);
5856                 font.fontInfo().setUuline(FONT_TOGGLE);
5857                 toggleAndShow(cur, this, font);
5858                 break;
5859         }
5860
5861         case LFUN_FONT_UNDERWAVE: {
5862                 Font font(ignore_font, ignore_language);
5863                 font.fontInfo().setUwave(FONT_TOGGLE);
5864                 toggleAndShow(cur, this, font);
5865                 break;
5866         }
5867
5868         case LFUN_FONT_UNDERLINE: {
5869                 Font font(ignore_font, ignore_language);
5870                 font.fontInfo().setUnderbar(FONT_TOGGLE);
5871                 toggleAndShow(cur, this, font);
5872                 break;
5873         }
5874
5875         case LFUN_FONT_NO_SPELLCHECK: {
5876                 Font font(ignore_font, ignore_language);
5877                 font.fontInfo().setNoSpellcheck(FONT_TOGGLE);
5878                 toggleAndShow(cur, this, font);
5879                 break;
5880         }
5881
5882         case LFUN_FONT_SIZE: {
5883                 Font font(ignore_font, ignore_language);
5884                 setLyXSize(to_utf8(cmd.argument()), font.fontInfo());
5885                 toggleAndShow(cur, this, font);
5886                 break;
5887         }
5888
5889         case LFUN_LANGUAGE: {
5890                 string const lang_arg = cmd.getArg(0);
5891                 bool const reset = (lang_arg.empty() || lang_arg == "reset");
5892                 Language const * lang =
5893                         reset ? cur.bv().buffer().params().language
5894                               : languages.getLanguage(lang_arg);
5895                 // we allow reset_language, which is 0, but only if it
5896                 // was requested via empty or "reset" arg.
5897                 if (!lang && !reset)
5898                         break;
5899                 bool const toggle = (cmd.getArg(1) != "set");
5900                 selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
5901                 Font font(ignore_font, lang);
5902                 toggleAndShow(cur, this, font, toggle);
5903                 break;
5904         }
5905
5906         case LFUN_TEXTSTYLE_APPLY: {
5907                 unsigned int num = 0;
5908                 string const arg = to_utf8(cmd.argument());
5909                 // Argument?
5910                 if (!arg.empty()) {
5911                         if (isStrUnsignedInt(arg)) {
5912                                 num = convert<uint>(arg);
5913                                 if (num >= freeFonts.size()) {
5914                                         cur.message(_("Invalid argument (number exceeds stack size)!"));
5915                                         break;
5916                                 }
5917                         } else {
5918                                 cur.message(_("Invalid argument (must be a non-negative number)!"));
5919                                 break;
5920                         }
5921                 }
5922                 toggleAndShow(cur, this, freeFonts[num].second, toggleall);
5923                 cur.message(bformat(_("Text properties applied: %1$s"), freeFonts[num].first));
5924                 break;
5925         }
5926
5927         // Set the freefont using the contents of \param data dispatched from
5928         // the frontends and apply it at the current cursor location.
5929         case LFUN_TEXTSTYLE_UPDATE: {
5930                 Font font(ignore_font, ignore_language);
5931                 bool toggle;
5932                 if (font.fromString(to_utf8(cmd.argument()), toggle)) {
5933                         docstring const props = font.stateText(&bv->buffer().params(), true);
5934                         freeFonts.push(make_pair(props, font));
5935                         toggleall = toggle;
5936                         toggleAndShow(cur, this, font, toggleall);
5937                         cur.message(bformat(_("Text properties applied: %1$s"), props));
5938                 } else
5939                         LYXERR0("Invalid argument of textstyle-update");
5940                 break;
5941         }
5942
5943         case LFUN_FINISHED_LEFT:
5944                 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_LEFT:\n" << cur);
5945                 // We're leaving an inset, going left. If the inset is LTR, we're
5946                 // leaving from the front, so we should not move (remain at --- but
5947                 // not in --- the inset). If the inset is RTL, move left, without
5948                 // entering the inset itself; i.e., move to after the inset.
5949                 if (cur.paragraph().getFontSettings(
5950                                 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5951                         cursorVisLeft(cur, true);
5952                 break;
5953
5954         case LFUN_FINISHED_RIGHT:
5955                 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_RIGHT:\n" << cur);
5956                 // We're leaving an inset, going right. If the inset is RTL, we're
5957                 // leaving from the front, so we should not move (remain at --- but
5958                 // not in --- the inset). If the inset is LTR, move right, without
5959                 // entering the inset itself; i.e., move to after the inset.
5960                 if (!cur.paragraph().getFontSettings(
5961                                 cur.bv().buffer().params(), cur.pos()).isRightToLeft())
5962                         cursorVisRight(cur, true);
5963                 break;
5964
5965         case LFUN_FINISHED_BACKWARD:
5966                 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_BACKWARD:\n" << cur);
5967                 cur.setCurrentFont();
5968                 break;
5969
5970         case LFUN_FINISHED_FORWARD:
5971                 LYXERR(Debug::DEBUG, "handle LFUN_FINISHED_FORWARD:\n" << cur);
5972                 ++cur.pos();
5973                 cur.setCurrentFont();
5974                 break;
5975
5976         case LFUN_LAYOUT_PARAGRAPH: {
5977                 string data;
5978                 params2string(cur.paragraph(), data);
5979                 data = "show\n" + data;
5980                 bv->showDialog("paragraph", data);
5981                 break;
5982         }
5983
5984         case LFUN_PARAGRAPH_UPDATE: {
5985                 string data;
5986                 params2string(cur.paragraph(), data);
5987
5988                 // Will the paragraph accept changes from the dialog?
5989                 bool const accept =
5990                         cur.inset().allowParagraphCustomization(cur.idx());
5991
5992                 data = "update " + convert<string>(accept) + '\n' + data;
5993                 bv->updateDialog("paragraph", data);
5994                 break;
5995         }
5996
5997         case LFUN_ACCENT_UMLAUT:
5998         case LFUN_ACCENT_CIRCUMFLEX:
5999         case LFUN_ACCENT_GRAVE:
6000         case LFUN_ACCENT_ACUTE:
6001         case LFUN_ACCENT_TILDE:
6002         case LFUN_ACCENT_PERISPOMENI:
6003         case LFUN_ACCENT_CEDILLA:
6004         case LFUN_ACCENT_MACRON:
6005         case LFUN_ACCENT_DOT:
6006         case LFUN_ACCENT_UNDERDOT:
6007         case LFUN_ACCENT_UNDERBAR:
6008         case LFUN_ACCENT_CARON:
6009         case LFUN_ACCENT_BREVE:
6010         case LFUN_ACCENT_TIE:
6011         case LFUN_ACCENT_HUNGARIAN_UMLAUT:
6012         case LFUN_ACCENT_CIRCLE:
6013         case LFUN_ACCENT_OGONEK:
6014                 theApp()->handleKeyFunc(cmd.action());
6015                 if (!cmd.argument().empty())
6016                         // FIXME: Are all these characters encoded in one byte in utf8?
6017                         bv->translateAndInsert(cmd.argument()[0], this, cur);
6018                 cur.screenUpdateFlags(Update::FitCursor);
6019                 break;
6020
6021         case LFUN_FLOAT_LIST_INSERT: {
6022                 DocumentClass const & tclass = bv->buffer().params().documentClass();
6023                 if (tclass.floats().typeExist(to_utf8(cmd.argument()))) {
6024                         cur.recordUndo();
6025                         if (cur.selection())
6026                                 cutSelection(cur, false);
6027                         breakParagraph(cur);
6028
6029                         if (cur.lastpos() != 0) {
6030                                 cursorBackward(cur);
6031                                 breakParagraph(cur);
6032                         }
6033
6034                         docstring const laystr = cur.inset().usePlainLayout() ?
6035                                 tclass.plainLayoutName() :
6036                                 tclass.defaultLayoutName();
6037                         setLayout(cur, laystr);
6038                         ParagraphParameters p;
6039                         // FIXME If this call were replaced with one to clearParagraphParams(),
6040                         // then we could get rid of this method altogether.
6041                         setParagraphs(cur, p);
6042                         // FIXME This should be simplified when InsetFloatList takes a
6043                         // Buffer in its constructor.
6044                         InsetFloatList * ifl = new InsetFloatList(cur.buffer(), to_utf8(cmd.argument()));
6045                         ifl->setBuffer(bv->buffer());
6046                         insertInset(cur, ifl);
6047                         cur.posForward();
6048                 } else {
6049                         lyxerr << "Non-existent float type: "
6050                                << to_utf8(cmd.argument()) << endl;
6051                 }
6052                 break;
6053         }
6054
6055         case LFUN_CHANGE_ACCEPT: {
6056                 acceptOrRejectChanges(cur, ACCEPT);
6057                 break;
6058         }
6059
6060         case LFUN_CHANGE_REJECT: {
6061                 acceptOrRejectChanges(cur, REJECT);
6062                 break;
6063         }
6064
6065         case LFUN_THESAURUS_ENTRY: {
6066                 Language const * language = cur.getFont().language();
6067                 docstring arg = cmd.argument();
6068                 if (arg.empty()) {
6069                         arg = cur.selectionAsString(false);
6070                         // Too large. We unselect if needed and try to get
6071                         // the first word in selection or under cursor
6072                         if (arg.size() > 100 || arg.empty()) {
6073                                 if (cur.selection()) {
6074                                         DocIterator selbeg = cur.selectionBegin();
6075                                         cur.clearSelection();
6076                                         setCursorIntern(cur, selbeg.pit(), selbeg.pos());
6077                                         cur.screenUpdateFlags(Update::Force);
6078                                 }
6079                                 // Get word or selection
6080                                 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6081                                 arg = cur.selectionAsString(false);
6082                                 arg += " lang=" + from_ascii(language->lang());
6083                         }
6084                 } else {
6085                         string lang = cmd.getArg(1);
6086                         // This duplicates the code in GuiThesaurus::initialiseParams
6087                         if (prefixIs(lang, "lang=")) {
6088                                 language = languages.getLanguage(lang.substr(5));
6089                                 if (!language)
6090                                         language = cur.getFont().language();
6091                         }
6092                 }
6093                 string lang = language->code();
6094                 if (lyxrc.thesaurusdir_path.empty() && !thesaurus.thesaurusInstalled(from_ascii(lang))) {
6095                         LYXERR(Debug::ACTION, "Command " << cmd << ". Thesaurus not found for language " << lang);
6096                         frontend::Alert::warning(_("Path to thesaurus directory not set!"),
6097                                         _("The path to the thesaurus directory has not been specified.\n"
6098                                           "The thesaurus is not functional.\n"
6099                                           "Please refer to sec. 6.15.1 of the User's Guide for setup\n"
6100                                           "instructions."));
6101                 }
6102                 bv->showDialog("thesaurus", to_utf8(arg));
6103                 break;
6104         }
6105
6106         case LFUN_SPELLING_ADD: {
6107                 Language const * language = getLanguage(cur, cmd.getArg(1));
6108                 docstring word = from_utf8(cmd.getArg(0));
6109                 if (word.empty()) {
6110                         word = cur.selectionAsString(false);
6111                         // FIXME
6112                         if (word.size() > 100 || word.empty()) {
6113                                 // Get word or selection
6114                                 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6115                                 word = cur.selectionAsString(false);
6116                         }
6117                 }
6118                 WordLangTuple wl(word, language);
6119                 theSpellChecker()->insert(wl);
6120                 break;
6121         }
6122
6123         case LFUN_SPELLING_ADD_LOCAL: {
6124                 Language const * language = getLanguage(cur, cmd.getArg(1));
6125                 docstring word = from_utf8(cmd.getArg(0));
6126                 if (word.empty()) {
6127                         word = cur.selectionAsString(false);
6128                         if (word.size() > 100)
6129                                 break;
6130                         if (word.empty()) {
6131                                 // Get word or selection
6132                                 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6133                                 word = cur.selectionAsString(false);
6134                         }
6135                 }
6136                 WordLangTuple wl(word, language);
6137                 if (!bv->buffer().params().spellignored(wl)) {
6138                         cur.recordUndoBufferParams();
6139                         bv->buffer().params().spellignore().push_back(wl);
6140                         cur.recordUndo();
6141                         // trigger re-check of whole buffer
6142                         bv->buffer().requestSpellcheck();
6143                 }
6144                 break;
6145         }
6146
6147         case LFUN_SPELLING_REMOVE_LOCAL: {
6148                 Language const * language = getLanguage(cur, cmd.getArg(1));
6149                 docstring word = from_utf8(cmd.getArg(0));
6150                 if (word.empty()) {
6151                         word = cur.selectionAsString(false);
6152                         if (word.size() > 100)
6153                                 break;
6154                         if (word.empty()) {
6155                                 // Get word or selection
6156                                 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6157                                 word = cur.selectionAsString(false);
6158                         }
6159                 }
6160                 WordLangTuple wl(word, language);
6161                 bool has_item = false;
6162                 vector<WordLangTuple>::const_iterator it = bv->buffer().params().spellignore().begin();
6163                 for (; it != bv->buffer().params().spellignore().end(); ++it) {
6164                         if (it->lang()->code() != wl.lang()->code())
6165                                 continue;
6166                         if (it->word() == wl.word()) {
6167                                 has_item = true;
6168                                 break;
6169                         }
6170                 }
6171                 if (has_item) {
6172                         cur.recordUndoBufferParams();
6173                         bv->buffer().params().spellignore().erase(it);
6174                         cur.recordUndo();
6175                         // trigger re-check of whole buffer
6176                         bv->buffer().requestSpellcheck();
6177                 }
6178                 break;
6179         }
6180
6181
6182         case LFUN_SPELLING_IGNORE: {
6183                 Language const * language = getLanguage(cur, cmd.getArg(1));
6184                 docstring word = from_utf8(cmd.getArg(0));
6185                 if (word.empty()) {
6186                         word = cur.selectionAsString(false);
6187                         // FIXME
6188                         if (word.size() > 100 || word.empty()) {
6189                                 // Get word or selection
6190                                 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6191                                 word = cur.selectionAsString(false);
6192                         }
6193                 }
6194                 WordLangTuple wl(word, language);
6195                 theSpellChecker()->accept(wl);
6196                 break;
6197         }
6198
6199         case LFUN_SPELLING_REMOVE: {
6200                 Language const * language = getLanguage(cur, cmd.getArg(1));
6201                 docstring word = from_utf8(cmd.getArg(0));
6202                 if (word.empty()) {
6203                         word = cur.selectionAsString(false);
6204                         // FIXME
6205                         if (word.size() > 100 || word.empty()) {
6206                                 // Get word or selection
6207                                 selectWordWhenUnderCursor(cur, WHOLE_WORD);
6208                                 word = cur.selectionAsString(false);
6209                         }
6210                 }
6211                 WordLangTuple wl(word, language);
6212                 theSpellChecker()->remove(wl);
6213                 break;
6214         }
6215
6216         case LFUN_PARAGRAPH_PARAMS_APPLY: {
6217                 // Given data, an encoding of the ParagraphParameters
6218                 // generated in the Paragraph dialog, this function sets
6219                 // the current paragraph, or currently selected paragraphs,
6220                 // appropriately.
6221                 // NOTE: This function overrides all existing settings.
6222                 setParagraphs(cur, cmd.argument());
6223                 cur.message(_("Paragraph layout set"));
6224                 break;
6225         }
6226
6227         case LFUN_PARAGRAPH_PARAMS: {
6228                 // Given data, an encoding of the ParagraphParameters as we'd
6229                 // find them in a LyX file, this function modifies the current paragraph,
6230                 // or currently selected paragraphs.
6231                 // NOTE: This function only modifies, and does not override, existing
6232                 // settings.
6233                 setParagraphs(cur, cmd.argument(), true);
6234                 cur.message(_("Paragraph layout set"));
6235                 break;
6236         }
6237
6238         case LFUN_ESCAPE:
6239                 if (cur.selection()) {
6240                         cur.selection(false);
6241                 } else {
6242                         cur.undispatched();
6243                         // This used to be LFUN_FINISHED_RIGHT, I think FORWARD is more
6244                         // correct, but I'm not 100% sure -- dov, 071019
6245                         cmd = FuncRequest(LFUN_FINISHED_FORWARD);
6246                 }
6247                 break;
6248
6249         case LFUN_OUTLINE_UP: {
6250                 pos_type const opos = cur.pos();
6251                 outline(OutlineUp, cur);
6252                 setCursor(cur, cur.pit(), opos);
6253                 cur.forceBufferUpdate();
6254                 needsUpdate = true;
6255                 break;
6256         }
6257
6258         case LFUN_OUTLINE_DOWN: {
6259                 pos_type const opos = cur.pos();
6260                 outline(OutlineDown, cur);
6261                 setCursor(cur, cur.pit(), opos);
6262                 cur.forceBufferUpdate();
6263                 needsUpdate = true;
6264                 break;
6265         }
6266
6267         case LFUN_OUTLINE_IN:
6268                 outline(OutlineIn, cur);
6269                 cur.forceBufferUpdate();
6270                 needsUpdate = true;
6271                 break;
6272
6273         case LFUN_OUTLINE_OUT:
6274                 outline(OutlineOut, cur);
6275                 cur.forceBufferUpdate();
6276                 needsUpdate = true;
6277                 break;
6278
6279         case LFUN_SERVER_GET_STATISTICS: {
6280                 DocIterator from, to;
6281                 if (cur.selection()) {
6282                         from = cur.selectionBegin();
6283                         to = cur.selectionEnd();
6284                 } else {
6285                         from = doc_iterator_begin(cur.buffer());
6286                         to = doc_iterator_end(cur.buffer());
6287                 }
6288
6289                 cur.buffer()->updateStatistics(from, to);
6290                 string const arg0 = cmd.getArg(0);
6291                 if (arg0 == "words") {
6292                         cur.message(convert<docstring>(cur.buffer()->wordCount()));
6293                 } else if (arg0 == "chars") {
6294                         cur.message(convert<docstring>(cur.buffer()->charCount(false)));
6295                 } else if (arg0 == "chars-space") {
6296                         cur.message(convert<docstring>(cur.buffer()->charCount(true)));
6297                 } else {
6298                         cur.message(convert<docstring>(cur.buffer()->wordCount()) + " "
6299                         + convert<docstring>(cur.buffer()->charCount(false)) + " "
6300                         + convert<docstring>(cur.buffer()->charCount(true)));
6301                 }
6302                 break;
6303         }
6304
6305         default:
6306                 LYXERR(Debug::ACTION, "Command " << cmd << " not DISPATCHED by Text");
6307                 cur.undispatched();
6308                 break;
6309         }
6310
6311         needsUpdate |= (cur.pos() != cur.lastpos()) && cur.selection();
6312
6313         if (lyxrc.spellcheck_continuously && !needsUpdate) {
6314                 // Check for misspelled text
6315                 // The redraw is useful because of the painting of
6316                 // misspelled markers depends on the cursor position.
6317                 // Trigger a redraw for cursor moves inside misspelled text.
6318                 if (!cur.inTexted()) {
6319                         // move from regular text to math
6320                         needsUpdate = last_misspelled;
6321                 } else if (oldTopSlice != cur.top() || oldBoundary != cur.boundary()) {
6322                         // move inside regular text
6323                         needsUpdate = last_misspelled
6324                                 || cur.paragraph().isMisspelled(cur.pos(), true);
6325                 }
6326         }
6327
6328         // FIXME: The cursor flag is reset two lines below
6329         // so we need to check here if some of the LFUN did touch that.
6330         // for now only Text::erase() and Text::backspace() do that.
6331         // The plan is to verify all the LFUNs and then to remove this
6332         // singleParUpdate boolean altogether.
6333         if (cur.result().screenUpdate() & Update::Force) {
6334                 singleParUpdate = false;
6335                 needsUpdate = true;
6336         }
6337
6338         // FIXME: the following code should go in favor of fine grained
6339         // update flag treatment.
6340         if (singleParUpdate) {
6341                 // Inserting characters does not change par height in general. So, try
6342                 // to update _only_ this paragraph. BufferView will detect if a full
6343                 // metrics update is needed anyway.
6344                 cur.screenUpdateFlags(Update::SinglePar | Update::FitCursor);
6345                 return;
6346         }
6347         if (!needsUpdate
6348             && &oldTopSlice.inset() == &cur.inset()
6349             && oldTopSlice.idx() == cur.idx()
6350             && !oldSelection // oldSelection is a backup of cur.selection() at the beginning of the function.
6351             && !cur.selection())
6352                 // FIXME: it would be better if we could just do this
6353                 //
6354                 //if (cur.result().update() != Update::FitCursor)
6355                 //      cur.noScreenUpdate();
6356                 //
6357                 // But some LFUNs do not set Update::FitCursor when needed, so we
6358                 // do it for all. This is not very harmfull as FitCursor will provoke
6359                 // a full redraw only if needed but still, a proper review of all LFUN
6360                 // should be done and this needsUpdate boolean can then be removed.
6361                 cur.screenUpdateFlags(Update::FitCursor);
6362         else
6363                 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
6364 }
6365
6366
6367 bool Text::getStatus(Cursor & cur, FuncRequest const & cmd,
6368                         FuncStatus & status) const
6369 {
6370         LBUFERR(this == cur.text());
6371
6372         FontInfo const & fontinfo = cur.real_current_font.fontInfo();
6373         bool enable = true;
6374         bool allow_in_passthru = false;
6375         InsetCode code = NO_CODE;
6376
6377         switch (cmd.action()) {
6378
6379         case LFUN_DEPTH_DECREMENT:
6380                 enable = changeDepthAllowed(cur, DEC_DEPTH);
6381                 break;
6382
6383         case LFUN_DEPTH_INCREMENT:
6384                 enable = changeDepthAllowed(cur, INC_DEPTH);
6385                 break;
6386
6387         case LFUN_APPENDIX:
6388                 // FIXME We really should not allow this to be put, e.g.,
6389                 // in a footnote, or in ERT. But it would make sense in a
6390                 // branch, so I'm not sure what to do.
6391                 status.setOnOff(cur.paragraph().params().startOfAppendix());
6392                 break;
6393
6394         case LFUN_DIALOG_SHOW_NEW_INSET:
6395                 if (cmd.argument() == "bibitem")
6396                         code = BIBITEM_CODE;
6397                 else if (cmd.argument() == "bibtex") {
6398                         code = BIBTEX_CODE;
6399                         // not allowed in description items
6400                         enable = !inDescriptionItem(cur);
6401                 }
6402                 else if (cmd.argument() == "box")
6403                         code = BOX_CODE;
6404                 else if (cmd.argument() == "branch")
6405                         code = BRANCH_CODE;
6406                 else if (cmd.argument() == "citation")
6407                         code = CITE_CODE;
6408                 else if (cmd.argument() == "counter")
6409                         code = COUNTER_CODE;
6410                 else if (cmd.argument() == "ert")
6411                         code = ERT_CODE;
6412                 else if (cmd.argument() == "external")
6413                         code = EXTERNAL_CODE;
6414                 else if (cmd.argument() == "float")
6415                         code = FLOAT_CODE;
6416                 else if (cmd.argument() == "graphics")
6417                         code = GRAPHICS_CODE;
6418                 else if (cmd.argument() == "href")
6419                         code = HYPERLINK_CODE;
6420                 else if (cmd.argument() == "include")
6421                         code = INCLUDE_CODE;
6422                 else if (cmd.argument() == "index")
6423                         code = INDEX_CODE;
6424                 else if (cmd.argument() == "index_print")
6425                         code = INDEX_PRINT_CODE;
6426                 else if (cmd.argument() == "listings")
6427                         code = LISTINGS_CODE;
6428                 else if (cmd.argument() == "mathspace")
6429                         code = MATH_HULL_CODE;
6430                 else if (cmd.argument() == "nomenclature")
6431                         code = NOMENCL_CODE;
6432                 else if (cmd.argument() == "nomencl_print")
6433                         code = NOMENCL_PRINT_CODE;
6434                 else if (cmd.argument() == "label")
6435                         code = LABEL_CODE;
6436                 else if (cmd.argument() == "line")
6437                         code = LINE_CODE;
6438                 else if (cmd.argument() == "note")
6439                         code = NOTE_CODE;
6440                 else if (cmd.argument() == "phantom")
6441                         code = PHANTOM_CODE;
6442                 else if (cmd.argument() == "ref")
6443                         code = REF_CODE;
6444                 else if (cmd.argument() == "space")
6445                         code = SPACE_CODE;
6446                 else if (cmd.argument() == "toc")
6447                         code = TOC_CODE;
6448                 else if (cmd.argument() == "vspace")
6449                         code = VSPACE_CODE;
6450                 else if (cmd.argument() == "wrap")
6451                         code = WRAP_CODE;
6452                 break;
6453
6454         case LFUN_ERT_INSERT:
6455                 code = ERT_CODE;
6456                 break;
6457         case LFUN_LISTING_INSERT:
6458                 code = LISTINGS_CODE;
6459                 // not allowed in description items
6460                 enable = !inDescriptionItem(cur);
6461                 break;
6462         case LFUN_FOOTNOTE_INSERT:
6463                 code = FOOT_CODE;
6464                 break;
6465         case LFUN_TABULAR_INSERT:
6466                 code = TABULAR_CODE;
6467                 break;
6468         case LFUN_TABULAR_STYLE_INSERT:
6469                 code = TABULAR_CODE;
6470                 break;
6471         case LFUN_MARGINALNOTE_INSERT:
6472                 code = MARGIN_CODE;
6473                 break;
6474         case LFUN_FLOAT_INSERT:
6475         case LFUN_FLOAT_WIDE_INSERT:
6476                 // FIXME: If there is a selection, we should check whether there
6477                 // are floats in the selection, but this has performance issues, see
6478                 // LFUN_CHANGE_ACCEPT/REJECT.
6479                 code = FLOAT_CODE;
6480                 if (inDescriptionItem(cur))
6481                         // not allowed in description items
6482                         enable = false;
6483                 else {
6484                         InsetCode const inset_code = cur.inset().lyxCode();
6485
6486                         // algorithm floats cannot be put in another float
6487                         if (to_utf8(cmd.argument()) == "algorithm") {
6488                                 enable = inset_code != WRAP_CODE && inset_code != FLOAT_CODE;
6489                                 break;
6490                         }
6491
6492                         // for figures and tables: only allow in another
6493                         // float or wrap if it is of the same type and
6494                         // not a subfloat already
6495                         if(cur.inset().lyxCode() == code) {
6496                                 InsetFloat const & ins =
6497                                         static_cast<InsetFloat const &>(cur.inset());
6498                                 enable = ins.params().type == to_utf8(cmd.argument())
6499                                         && !ins.params().subfloat;
6500                         } else if(cur.inset().lyxCode() == WRAP_CODE) {
6501                                 InsetWrap const & ins =
6502                                         static_cast<InsetWrap const &>(cur.inset());
6503                                 enable = ins.params().type == to_utf8(cmd.argument());
6504                         }
6505                 }
6506                 break;
6507         case LFUN_WRAP_INSERT:
6508                 code = WRAP_CODE;
6509                 // not allowed in description items
6510                 enable = !inDescriptionItem(cur);
6511                 break;
6512         case LFUN_FLOAT_LIST_INSERT: {
6513                 code = FLOAT_LIST_CODE;
6514                 // not allowed in description items
6515                 enable = !inDescriptionItem(cur);
6516                 if (enable) {
6517                         FloatList const & floats = cur.buffer()->params().documentClass().floats();
6518                         FloatList::const_iterator cit = floats[to_ascii(cmd.argument())];
6519                         // make sure we know about such floats
6520                         if (cit == floats.end() ||
6521                                         // and that we know how to generate a list of them
6522                             (!cit->second.usesFloatPkg() && cit->second.listCommand().empty())) {
6523                                 status.setUnknown(true);
6524                                 // probably not necessary, but...
6525                                 enable = false;
6526                         }
6527                 }
6528                 break;
6529         }
6530         case LFUN_CAPTION_INSERT: {
6531                 code = CAPTION_CODE;
6532                 string arg = cmd.getArg(0);
6533                 bool varia = arg != "Unnumbered"
6534                         && cur.inset().allowsCaptionVariation(arg);
6535                 // not allowed in description items,
6536                 // and in specific insets
6537                 enable = !inDescriptionItem(cur)
6538                         && (varia || arg.empty() || arg == "Standard");
6539                 break;
6540         }
6541         case LFUN_NOTE_INSERT:
6542                 code = NOTE_CODE;
6543                 break;
6544         case LFUN_FLEX_INSERT: {
6545                 code = FLEX_CODE;
6546                 docstring s = from_utf8(cmd.getArg(0));
6547                 // Prepend "Flex:" prefix if not there
6548                 if (!prefixIs(s, from_ascii("Flex:")))
6549                         s = from_ascii("Flex:") + s;
6550                 if (!cur.buffer()->params().documentClass().hasInsetLayout(s))
6551                         enable = false;
6552                 else {
6553                         InsetLyXType ilt =
6554                                 cur.buffer()->params().documentClass().insetLayout(s).lyxtype();
6555                         if (ilt != InsetLyXType::CHARSTYLE
6556                             && ilt != InsetLyXType::CUSTOM
6557                             && ilt != InsetLyXType::STANDARD)
6558                                 enable = false;
6559                 }
6560                 break;
6561         }
6562         case LFUN_BOX_INSERT:
6563                 code = BOX_CODE;
6564                 break;
6565         case LFUN_BRANCH_INSERT:
6566                 code = BRANCH_CODE;
6567                 if (cur.buffer()->masterBuffer()->params().branchlist().empty()
6568                     && cur.buffer()->params().branchlist().empty())
6569                         enable = false;
6570                 break;
6571         case LFUN_IPA_INSERT:
6572                 code = IPA_CODE;
6573                 break;
6574         case LFUN_PHANTOM_INSERT:
6575                 code = PHANTOM_CODE;
6576                 break;
6577         case LFUN_LABEL_INSERT:
6578                 code = LABEL_CODE;
6579                 break;
6580         case LFUN_INFO_INSERT:
6581                 code = INFO_CODE;
6582                 enable = cmd.argument().empty()
6583                         || infoparams.validateArgument(cur.buffer(), cmd.argument(), true);
6584                 break;
6585         case LFUN_ARGUMENT_INSERT: {
6586                 code = ARG_CODE;
6587                 allow_in_passthru = true;
6588                 string const arg = cmd.getArg(0);
6589                 if (arg.empty()) {
6590                         enable = false;
6591                         break;
6592                 }
6593                 Layout const & lay = cur.paragraph().layout();
6594                 Layout::LaTeXArgMap args = lay.args();
6595                 Layout::LaTeXArgMap::const_iterator const lait =
6596                                 args.find(arg);
6597                 if (lait != args.end()) {
6598                         enable = true;
6599                         pit_type pit = cur.pit();
6600                         pit_type lastpit = cur.pit();
6601                         if (lay.isEnvironment() && !prefixIs(arg, "item:")) {
6602                                 // In a sequence of "merged" environment layouts, we only allow
6603                                 // non-item arguments once.
6604                                 lastpit = cur.lastpit();
6605                                 // get the first paragraph in sequence with this layout
6606                                 depth_type const current_depth = cur.paragraph().params().depth();
6607                                 while (true) {
6608                                         if (pit == 0)
6609                                                 break;
6610                                         Paragraph cpar = pars_[pit - 1];
6611                                         if (cpar.layout() == lay && cpar.params().depth() == current_depth)
6612                                                 --pit;
6613                                         else
6614                                                 break;
6615                                 }
6616                         }
6617                         for (; pit <= lastpit; ++pit) {
6618                                 if (pars_[pit].layout() != lay)
6619                                         break;
6620                                 for (auto const & table : pars_[pit].insetList())
6621                                         if (InsetArgument const * ins = table.inset->asInsetArgument())
6622                                                 if (ins->name() == arg) {
6623                                                         // we have this already
6624                                                         enable = false;
6625                                                         break;
6626                                                 }
6627                         }
6628                 } else
6629                         enable = false;
6630                 break;
6631         }
6632         case LFUN_INDEX_INSERT:
6633                 code = INDEX_CODE;
6634                 break;
6635         case LFUN_INDEX_PRINT:
6636                 code = INDEX_PRINT_CODE;
6637                 // not allowed in description items
6638                 enable = !inDescriptionItem(cur);
6639                 break;
6640         case LFUN_NOMENCL_INSERT:
6641                 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6642                         enable = false;
6643                         break;
6644                 }
6645                 code = NOMENCL_CODE;
6646                 break;
6647         case LFUN_NOMENCL_PRINT:
6648                 code = NOMENCL_PRINT_CODE;
6649                 // not allowed in description items
6650                 enable = !inDescriptionItem(cur);
6651                 break;
6652         case LFUN_HREF_INSERT:
6653                 if (cur.selIsMultiCell() || cur.selIsMultiLine()) {
6654                         enable = false;
6655                         break;
6656                 }
6657                 code = HYPERLINK_CODE;
6658                 break;
6659         case LFUN_INDEXMACRO_INSERT: {
6660                 string const arg = cmd.getArg(0);
6661                 if (arg == "sortkey")
6662                         code = INDEXMACRO_SORTKEY_CODE;
6663                 else
6664                         code = INDEXMACRO_CODE;
6665                 break;
6666         }
6667         case LFUN_IPAMACRO_INSERT: {
6668                 string const arg = cmd.getArg(0);
6669                 if (arg == "deco")
6670                         code = IPADECO_CODE;
6671                 else
6672                         code = IPACHAR_CODE;
6673                 break;
6674         }
6675         case LFUN_QUOTE_INSERT:
6676                 // always allow this, since we will inset a raw quote
6677                 // if an inset is not allowed.
6678                 allow_in_passthru = true;
6679                 break;
6680         case LFUN_SPECIALCHAR_INSERT:
6681                 code = SPECIALCHAR_CODE;
6682                 break;
6683         case LFUN_SPACE_INSERT:
6684                 // slight hack: we know this is allowed in math mode
6685                 if (cur.inTexted())
6686                         code = SPACE_CODE;
6687                 break;
6688         case LFUN_PREVIEW_INSERT:
6689                 code = PREVIEW_CODE;
6690                 break;
6691         case LFUN_SCRIPT_INSERT:
6692                 code = SCRIPT_CODE;
6693                 break;
6694
6695         case LFUN_MATH_INSERT:
6696         case LFUN_MATH_AMS_MATRIX:
6697         case LFUN_MATH_MATRIX:
6698         case LFUN_MATH_DELIM:
6699         case LFUN_MATH_BIGDELIM:
6700         case LFUN_MATH_DISPLAY:
6701         case LFUN_MATH_MODE:
6702         case LFUN_MATH_MACRO:
6703         case LFUN_MATH_SUBSCRIPT:
6704         case LFUN_MATH_SUPERSCRIPT:
6705                 code = MATH_HULL_CODE;
6706                 break;
6707
6708         case LFUN_REGEXP_MODE:
6709                 code = MATH_HULL_CODE;
6710                 enable = cur.buffer()->isInternal() && !cur.inRegexped();
6711                 break;
6712
6713         case LFUN_INSET_MODIFY:
6714                 // We need to disable this, because we may get called for a
6715                 // tabular cell via
6716                 // InsetTabular::getStatus() -> InsetText::getStatus()
6717                 // and we don't handle LFUN_INSET_MODIFY.
6718                 enable = false;
6719                 break;
6720
6721         case LFUN_FONT_EMPH:
6722                 status.setOnOff(fontinfo.emph() == FONT_ON);
6723                 enable = !cur.paragraph().isPassThru();
6724                 break;
6725
6726         case LFUN_FONT_ITAL:
6727                 status.setOnOff(fontinfo.shape() == ITALIC_SHAPE);
6728                 enable = !cur.paragraph().isPassThru();
6729                 break;
6730
6731         case LFUN_FONT_NOUN:
6732                 status.setOnOff(fontinfo.noun() == FONT_ON);
6733                 enable = !cur.paragraph().isPassThru();
6734                 break;
6735
6736         case LFUN_FONT_BOLD:
6737         case LFUN_FONT_BOLDSYMBOL:
6738                 status.setOnOff(fontinfo.series() == BOLD_SERIES);
6739                 enable = !cur.paragraph().isPassThru();
6740                 break;
6741
6742         case LFUN_FONT_SANS:
6743                 status.setOnOff(fontinfo.family() == SANS_FAMILY);
6744                 enable = !cur.paragraph().isPassThru();
6745                 break;
6746
6747         case LFUN_FONT_ROMAN:
6748                 status.setOnOff(fontinfo.family() == ROMAN_FAMILY);
6749                 enable = !cur.paragraph().isPassThru();
6750                 break;
6751
6752         case LFUN_FONT_TYPEWRITER:
6753                 status.setOnOff(fontinfo.family() == TYPEWRITER_FAMILY);
6754                 enable = !cur.paragraph().isPassThru();
6755                 break;
6756
6757         case LFUN_CUT:
6758                 enable = cur.selection();
6759                 break;
6760
6761         case LFUN_PASTE: {
6762                 if (cmd.argument().empty()) {
6763                         if (theClipboard().isInternal())
6764                                 enable = cap::numberOfSelections() > 0;
6765                         else
6766                                 enable = !theClipboard().empty();
6767                         break;
6768                 }
6769
6770                 // we have an argument
6771                 string const arg = to_utf8(cmd.argument());
6772                 if (isStrUnsignedInt(arg)) {
6773                         // it's a number and therefore means the internal stack
6774                         unsigned int n = convert<unsigned int>(arg);
6775                         enable = cap::numberOfSelections() > n;
6776                         break;
6777                 }
6778
6779                 // explicit text type?
6780                 if (arg == "html") {
6781                         // Do not enable for PlainTextType, since some tidying in the
6782                         // frontend is needed for HTML, which is too unsafe for plain text.
6783                         enable = theClipboard().hasTextContents(Clipboard::HtmlTextType);
6784                         break;
6785                 } else if (arg == "latex") {
6786                         // LaTeX is usually not available on the clipboard with
6787                         // the correct MIME type, but in plain text.
6788                         enable = theClipboard().hasTextContents(Clipboard::PlainTextType) ||
6789                                  theClipboard().hasTextContents(Clipboard::LaTeXTextType);
6790                         break;
6791                 }
6792
6793                 Clipboard::GraphicsType type = Clipboard::AnyGraphicsType;
6794                 if (arg == "pdf")
6795                         type = Clipboard::PdfGraphicsType;
6796                 else if (arg == "png")
6797                         type = Clipboard::PngGraphicsType;
6798                 else if (arg == "jpeg")
6799                         type = Clipboard::JpegGraphicsType;
6800                 else if (arg == "linkback")
6801                         type = Clipboard::LinkBackGraphicsType;
6802                 else if (arg == "emf")
6803                         type = Clipboard::EmfGraphicsType;
6804                 else if (arg == "wmf")
6805                         type = Clipboard::WmfGraphicsType;
6806                 else {
6807                         // unknown argument
6808                         LYXERR0("Unrecognized graphics type: " << arg);
6809                         // we don't want to assert if the user just mistyped the LFUN
6810                         LATTEST(cmd.origin() != FuncRequest::INTERNAL);
6811                         enable = false;
6812                         break;
6813                 }
6814                 enable = theClipboard().hasGraphicsContents(type);
6815                 break;
6816         }
6817
6818         case LFUN_CLIPBOARD_PASTE:
6819         case LFUN_CLIPBOARD_PASTE_SIMPLE:
6820                 enable = !theClipboard().empty();
6821                 break;
6822
6823         case LFUN_PRIMARY_SELECTION_PASTE:
6824                 status.setUnknown(!theSelection().supported());
6825                 enable = cur.selection() || !theSelection().empty();
6826                 break;
6827
6828         case LFUN_SELECTION_PASTE:
6829                 enable = cap::selection();
6830                 break;
6831
6832         case LFUN_PARAGRAPH_MOVE_UP:
6833                 enable = cur.pit() > 0 && !cur.selection();
6834                 break;
6835
6836         case LFUN_PARAGRAPH_MOVE_DOWN:
6837                 enable = cur.pit() < cur.lastpit() && !cur.selection();
6838                 break;
6839
6840         case LFUN_CHANGE_ACCEPT:
6841         case LFUN_CHANGE_REJECT:
6842                 if (!cur.selection())
6843                         enable = cur.paragraph().isChanged(cur.pos());
6844                 else {
6845                         // will enable if there is a change in the selection
6846                         enable = false;
6847
6848                         // cheap improvement for efficiency: using cached
6849                         // buffer variable, if there is no change in the
6850                         // document, no need to check further.
6851                         if (!cur.buffer()->areChangesPresent())
6852                                 break;
6853
6854                         for (DocIterator it = cur.selectionBegin(); ; it.forwardPar()) {
6855                                 pos_type const beg = it.pos();
6856                                 pos_type end;
6857                                 bool const in_last_par = (it.pit() == cur.selectionEnd().pit() &&
6858                                                           it.idx() == cur.selectionEnd().idx());
6859                                 if (in_last_par)
6860                                         end = cur.selectionEnd().pos();
6861                                 else
6862                                         // the +1 is needed for cases, e.g., where there is a
6863                                         // paragraph break. See #11629.
6864                                         end = it.lastpos() + 1;
6865                                 if (beg != end && it.paragraph().isChanged(beg, end)) {
6866                                         enable = true;
6867                                         break;
6868                                 }
6869                                 if (beg != end && it.paragraph().hasChangedInsets(beg, end)) {
6870                                         enable = true;
6871                                         break;
6872                                 }
6873                                 if (in_last_par)
6874                                         break;
6875                         }
6876                 }
6877                 break;
6878
6879         case LFUN_OUTLINE_UP:
6880         case LFUN_OUTLINE_DOWN:
6881         case LFUN_OUTLINE_IN:
6882         case LFUN_OUTLINE_OUT:
6883                 enable = cur.text()->getTocLevel(cur.pit()) != Layout::NOT_IN_TOC;
6884                 break;
6885
6886         case LFUN_NEWLINE_INSERT:
6887                 // LaTeX restrictions (labels or empty par)
6888                 enable = !cur.paragraph().isPassThru()
6889                         && cur.pos() > cur.paragraph().beginOfBody();
6890                 break;
6891
6892         case LFUN_SEPARATOR_INSERT:
6893                 // Always enabled for now
6894                 enable = true;
6895                 break;
6896
6897         case LFUN_TAB_INSERT:
6898         case LFUN_TAB_DELETE:
6899                 enable = cur.paragraph().isPassThru();
6900                 break;
6901
6902         case LFUN_GRAPHICS_SET_GROUP: {
6903                 InsetGraphics * ins = graphics::getCurrentGraphicsInset(cur);
6904                 if (!ins)
6905                         enable = false;
6906                 else
6907                         status.setOnOff(to_utf8(cmd.argument()) == ins->getParams().groupId);
6908                 break;
6909         }
6910
6911         case LFUN_NEWPAGE_INSERT:
6912                 // not allowed in description items
6913                 code = NEWPAGE_CODE;
6914                 enable = !inDescriptionItem(cur);
6915                 break;
6916
6917         case LFUN_LANGUAGE:
6918                 enable = !cur.paragraph().isPassThru();
6919                 status.setOnOff(cmd.getArg(0) == cur.real_current_font.language()->lang());
6920                 break;
6921
6922         case LFUN_PARAGRAPH_BREAK:
6923                 enable = inset().allowMultiPar();
6924                 break;
6925
6926         case LFUN_SPELLING_ADD:
6927         case LFUN_SPELLING_ADD_LOCAL:
6928         case LFUN_SPELLING_REMOVE_LOCAL:
6929         case LFUN_SPELLING_IGNORE:
6930         case LFUN_SPELLING_REMOVE:
6931                 enable = theSpellChecker() != nullptr;
6932                 if (enable && !cmd.getArg(1).empty()) {
6933                         // validate explicitly given language
6934                         Language const * const lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1)));
6935                         enable &= lang != nullptr;
6936                 }
6937                 break;
6938
6939         case LFUN_LAYOUT:
6940         case LFUN_LAYOUT_TOGGLE: {
6941                 bool const ignoreautonests = cmd.getArg(1) == "ignoreautonests";
6942                 docstring const req_layout = ignoreautonests ? from_utf8(cmd.getArg(0)) : cmd.argument();
6943                 docstring const layout = resolveLayout(req_layout, cur);
6944
6945                 // FIXME: make this work in multicell selection case
6946                 enable = !owner_->forcePlainLayout() && !layout.empty() && !cur.selIsMultiCell();
6947                 status.setOnOff(!owner_->forcePlainLayout() && !cur.selIsMultiCell()
6948                                 && isAlreadyLayout(layout, cur));
6949                 break;
6950         }
6951
6952         case LFUN_ENVIRONMENT_SPLIT: {
6953                 if (cmd.argument() == "outer") {
6954                         // check if we have an environment in our nesting hierarchy
6955                         bool res = false;
6956                         depth_type const current_depth = cur.paragraph().params().depth();
6957                         pit_type pit = cur.pit();
6958                         Paragraph cpar = pars_[pit];
6959                         while (true) {
6960                                 if (pit == 0 || cpar.params().depth() == 0)
6961                                         break;
6962                                 --pit;
6963                                 cpar = pars_[pit];
6964                                 if (cpar.params().depth() < current_depth)
6965                                         res = cpar.layout().isEnvironment();
6966                         }
6967                         enable = res;
6968                         break;
6969                 }
6970                 else if (cmd.argument() == "previous") {
6971                         // look if we have an environment in the previous par
6972                         pit_type pit = cur.pit();
6973                         Paragraph cpar = pars_[pit];
6974                         if (pit > 0) {
6975                                 --pit;
6976                                 cpar = pars_[pit];
6977                                 enable = cpar.layout().isEnvironment();
6978                                 break;
6979                         }
6980                         enable = false;
6981                         break;
6982                 }
6983                 else if (cur.paragraph().layout().isEnvironment()) {
6984                         enable = cmd.argument() == "before"
6985                                 || cur.pos() > 0 || !isFirstInSequence(cur.pit());
6986                         break;
6987                 }
6988                 enable = false;
6989                 break;
6990         }
6991
6992         case LFUN_LAYOUT_PARAGRAPH:
6993         case LFUN_PARAGRAPH_PARAMS:
6994         case LFUN_PARAGRAPH_PARAMS_APPLY:
6995         case LFUN_PARAGRAPH_UPDATE:
6996                 enable = owner_->allowParagraphCustomization();
6997                 break;
6998
6999         // FIXME: why are accent lfuns forbidden with pass_thru layouts?
7000         //  Because they insert COMBINING DIACRITICAL Unicode characters,
7001         //  that cannot be handled by LaTeX but must be converted according
7002         //  to the definition in lib/unicodesymbols?
7003         case LFUN_ACCENT_ACUTE:
7004         case LFUN_ACCENT_BREVE:
7005         case LFUN_ACCENT_CARON:
7006         case LFUN_ACCENT_CEDILLA:
7007         case LFUN_ACCENT_CIRCLE:
7008         case LFUN_ACCENT_CIRCUMFLEX:
7009         case LFUN_ACCENT_DOT:
7010         case LFUN_ACCENT_GRAVE:
7011         case LFUN_ACCENT_HUNGARIAN_UMLAUT:
7012         case LFUN_ACCENT_MACRON:
7013         case LFUN_ACCENT_OGONEK:
7014         case LFUN_ACCENT_TIE:
7015         case LFUN_ACCENT_TILDE:
7016         case LFUN_ACCENT_PERISPOMENI:
7017         case LFUN_ACCENT_UMLAUT:
7018         case LFUN_ACCENT_UNDERBAR:
7019         case LFUN_ACCENT_UNDERDOT:
7020         case LFUN_FONT_FRAK:
7021         case LFUN_FONT_SIZE:
7022         case LFUN_FONT_STATE:
7023         case LFUN_FONT_UNDERLINE:
7024         case LFUN_FONT_STRIKEOUT:
7025         case LFUN_FONT_CROSSOUT:
7026         case LFUN_FONT_UNDERUNDERLINE:
7027         case LFUN_FONT_UNDERWAVE:
7028         case LFUN_FONT_NO_SPELLCHECK:
7029         case LFUN_TEXTSTYLE_UPDATE:
7030                 enable = !cur.paragraph().isPassThru();
7031                 break;
7032
7033         case LFUN_FONT_DEFAULT: {
7034                 Font font(inherit_font, ignore_language);
7035                 BufferParams const & bp = cur.buffer()->masterParams();
7036                 if (cur.selection()) {
7037                         enable = false;
7038                         // Check if we have a non-default font attribute
7039                         // in the selection range.
7040                         DocIterator const from = cur.selectionBegin();
7041                         DocIterator const to = cur.selectionEnd();
7042                         for (DocIterator dit = from ; dit != to && !dit.atEnd(); ) {
7043                                 if (!dit.inTexted()) {
7044                                         dit.forwardPos();
7045                                         continue;
7046                                 }
7047                                 Paragraph const & par = dit.paragraph();
7048                                 pos_type const pos = dit.pos();
7049                                 Font tmp = par.getFontSettings(bp, pos);
7050                                 if (tmp.fontInfo() != font.fontInfo()
7051                                     || tmp.language() != bp.language) {
7052                                         enable = true;
7053                                         break;
7054                                 }
7055                                 dit.forwardPos();
7056                         }
7057                         break;
7058                 }
7059                 // Disable if all is default already.
7060                 enable = (cur.current_font.fontInfo() != font.fontInfo()
7061                           || cur.current_font.language() != bp.language);
7062                 break;
7063         }
7064
7065         case LFUN_TEXTSTYLE_APPLY:
7066                 enable = !freeFonts.empty();
7067                 break;
7068
7069         case LFUN_WORD_DELETE_FORWARD:
7070         case LFUN_WORD_DELETE_BACKWARD:
7071         case LFUN_LINE_DELETE_FORWARD:
7072         case LFUN_WORD_FORWARD:
7073         case LFUN_WORD_BACKWARD:
7074         case LFUN_WORD_RIGHT:
7075         case LFUN_WORD_LEFT:
7076         case LFUN_CHAR_FORWARD:
7077         case LFUN_CHAR_FORWARD_SELECT:
7078         case LFUN_CHAR_BACKWARD:
7079         case LFUN_CHAR_BACKWARD_SELECT:
7080         case LFUN_CHAR_LEFT:
7081         case LFUN_CHAR_LEFT_SELECT:
7082         case LFUN_CHAR_RIGHT:
7083         case LFUN_CHAR_RIGHT_SELECT:
7084         case LFUN_UP:
7085         case LFUN_UP_SELECT:
7086         case LFUN_DOWN:
7087         case LFUN_DOWN_SELECT:
7088         case LFUN_PARAGRAPH_SELECT:
7089         case LFUN_PARAGRAPH_UP_SELECT:
7090         case LFUN_PARAGRAPH_DOWN_SELECT:
7091         case LFUN_LINE_BEGIN_SELECT:
7092         case LFUN_LINE_END_SELECT:
7093         case LFUN_WORD_FORWARD_SELECT:
7094         case LFUN_WORD_BACKWARD_SELECT:
7095         case LFUN_WORD_RIGHT_SELECT:
7096         case LFUN_WORD_LEFT_SELECT:
7097         case LFUN_WORD_SELECT:
7098         case LFUN_SECTION_SELECT:
7099         case LFUN_BUFFER_BEGIN:
7100         case LFUN_BUFFER_END:
7101         case LFUN_BUFFER_BEGIN_SELECT:
7102         case LFUN_BUFFER_END_SELECT:
7103         case LFUN_INSET_BEGIN:
7104         case LFUN_INSET_END:
7105         case LFUN_INSET_BEGIN_SELECT:
7106         case LFUN_INSET_END_SELECT:
7107         case LFUN_PARAGRAPH_UP:
7108         case LFUN_PARAGRAPH_DOWN:
7109         case LFUN_LINE_BEGIN:
7110         case LFUN_LINE_END:
7111         case LFUN_CHAR_DELETE_FORWARD:
7112         case LFUN_CHAR_DELETE_BACKWARD:
7113         case LFUN_WORD_UPCASE:
7114         case LFUN_WORD_LOWCASE:
7115         case LFUN_WORD_CAPITALIZE:
7116         case LFUN_CHARS_TRANSPOSE:
7117         case LFUN_SERVER_GET_XY:
7118         case LFUN_SERVER_SET_XY:
7119         case LFUN_SERVER_GET_LAYOUT:
7120         case LFUN_SELF_INSERT:
7121         case LFUN_UNICODE_INSERT:
7122         case LFUN_THESAURUS_ENTRY:
7123         case LFUN_ESCAPE:
7124         case LFUN_SERVER_GET_STATISTICS:
7125                 // these are handled in our dispatch()
7126                 enable = true;
7127                 break;
7128
7129         case LFUN_INSET_INSERT: {
7130                 string const type = cmd.getArg(0);
7131                 if (type == "toc") {
7132                         code = TOC_CODE;
7133                         // not allowed in description items
7134                         //FIXME: couldn't this be merged in Inset::insetAllowed()?
7135                         enable = !inDescriptionItem(cur);
7136                 } else {
7137                         enable = true;
7138                 }
7139                 break;
7140         }
7141
7142         case LFUN_SEARCH_IGNORE: {
7143                 bool const value = cmd.getArg(1) == "true";
7144                 setIgnoreFormat(cmd.getArg(0), value);
7145                 break;
7146         }
7147
7148         default:
7149                 return false;
7150         }
7151
7152         if (code != NO_CODE
7153             && (cur.empty()
7154                 || !cur.inset().insetAllowed(code)
7155                 || (cur.paragraph().layout().pass_thru && !allow_in_passthru)))
7156                 enable = false;
7157
7158         status.setEnabled(enable);
7159         return true;
7160 }
7161
7162
7163 void Text::pasteString(Cursor & cur, docstring const & clip,
7164                 bool asParagraphs)
7165 {
7166         if (!clip.empty()) {
7167                 cur.recordUndo();
7168                 if (asParagraphs)
7169                         insertStringAsParagraphs(cur, clip, cur.current_font);
7170                 else
7171                         insertStringAsLines(cur, clip, cur.current_font);
7172         }
7173 }
7174
7175
7176 // FIXME: an item inset would make things much easier.
7177 bool Text::inDescriptionItem(Cursor const & cur) const
7178 {
7179         Paragraph const & par = cur.paragraph();
7180         pos_type const pos = cur.pos();
7181         pos_type const body_pos = par.beginOfBody();
7182
7183         if (par.layout().latextype != LATEX_LIST_ENVIRONMENT
7184             && (par.layout().latextype != LATEX_ITEM_ENVIRONMENT
7185                 || par.layout().margintype != MARGIN_FIRST_DYNAMIC))
7186                 return false;
7187
7188         return (pos < body_pos
7189                 || (pos == body_pos
7190                     && (pos == 0 || par.getChar(pos - 1) != ' ')));
7191 }
7192
7193
7194 std::vector<docstring> Text::getFreeFonts() const
7195 {
7196         vector<docstring> ffList;
7197
7198         for (auto const & f : freeFonts)
7199                 ffList.push_back(f.first);
7200
7201         return ffList;
7202 }
7203
7204 } // namespace lyx