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