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