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