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