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