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