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