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