]> git.lyx.org Git - lyx.git/blob - src/Text.cpp
d227bd5cd5af108f1072a0d0ec168aa058219edf
[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 Asger Alstrup
7  * \author Lars Gullik Bjønnes
8  * \author Dov Feldstern
9  * \author Jean-Marc Lasgouttes
10  * \author John Levon
11  * \author André Pönitz
12  * \author Stefan Schimanski
13  * \author Dekel Tsur
14  * \author Jürgen Vigna
15  *
16  * Full author contact details are available in file CREDITS.
17  */
18
19 #include <config.h>
20
21 #include "Text.h"
22
23 #include "Author.h"
24 #include "Buffer.h"
25 #include "BufferParams.h"
26 #include "BufferView.h"
27 #include "Changes.h"
28 #include "CompletionList.h"
29 #include "Cursor.h"
30 #include "CursorSlice.h"
31 #include "CutAndPaste.h"
32 #include "Encoding.h"
33 #include "ErrorList.h"
34 #include "factory.h"
35 #include "Font.h"
36 #include "Language.h"
37 #include "Layout.h"
38 #include "Lexer.h"
39 #include "lyxfind.h"
40 #include "LyXRC.h"
41 #include "Paragraph.h"
42 #include "ParagraphParameters.h"
43 #include "TextClass.h"
44 #include "TextMetrics.h"
45 #include "WordList.h"
46
47 #include "insets/Inset.h"
48 #include "insets/InsetText.h"
49 #include "insets/InsetCaption.h"
50 #include "insets/InsetIPAMacro.h"
51 #include "insets/InsetSpecialChar.h"
52 #include "insets/InsetTabular.h"
53
54 #include "support/convert.h"
55 #include "support/debug.h"
56 #include "support/docstream.h"
57 #include "support/docstring.h"
58 #include "support/gettext.h"
59 #include "support/lassert.h"
60 #include "support/lstrings.h"
61 #include "support/lyxtime.h"
62 #include "support/textutils.h"
63 #include "support/unique_ptr.h"
64
65 #include <sstream>
66
67
68 using namespace std;
69 using namespace lyx::support;
70
71 namespace lyx {
72
73 using cap::cutSelection;
74 using cap::pasteParagraphList;
75
76 static bool moveItem(Paragraph & fromPar, pos_type fromPos,
77         Paragraph & toPar, pos_type toPos, BufferParams const & params)
78 {
79         // Note: moveItem() does not honour change tracking!
80         // Therefore, it should only be used for breaking and merging paragraphs
81
82         // We need a copy here because the character at fromPos is going to be erased.
83         Font const tmpFont = fromPar.getFontSettings(params, fromPos);
84         Change const tmpChange = fromPar.lookupChange(fromPos);
85
86         if (Inset * tmpInset = fromPar.getInset(fromPos)) {
87                 fromPar.releaseInset(fromPos);
88                 // The inset is not in fromPar any more.
89                 if (!toPar.insertInset(toPos, tmpInset, tmpFont, tmpChange)) {
90                         delete tmpInset;
91                         return false;
92                 }
93                 return true;
94         }
95
96         char_type const tmpChar = fromPar.getChar(fromPos);
97         fromPar.eraseChar(fromPos, false);
98         toPar.insertChar(toPos, tmpChar, tmpFont, tmpChange);
99         return true;
100 }
101
102
103 void breakParagraphConservative(BufferParams const & bparams,
104         ParagraphList & pars, pit_type pit, pos_type pos)
105 {
106         // create a new paragraph
107         Paragraph & tmp = *pars.insert(pars.iterator_at(pit + 1), Paragraph());
108         Paragraph & par = pars[pit];
109
110         tmp.setInsetOwner(&par.inInset());
111         tmp.makeSameLayout(par);
112
113         LASSERT(pos <= par.size(), return);
114
115         if (pos < par.size()) {
116                 // move everything behind the break position to the new paragraph
117                 pos_type pos_end = par.size() - 1;
118
119                 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
120                         if (moveItem(par, pos, tmp, j, bparams)) {
121                                 ++j;
122                         }
123                 }
124                 // Move over the end-of-par change information
125                 tmp.setChange(tmp.size(), par.lookupChange(par.size()));
126                 par.setChange(par.size(), Change(bparams.track_changes ?
127                                            Change::INSERTED : Change::UNCHANGED));
128         }
129 }
130
131
132 void mergeParagraph(BufferParams const & bparams,
133         ParagraphList & pars, pit_type par_offset)
134 {
135         Paragraph & next = pars[par_offset + 1];
136         Paragraph & par = pars[par_offset];
137
138         pos_type pos_end = next.size() - 1;
139         pos_type pos_insert = par.size();
140
141         // the imaginary end-of-paragraph character (at par.size()) has to be
142         // marked as unmodified. Otherwise, its change is adopted by the first
143         // character of the next paragraph.
144         if (par.isChanged(par.size())) {
145                 LYXERR(Debug::CHANGES,
146                    "merging par with inserted/deleted end-of-par character");
147                 par.setChange(par.size(), Change(Change::UNCHANGED));
148         }
149
150         Change change = next.lookupChange(next.size());
151
152         // move the content of the second paragraph to the end of the first one
153         for (pos_type i = 0, j = pos_insert; i <= pos_end; ++i) {
154                 if (moveItem(next, 0, par, j, bparams)) {
155                         ++j;
156                 }
157         }
158
159         // move the change of the end-of-paragraph character
160         par.setChange(par.size(), change);
161
162         pars.erase(pars.iterator_at(par_offset + 1));
163 }
164
165
166 Text::Text(InsetText * owner, bool use_default_layout)
167         : owner_(owner)
168 {
169         pars_.push_back(Paragraph());
170         Paragraph & par = pars_.back();
171         par.setInsetOwner(owner);
172         DocumentClass const & dc = owner->buffer().params().documentClass();
173         if (use_default_layout)
174                 par.setDefaultLayout(dc);
175         else
176                 par.setPlainLayout(dc);
177 }
178
179
180 Text::Text(InsetText * owner, Text const & text)
181         : owner_(owner), pars_(text.pars_)
182 {
183         for (auto & p : pars_)
184                 p.setInsetOwner(owner);
185 }
186
187
188 pit_type Text::depthHook(pit_type pit, depth_type depth) const
189 {
190         pit_type newpit = pit;
191
192         if (newpit != 0)
193                 --newpit;
194
195         while (newpit != 0 && pars_[newpit].getDepth() > depth)
196                 --newpit;
197
198         if (pars_[newpit].getDepth() > depth)
199                 return pit;
200
201         return newpit;
202 }
203
204
205 pit_type Text::outerHook(pit_type par_offset) const
206 {
207         Paragraph const & par = pars_[par_offset];
208
209         if (par.getDepth() == 0)
210                 return pars_.size();
211         return depthHook(par_offset, depth_type(par.getDepth() - 1));
212 }
213
214
215 bool Text::isFirstInSequence(pit_type par_offset) const
216 {
217         Paragraph const & par = pars_[par_offset];
218
219         pit_type dhook_offset = depthHook(par_offset, par.getDepth());
220
221         if (dhook_offset == par_offset)
222                 return true;
223
224         Paragraph const & dhook = pars_[dhook_offset];
225
226         return dhook.layout() != par.layout()
227                 || dhook.getDepth() != par.getDepth();
228 }
229
230
231 pit_type Text::lastInSequence(pit_type pit) const
232 {
233         depth_type const depth = pars_[pit].getDepth();
234         pit_type newpit = pit;
235
236         while (size_t(newpit + 1) < pars_.size() &&
237                (pars_[newpit + 1].getDepth() > depth ||
238                 (pars_[newpit + 1].getDepth() == depth &&
239                  pars_[newpit + 1].layout() == pars_[pit].layout())))
240                 ++newpit;
241
242         return newpit;
243 }
244
245
246 int Text::getTocLevel(pit_type par_offset) const
247 {
248         Paragraph const & par = pars_[par_offset];
249
250         if (par.layout().isEnvironment() && !isFirstInSequence(par_offset))
251                 return Layout::NOT_IN_TOC;
252
253         return par.layout().toclevel;
254 }
255
256
257 Font const Text::outerFont(pit_type par_offset) const
258 {
259         depth_type par_depth = pars_[par_offset].getDepth();
260         FontInfo tmpfont = inherit_font;
261         depth_type prev_par_depth = 0;
262         // Resolve against environment font information
263         while (par_offset != pit_type(pars_.size())
264                && par_depth != prev_par_depth
265                && par_depth
266                && !tmpfont.resolved()) {
267                 prev_par_depth = par_depth;
268                 par_offset = outerHook(par_offset);
269                 if (par_offset != pit_type(pars_.size())) {
270                         tmpfont.realize(pars_[par_offset].layout().font);
271                         par_depth = pars_[par_offset].getDepth();
272                 }
273         }
274
275         return Font(tmpfont);
276 }
277
278
279 int Text::getEndLabel(pit_type p) const
280 {
281         pit_type pit = p;
282         depth_type par_depth = pars_[p].getDepth();
283         while (pit != pit_type(pars_.size())) {
284                 Layout const & layout = pars_[pit].layout();
285                 int const endlabeltype = layout.endlabeltype;
286
287                 if (endlabeltype != END_LABEL_NO_LABEL) {
288                         if (p + 1 == pit_type(pars_.size()))
289                                 return endlabeltype;
290
291                         depth_type const next_depth =
292                                 pars_[p + 1].getDepth();
293                         if (par_depth > next_depth ||
294                             (par_depth == next_depth && layout != pars_[p + 1].layout()))
295                                 return endlabeltype;
296                         break;
297                 }
298                 if (par_depth == 0)
299                         break;
300                 pit = outerHook(pit);
301                 if (pit != pit_type(pars_.size()))
302                         par_depth = pars_[pit].getDepth();
303         }
304         return END_LABEL_NO_LABEL;
305 }
306
307
308 static void acceptOrRejectChanges(ParagraphList & pars,
309         BufferParams const & bparams, Text::ChangeOp op)
310 {
311         pit_type pars_size = static_cast<pit_type>(pars.size());
312
313         // first, accept or reject changes within each individual
314         // paragraph (do not consider end-of-par)
315         for (pit_type pit = 0; pit < pars_size; ++pit) {
316                 // prevent assertion failure
317                 if (!pars[pit].empty()) {
318                         if (op == Text::ACCEPT)
319                                 pars[pit].acceptChanges(0, pars[pit].size());
320                         else
321                                 pars[pit].rejectChanges(0, pars[pit].size());
322                 }
323         }
324
325         // next, accept or reject imaginary end-of-par characters
326         for (pit_type pit = 0; pit < pars_size; ++pit) {
327                 pos_type pos = pars[pit].size();
328                 if (pars[pit].isChanged(pos)) {
329                         // keep the end-of-par char if it is inserted and accepted
330                         // or when it is deleted and rejected.
331                         if (pars[pit].isInserted(pos) == (op == Text::ACCEPT)) {
332                                 pars[pit].setChange(pos, Change(Change::UNCHANGED));
333                         } else {
334                                 if (pit == pars_size - 1) {
335                                         // we cannot remove a par break at the end of the last
336                                         // paragraph; instead, we mark it unchanged
337                                         pars[pit].setChange(pos, Change(Change::UNCHANGED));
338                                 } else {
339                                         mergeParagraph(bparams, pars, pit);
340                                         --pit;
341                                         --pars_size;
342                                 }
343                         }
344                 }
345         }
346 }
347
348
349 void acceptChanges(ParagraphList & pars, BufferParams const & bparams)
350 {
351         acceptOrRejectChanges(pars, bparams, Text::ACCEPT);
352 }
353
354
355 void rejectChanges(ParagraphList & pars, BufferParams const & bparams)
356 {
357         acceptOrRejectChanges(pars, bparams, Text::REJECT);
358 }
359
360
361 InsetText const & Text::inset() const
362 {
363         return *owner_;
364 }
365
366
367
368 void Text::readParToken(Paragraph & par, Lexer & lex,
369         string const & token, Font & font, Change & change, ErrorList & errorList)
370 {
371         Buffer * buf = const_cast<Buffer *>(&owner_->buffer());
372         BufferParams & bp = buf->params();
373
374         if (token[0] != '\\') {
375                 docstring dstr = lex.getDocString();
376                 par.appendString(dstr, font, change);
377
378         } else if (token == "\\begin_layout") {
379                 lex.eatLine();
380                 docstring layoutname = lex.getDocString();
381
382                 font = Font(inherit_font, bp.language);
383                 change = Change(Change::UNCHANGED);
384
385                 DocumentClass const & tclass = bp.documentClass();
386
387                 if (layoutname.empty())
388                         layoutname = tclass.defaultLayoutName();
389
390                 if (owner_->forcePlainLayout()) {
391                         // in this case only the empty layout is allowed
392                         layoutname = tclass.plainLayoutName();
393                 } else if (par.usePlainLayout()) {
394                         // in this case, default layout maps to empty layout
395                         if (layoutname == tclass.defaultLayoutName())
396                                 layoutname = tclass.plainLayoutName();
397                 } else {
398                         // otherwise, the empty layout maps to the default
399                         if (layoutname == tclass.plainLayoutName())
400                                 layoutname = tclass.defaultLayoutName();
401                 }
402
403                 // When we apply an unknown layout to a document, we add this layout to the textclass
404                 // of this document. For example, when you apply class article to a beamer document,
405                 // all unknown layouts such as frame will be added to document class article so that
406                 // these layouts can keep their original names.
407                 bool const added_one = tclass.addLayoutIfNeeded(layoutname);
408                 if (added_one) {
409                         // Warn the user.
410                         docstring const s = bformat(_("Layout `%1$s' was not found."), layoutname);
411                         errorList.push_back(ErrorItem(_("Layout Not Found"), s,
412                                                       {par.id(), 0}, {par.id(), -1}));
413                 }
414
415                 par.setLayout(bp.documentClass()[layoutname]);
416
417                 // Test whether the layout is obsolete.
418                 Layout const & layout = par.layout();
419                 if (!layout.obsoleted_by().empty())
420                         par.setLayout(bp.documentClass()[layout.obsoleted_by()]);
421
422                 par.params().read(lex);
423
424         } else if (token == "\\end_layout") {
425                 LYXERR0("Solitary \\end_layout in line " << lex.lineNumber() << "\n"
426                        << "Missing \\begin_layout ?");
427         } else if (token == "\\end_inset") {
428                 LYXERR0("Solitary \\end_inset in line " << lex.lineNumber() << "\n"
429                        << "Missing \\begin_inset ?");
430         } else if (token == "\\begin_inset") {
431                 Inset * inset = readInset(lex, buf);
432                 if (inset)
433                         par.insertInset(par.size(), inset, font, change);
434                 else {
435                         lex.eatLine();
436                         docstring line = lex.getDocString();
437                         errorList.push_back(ErrorItem(_("Unknown Inset"), line,
438                                                       {par.id(), 0}, {par.id(), -1}));
439                 }
440         } else if (token == "\\family") {
441                 lex.next();
442                 setLyXFamily(lex.getString(), font.fontInfo());
443         } else if (token == "\\series") {
444                 lex.next();
445                 setLyXSeries(lex.getString(), font.fontInfo());
446         } else if (token == "\\shape") {
447                 lex.next();
448                 setLyXShape(lex.getString(), font.fontInfo());
449         } else if (token == "\\size") {
450                 lex.next();
451                 setLyXSize(lex.getString(), font.fontInfo());
452         } else if (token == "\\lang") {
453                 lex.next();
454                 string const tok = lex.getString();
455                 Language const * lang = languages.getLanguage(tok);
456                 if (lang) {
457                         font.setLanguage(lang);
458                 } else {
459                         font.setLanguage(bp.language);
460                         lex.printError("Unknown language `$$Token'");
461                 }
462         } else if (token == "\\numeric") {
463                 lex.next();
464                 font.fontInfo().setNumber(setLyXMisc(lex.getString()));
465         } else if (token == "\\nospellcheck") {
466                 lex.next();
467                 font.fontInfo().setNoSpellcheck(setLyXMisc(lex.getString()));
468         } else if (token == "\\emph") {
469                 lex.next();
470                 font.fontInfo().setEmph(setLyXMisc(lex.getString()));
471         } else if (token == "\\bar") {
472                 lex.next();
473                 string const tok = lex.getString();
474
475                 if (tok == "under")
476                         font.fontInfo().setUnderbar(FONT_ON);
477                 else if (tok == "no")
478                         font.fontInfo().setUnderbar(FONT_OFF);
479                 else if (tok == "default")
480                         font.fontInfo().setUnderbar(FONT_INHERIT);
481                 else
482                         lex.printError("Unknown bar font flag "
483                                        "`$$Token'");
484         } else if (token == "\\strikeout") {
485                 lex.next();
486                 font.fontInfo().setStrikeout(setLyXMisc(lex.getString()));
487         } else if (token == "\\xout") {
488                 lex.next();
489                 font.fontInfo().setXout(setLyXMisc(lex.getString()));
490         } else if (token == "\\uuline") {
491                 lex.next();
492                 font.fontInfo().setUuline(setLyXMisc(lex.getString()));
493         } else if (token == "\\uwave") {
494                 lex.next();
495                 font.fontInfo().setUwave(setLyXMisc(lex.getString()));
496         } else if (token == "\\noun") {
497                 lex.next();
498                 font.fontInfo().setNoun(setLyXMisc(lex.getString()));
499         } else if (token == "\\color") {
500                 lex.next();
501                 setLyXColor(lex.getString(), font.fontInfo());
502         } else if (token == "\\SpecialChar" ||
503                    (token == "\\SpecialCharNoPassThru" &&
504                     !par.layout().pass_thru && !inset().isPassThru())) {
505                 auto inset = make_unique<InsetSpecialChar>();
506                 inset->read(lex);
507                 inset->setBuffer(*buf);
508                 par.insertInset(par.size(), inset.release(), font, change);
509         } else if (token == "\\SpecialCharNoPassThru") {
510                 lex.next();
511                 docstring const s = ltrim(lex.getDocString(), "\\");
512                 par.insert(par.size(), s, font, change);
513         } else if (token == "\\IPAChar") {
514                 auto inset = make_unique<InsetIPAChar>();
515                 inset->read(lex);
516                 inset->setBuffer(*buf);
517                 par.insertInset(par.size(), inset.release(), font, change);
518         } else if (token == "\\twohyphens" || token == "\\threehyphens") {
519                 // Ideally, this should be done by lyx2lyx, but lyx2lyx does not know the
520                 // running font and does not know anything about layouts (and CopyStyle).
521                 Layout const & layout(par.layout());
522                 FontInfo info = font.fontInfo();
523                 info.realize(layout.resfont);
524                 if (layout.pass_thru || inset().isPassThru() ||
525                     info.family() == TYPEWRITER_FAMILY) {
526                         if (token == "\\twohyphens")
527                                 par.insert(par.size(), from_ascii("--"), font, change);
528                         else
529                                 par.insert(par.size(), from_ascii("---"), font, change);
530                 } else {
531                         if (token == "\\twohyphens")
532                                 par.insertChar(par.size(), 0x2013, font, change);
533                         else
534                                 par.insertChar(par.size(), 0x2014, font, change);
535                 }
536         } else if (token == "\\backslash") {
537                 par.appendChar('\\', font, change);
538         } else if (token == "\\LyXTable") {
539                 auto inset = make_unique<InsetTabular>(buf);
540                 inset->read(lex);
541                 par.insertInset(par.size(), inset.release(), font, change);
542         } else if (token == "\\change_unchanged") {
543                 change = Change(Change::UNCHANGED);
544         } else if (token == "\\change_inserted" || token == "\\change_deleted") {
545                 lex.eatLine();
546                 istringstream is(lex.getString());
547                 int aid;
548                 time_t ct;
549                 is >> aid >> ct;
550                 BufferParams::AuthorMap const & am = bp.author_map_;
551                 if (am.find(aid) == am.end()) {
552                         errorList.push_back(ErrorItem(
553                                 _("Change tracking author index missing"),
554                                 bformat(_("A change tracking author information for index "
555                                           "%1$d is missing. This can happen after a wrong "
556                                           "merge by a version control system. In this case, "
557                                           "either fix the merge, or have this information "
558                                           "missing until the corresponding tracked changes "
559                                           "are merged or this user edits the file again.\n"),
560                                         aid),
561                                 {par.id(), par.size()}, {par.id(), par.size() + 1}));
562                         bp.addAuthor(Author(aid));
563                 }
564                 if (token == "\\change_inserted")
565                         change = Change(Change::INSERTED, am.find(aid)->second, ct);
566                 else
567                         change = Change(Change::DELETED, am.find(aid)->second, ct);
568         } else {
569                 lex.eatLine();
570                 errorList.push_back(ErrorItem(_("Unknown token"),
571                                               bformat(_("Unknown token: %1$s %2$s\n"),
572                                                       from_utf8(token),
573                                                       lex.getDocString()),
574                                               {par.id(), 0}, {par.id(), -1}));
575         }
576 }
577
578
579 void Text::readParagraph(Paragraph & par, Lexer & lex,
580         ErrorList & errorList)
581 {
582         lex.nextToken();
583         string token = lex.getString();
584         Font font;
585         Change change(Change::UNCHANGED);
586
587         while (lex.isOK()) {
588                 readParToken(par, lex, token, font, change, errorList);
589
590                 lex.nextToken();
591                 token = lex.getString();
592
593                 if (token.empty())
594                         continue;
595
596                 if (token == "\\end_layout") {
597                         //Ok, paragraph finished
598                         break;
599                 }
600
601                 LYXERR(Debug::PARSER, "Handling paragraph token: `" << token << '\'');
602                 if (token == "\\begin_layout" || token == "\\end_document"
603                     || token == "\\end_inset" || token == "\\begin_deeper"
604                     || token == "\\end_deeper") {
605                         lex.pushToken(token);
606                         lyxerr << "Paragraph ended in line "
607                                << lex.lineNumber() << "\n"
608                                << "Missing \\end_layout.\n";
609                         break;
610                 }
611         }
612         // Final change goes to paragraph break:
613         if (inset().allowMultiPar())
614                 par.setChange(par.size(), change);
615
616         // Initialize begin_of_body_ on load; redoParagraph maintains
617         par.setBeginOfBody();
618
619         // mark paragraph for spell checking on load
620         // par.requestSpellCheck();
621 }
622
623
624 class TextCompletionList : public CompletionList
625 {
626 public:
627         ///
628         TextCompletionList(Cursor const & cur, WordList const & list)
629                 : buffer_(cur.buffer()), list_(list)
630         {}
631         ///
632         virtual ~TextCompletionList() {}
633
634         ///
635         bool sorted() const override { return true; }
636         ///
637         size_t size() const override
638         {
639                 return list_.size();
640         }
641         ///
642         docstring const & data(size_t idx) const override
643         {
644                 return list_.word(idx);
645         }
646
647 private:
648         ///
649         Buffer const * buffer_;
650         ///
651         WordList const & list_;
652 };
653
654
655 bool Text::empty() const
656 {
657         return pars_.empty() || (pars_.size() == 1 && pars_[0].empty()
658                 // FIXME: Should we consider the labeled type as empty too?
659                 && pars_[0].layout().labeltype == LABEL_NO_LABEL);
660 }
661
662
663 double Text::spacing(Paragraph const & par) const
664 {
665         if (par.params().spacing().isDefault())
666                 return owner_->buffer().params().spacing().getValue();
667         return par.params().spacing().getValue();
668 }
669
670
671 /**
672  * This breaks a paragraph at the specified position.
673  * The new paragraph will:
674  * - Decrease depth by one (or change layout to default layout) when
675  *    keep_layout == false
676  * - keep current depth and layout when keep_layout == true
677  */
678 static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
679                     bool keep_layout)
680 {
681         BufferParams const & bparams = text.inset().buffer().params();
682         ParagraphList & pars = text.paragraphs();
683         // create a new paragraph, and insert into the list
684         ParagraphList::iterator tmp =
685                 pars.insert(pars.iterator_at(par_offset + 1), Paragraph());
686
687         Paragraph & par = pars[par_offset];
688
689         // remember to set the inset_owner
690         tmp->setInsetOwner(&par.inInset());
691         // without doing that we get a crash when typing <Return> at the
692         // end of a paragraph
693         tmp->setPlainOrDefaultLayout(bparams.documentClass());
694
695         if (keep_layout) {
696                 tmp->setLayout(par.layout());
697                 tmp->setLabelWidthString(par.params().labelWidthString());
698                 tmp->params().depth(par.params().depth());
699         } else if (par.params().depth() > 0) {
700                 Paragraph const & hook = pars[text.outerHook(par_offset)];
701                 tmp->setLayout(hook.layout());
702                 // not sure the line below is useful
703                 tmp->setLabelWidthString(par.params().labelWidthString());
704                 tmp->params().depth(hook.params().depth());
705         }
706
707         bool const isempty = (par.allowEmpty() && par.empty());
708
709         if (!isempty && (par.size() > pos || par.empty())) {
710                 tmp->setLayout(par.layout());
711                 tmp->params().align(par.params().align());
712                 tmp->setLabelWidthString(par.params().labelWidthString());
713
714                 tmp->params().depth(par.params().depth());
715                 tmp->params().noindent(par.params().noindent());
716                 tmp->params().spacing(par.params().spacing());
717
718                 // move everything behind the break position
719                 // to the new paragraph
720
721                 /* Note: if !keepempty, empty() == true, then we reach
722                  * here with size() == 0. So pos_end becomes - 1. This
723                  * doesn't cause problems because both loops below
724                  * enforce pos <= pos_end and 0 <= pos
725                  */
726                 pos_type pos_end = par.size() - 1;
727
728                 for (pos_type i = pos, j = 0; i <= pos_end; ++i) {
729                         if (moveItem(par, pos, *tmp, j, bparams)) {
730                                 ++j;
731                         }
732                 }
733         }
734
735         // Move over the end-of-par change information
736         tmp->setChange(tmp->size(), par.lookupChange(par.size()));
737         par.setChange(par.size(), Change(bparams.track_changes ?
738                                            Change::INSERTED : Change::UNCHANGED));
739
740         if (pos) {
741                 // Make sure that we keep the language when
742                 // breaking paragraph.
743                 if (tmp->empty()) {
744                         Font changed = tmp->getFirstFontSettings(bparams);
745                         Font const & old = par.getFontSettings(bparams, par.size());
746                         changed.setLanguage(old.language());
747                         tmp->setFont(0, changed);
748                 }
749
750                 return;
751         }
752
753         if (!isempty) {
754                 bool const soa = par.params().startOfAppendix();
755                 par.params().clear();
756                 // do not lose start of appendix marker (bug 4212)
757                 par.params().startOfAppendix(soa);
758                 par.setPlainOrDefaultLayout(bparams.documentClass());
759         }
760
761         if (keep_layout) {
762                 par.setLayout(tmp->layout());
763                 par.setLabelWidthString(tmp->params().labelWidthString());
764                 par.params().depth(tmp->params().depth());
765         }
766 }
767
768
769 void Text::breakParagraph(Cursor & cur, bool inverse_logic)
770 {
771         LBUFERR(this == cur.text());
772
773         Paragraph & cpar = cur.paragraph();
774         pit_type cpit = cur.pit();
775
776         DocumentClass const & tclass = cur.buffer()->params().documentClass();
777         Layout const & layout = cpar.layout();
778
779         if (cur.lastpos() == 0 && !cpar.allowEmpty()) {
780                 if (changeDepthAllowed(cur, DEC_DEPTH)) {
781                         changeDepth(cur, DEC_DEPTH);
782                         pit_type const prev = depthHook(cpit, cpar.getDepth());
783                         docstring const & lay = pars_[prev].layout().name();
784                         if (lay != layout.name())
785                                 setLayout(cur, lay);
786                 } else {
787                         docstring const & lay = cur.paragraph().usePlainLayout()
788                             ? tclass.plainLayoutName() : tclass.defaultLayoutName();
789                         if (lay != layout.name())
790                                 setLayout(cur, lay);
791                 }
792                 return;
793         }
794
795         cur.recordUndo();
796
797         // Always break behind a space
798         // It is better to erase the space (Dekel)
799         if (cur.pos() != cur.lastpos() && cpar.isLineSeparator(cur.pos()))
800                 cpar.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
801
802         // What should the layout for the new paragraph be?
803         bool keep_layout = layout.isEnvironment()
804                 || (layout.isParagraph() && layout.parbreak_is_newline);
805         if (inverse_logic)
806                 keep_layout = !keep_layout;
807
808         // We need to remember this before we break the paragraph, because
809         // that invalidates the layout variable
810         bool sensitive = layout.labeltype == LABEL_SENSITIVE;
811
812         // we need to set this before we insert the paragraph.
813         bool const isempty = cpar.allowEmpty() && cpar.empty();
814
815         lyx::breakParagraph(*this, cpit, cur.pos(), keep_layout);
816
817         // After this, neither paragraph contains any rows!
818
819         cpit = cur.pit();
820         pit_type next_par = cpit + 1;
821
822         // well this is the caption hack since one caption is really enough
823         if (sensitive) {
824                 if (cur.pos() == 0)
825                         // set to standard-layout
826                 //FIXME Check if this should be plainLayout() in some cases
827                         pars_[cpit].applyLayout(tclass.defaultLayout());
828                 else
829                         // set to standard-layout
830                         //FIXME Check if this should be plainLayout() in some cases
831                         pars_[next_par].applyLayout(tclass.defaultLayout());
832         }
833
834         while (!pars_[next_par].empty() && pars_[next_par].isNewline(0)) {
835                 if (!pars_[next_par].eraseChar(0, cur.buffer()->params().track_changes))
836                         break; // the character couldn't be deleted physically due to change tracking
837         }
838
839         // A singlePar update is not enough in this case.
840         cur.screenUpdateFlags(Update::Force);
841         cur.forceBufferUpdate();
842
843         // This check is necessary. Otherwise the new empty paragraph will
844         // be deleted automatically. And it is more friendly for the user!
845         if (cur.pos() != 0 || isempty)
846                 setCursor(cur, cur.pit() + 1, 0);
847         else
848                 setCursor(cur, cur.pit(), 0);
849 }
850
851
852 // needed to insert the selection
853 void Text::insertStringAsLines(Cursor & cur, docstring const & str,
854                 Font const & font)
855 {
856         BufferParams const & bparams = owner_->buffer().params();
857         pit_type pit = cur.pit();
858         pos_type pos = cur.pos();
859
860         // The special chars we handle
861         map<wchar_t, InsetSpecialChar::Kind> specialchars;
862         specialchars[0x200c] = InsetSpecialChar::LIGATURE_BREAK;
863         specialchars[0x200b] = InsetSpecialChar::ALLOWBREAK;
864         specialchars[0x2026] = InsetSpecialChar::LDOTS;
865         specialchars[0x2011] = InsetSpecialChar::NOBREAKDASH;
866
867         // insert the string, don't insert doublespace
868         bool space_inserted = true;
869         for (auto const & ch : str) {
870                 Paragraph & par = pars_[pit];
871                 if (ch == '\n') {
872                         if (inset().allowMultiPar() && (!par.empty() || par.allowEmpty())) {
873                                 lyx::breakParagraph(*this, pit, pos,
874                                         par.layout().isEnvironment());
875                                 ++pit;
876                                 pos = 0;
877                                 space_inserted = true;
878                         } else {
879                                 continue;
880                         }
881                 // do not insert consecutive spaces if !free_spacing
882                 } else if ((ch == ' ' || ch == '\t') &&
883                            space_inserted && !par.isFreeSpacing()) {
884                         continue;
885                 } else if (ch == '\t') {
886                         if (!par.isFreeSpacing()) {
887                                 // tabs are like spaces here
888                                 par.insertChar(pos, ' ', font, bparams.track_changes);
889                                 ++pos;
890                                 space_inserted = true;
891                         } else {
892                                 par.insertChar(pos, ch, font, bparams.track_changes);
893                                 ++pos;
894                                 space_inserted = true;
895                         }
896                 } else if (specialchars.find(ch) != specialchars.end()) {
897                         par.insertInset(pos, new InsetSpecialChar(specialchars.find(ch)->second),
898                                         font, bparams.track_changes ?
899                                                 Change(Change::INSERTED)
900                                               : Change(Change::UNCHANGED));
901                         ++pos;
902                         space_inserted = false;
903                 } else if (!isPrintable(ch)) {
904                         // Ignore (other) unprintables
905                         continue;
906                 } else {
907                         // just insert the character
908                         par.insertChar(pos, ch, font, bparams.track_changes);
909                         ++pos;
910                         space_inserted = (ch == ' ');
911                 }
912         }
913         setCursor(cur, pit, pos);
914 }
915
916
917 // turn double CR to single CR, others are converted into one
918 // blank. Then insertStringAsLines is called
919 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str,
920                 Font const & font)
921 {
922         docstring linestr = str;
923         bool newline_inserted = false;
924
925         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
926                 if (linestr[i] == '\n') {
927                         if (newline_inserted) {
928                                 // we know that \r will be ignored by
929                                 // insertStringAsLines. Of course, it is a dirty
930                                 // trick, but it works...
931                                 linestr[i - 1] = '\r';
932                                 linestr[i] = '\n';
933                         } else {
934                                 linestr[i] = ' ';
935                                 newline_inserted = true;
936                         }
937                 } else if (isPrintable(linestr[i])) {
938                         newline_inserted = false;
939                 }
940         }
941         insertStringAsLines(cur, linestr, font);
942 }
943
944
945 namespace {
946
947 bool canInsertChar(Cursor const & cur, char_type c)
948 {
949         Paragraph const & par = cur.paragraph();
950         // If not in free spacing mode, check if there will be two blanks together or a blank at
951         // the beginning of a paragraph.
952         if (!par.isFreeSpacing() && isLineSeparatorChar(c)) {
953                 if (cur.pos() == 0) {
954                         cur.message(_(
955                                         "You cannot insert a space at the "
956                                         "beginning of a paragraph. Please read the Tutorial."));
957                         return false;
958                 }
959                 // If something is wrong, ignore this character.
960                 LASSERT(cur.pos() > 0, return false);
961                 if ((par.isLineSeparator(cur.pos() - 1) || par.isNewline(cur.pos() - 1))
962                                 && !par.isDeleted(cur.pos() - 1)) {
963                         cur.message(_(
964                                         "You cannot type two spaces this way. "
965                                         "Please read the Tutorial."));
966                         return false;
967                 }
968         }
969
970         // Prevent to insert uncodable characters in verbatim and ERT.
971         // The encoding is inherited from the context here.
972         if (par.isPassThru() && cur.getEncoding()) {
973                 Encoding const * e = cur.getEncoding();
974                 if (!e->encodable(c)) {
975                         cur.message(_("Character is uncodable in this verbatim context."));
976                         return false;
977                 }
978         }
979         return true;
980 }
981
982 } // namespace
983
984
985 // insert a character, moves all the following breaks in the
986 // same Paragraph one to the right and make a rebreak
987 void Text::insertChar(Cursor & cur, char_type c)
988 {
989         LBUFERR(this == cur.text());
990
991         if (!canInsertChar(cur,c))
992                 return;
993
994         cur.recordUndo(INSERT_UNDO);
995
996         TextMetrics const & tm = cur.bv().textMetrics(this);
997         Buffer const & buffer = *cur.buffer();
998         Paragraph & par = cur.paragraph();
999         // try to remove this
1000         pit_type const pit = cur.pit();
1001
1002         if (lyxrc.auto_number) {
1003                 static docstring const number_operators = from_ascii("+-/*");
1004                 static docstring const number_unary_operators = from_ascii("+-");
1005
1006                 // Common Number Separators: comma, dot etc.
1007                 // European Number Terminators: percent, permille, degree, euro etc.
1008                 if (cur.current_font.fontInfo().number() == FONT_ON) {
1009                         if (!isDigitASCII(c) && !contains(number_operators, c) &&
1010                             !(isCommonNumberSeparator(c) &&
1011                               cur.pos() != 0 &&
1012                               cur.pos() != cur.lastpos() &&
1013                               tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1014                               tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON) &&
1015                             !(isEuropeanNumberTerminator(c) &&
1016                               cur.pos() != 0 &&
1017                               tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
1018                               tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON)
1019                            )
1020                                 number(cur); // Set current_font.number to OFF
1021                 } else if (isDigitASCII(c) &&
1022                            cur.real_current_font.isVisibleRightToLeft()) {
1023                         number(cur); // Set current_font.number to ON
1024
1025                         if (cur.pos() != 0) {
1026                                 char_type const ch = par.getChar(cur.pos() - 1);
1027                                 if (contains(number_unary_operators, ch) &&
1028                                     (cur.pos() == 1
1029                                      || par.isSeparator(cur.pos() - 2)
1030                                      || par.isEnvSeparator(cur.pos() - 2)
1031                                      || par.isNewline(cur.pos() - 2))
1032                                   ) {
1033                                         setCharFont(pit, cur.pos() - 1, cur.current_font,
1034                                                 tm.font_);
1035                                 } else if (isCommonNumberSeparator(ch)
1036                                      && cur.pos() >= 2
1037                                      && tm.displayFont(pit, cur.pos() - 2).fontInfo().number() == FONT_ON) {
1038                                         setCharFont(pit, cur.pos() - 1, cur.current_font,
1039                                                 tm.font_);
1040                                 }
1041                         }
1042                 }
1043         }
1044
1045         // In Bidi text, we want spaces to be treated in a special way: spaces
1046         // which are between words in different languages should get the
1047         // paragraph's language; otherwise, spaces should keep the language
1048         // they were originally typed in. This is only in effect while typing;
1049         // after the text is already typed in, the user can always go back and
1050         // explicitly set the language of a space as desired. But 99.9% of the
1051         // time, what we're doing here is what the user actually meant.
1052         //
1053         // The following cases are the ones in which the language of the space
1054         // should be changed to match that of the containing paragraph. In the
1055         // depictions, lowercase is LTR, uppercase is RTL, underscore (_)
1056         // represents a space, pipe (|) represents the cursor position (so the
1057         // character before it is the one just typed in). The different cases
1058         // are depicted logically (not visually), from left to right:
1059         //
1060         // 1. A_a|
1061         // 2. a_A|
1062         //
1063         // Theoretically, there are other situations that we should, perhaps, deal
1064         // with (e.g.: a|_A, A|_a). In practice, though, there really isn't any
1065         // point (to understand why, just try to create this situation...).
1066
1067         if ((cur.pos() >= 2) && (par.isLineSeparator(cur.pos() - 1))) {
1068                 // get font in front and behind the space in question. But do NOT
1069                 // use getFont(cur.pos()) because the character c is not inserted yet
1070                 Font const pre_space_font  = tm.displayFont(cur.pit(), cur.pos() - 2);
1071                 Font const & post_space_font = cur.real_current_font;
1072                 bool pre_space_rtl  = pre_space_font.isVisibleRightToLeft();
1073                 bool post_space_rtl = post_space_font.isVisibleRightToLeft();
1074
1075                 if (pre_space_rtl != post_space_rtl) {
1076                         // Set the space's language to match the language of the
1077                         // adjacent character whose direction is the paragraph's
1078                         // direction; don't touch other properties of the font
1079                         Language const * lang =
1080                                 (pre_space_rtl == par.isRTL(buffer.params())) ?
1081                                 pre_space_font.language() : post_space_font.language();
1082
1083                         Font space_font = tm.displayFont(cur.pit(), cur.pos() - 1);
1084                         space_font.setLanguage(lang);
1085                         par.setFont(cur.pos() - 1, space_font);
1086                 }
1087         }
1088
1089         pos_type pos = cur.pos();
1090         if (!cur.paragraph().isPassThru() && owner_->lyxCode() != IPA_CODE &&
1091             cur.real_current_font.fontInfo().family() != TYPEWRITER_FAMILY &&
1092             c == '-' && pos > 0) {
1093                 if (par.getChar(pos - 1) == '-') {
1094                         // convert "--" to endash
1095                         par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1096                         c = 0x2013;
1097                         pos--;
1098                 } else if (par.getChar(pos - 1) == 0x2013) {
1099                         // convert "---" to emdash
1100                         par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
1101                         c = 0x2014;
1102                         pos--;
1103                 }
1104         }
1105
1106         par.insertChar(pos, c, cur.current_font,
1107                 cur.buffer()->params().track_changes);
1108         cur.checkBufferStructure();
1109
1110 //              cur.screenUpdateFlags(Update::Force);
1111         bool boundary = cur.boundary()
1112                 || tm.isRTLBoundary(cur.pit(), pos + 1);
1113         setCursor(cur, cur.pit(), pos + 1, false, boundary);
1114         charInserted(cur);
1115 }
1116
1117
1118 void Text::charInserted(Cursor & cur)
1119 {
1120         Paragraph & par = cur.paragraph();
1121
1122         // register word if a non-letter was entered
1123         if (cur.pos() > 1
1124             && !par.isWordSeparator(cur.pos() - 2)
1125             && par.isWordSeparator(cur.pos() - 1)) {
1126                 // get the word in front of cursor
1127                 LBUFERR(this == cur.text());
1128                 par.updateWords();
1129         }
1130 }
1131
1132
1133 // the cursor set functions have a special mechanism. When they
1134 // realize, that you left an empty paragraph, they will delete it.
1135
1136 bool Text::cursorForwardOneWord(Cursor & cur)
1137 {
1138         LBUFERR(this == cur.text());
1139
1140         if (lyxrc.mac_like_cursor_movement) {
1141                 DocIterator dit(cur);
1142                 DocIterator prv(cur);
1143                 bool inword = false;
1144                 bool intext = dit.inTexted();
1145                 while (!dit.atEnd()) {
1146                         if (dit.inTexted()) { // no paragraphs in mathed
1147                                 Paragraph const & par = dit.paragraph();
1148                                 pos_type const pos = dit.pos();
1149
1150                                 if (!par.isDeleted(pos)) {
1151                                         bool wordsep = par.isWordSeparator(pos);
1152                                         if (inword && wordsep)
1153                                                 break; // stop at word end
1154                                         else if (!inword && !wordsep)
1155                                                 inword = true;
1156                                 }
1157                                 intext = true;
1158                         } else if (intext) {
1159                                 // move to end of math
1160                                 while (!dit.inTexted() && !dit.atEnd()) dit.forwardPos();
1161                                 break;
1162                         }
1163                         prv = dit;
1164                         dit.forwardPosIgnoreCollapsed();
1165                 }
1166                 if (dit.atEnd()) dit = prv;
1167                 if (dit == cur) return false; // we didn't move
1168                 Cursor orig(cur);
1169                 cur.setCursor(dit);
1170                 // see comment above
1171                 cur.bv().checkDepm(cur, orig);
1172                 return true;
1173         } else {
1174                 pos_type const lastpos = cur.lastpos();
1175                 pit_type pit = cur.pit();
1176                 pos_type pos = cur.pos();
1177                 Paragraph const & par = cur.paragraph();
1178
1179                 // Paragraph boundary is a word boundary
1180                 if (pos == lastpos || (pos + 1 == lastpos && par.isEnvSeparator(pos))) {
1181                         if (pit != cur.lastpit())
1182                                 return setCursor(cur, pit + 1, 0);
1183                         else
1184                                 return false;
1185                 }
1186
1187                 LASSERT(pos < lastpos, return false); // see above
1188                 if (!par.isWordSeparator(pos))
1189                         while (pos != lastpos && !par.isWordSeparator(pos))
1190                                 ++pos;
1191                 else if (par.isChar(pos))
1192                         while (pos != lastpos && par.isChar(pos))
1193                                 ++pos;
1194                 else if (!par.isSpace(pos)) // non-char inset
1195                         ++pos;
1196
1197                 // Skip over white space
1198                 while (pos != lastpos && par.isSpace(pos))
1199                              ++pos;
1200
1201                 // Don't skip a separator inset at the end of a paragraph
1202                 if (pos == lastpos && pos && par.isEnvSeparator(pos - 1))
1203                         --pos;
1204
1205                 return setCursor(cur, pit, pos);
1206         }
1207 }
1208
1209
1210 bool Text::cursorBackwardOneWord(Cursor & cur)
1211 {
1212         LBUFERR(this == cur.text());
1213
1214         if (lyxrc.mac_like_cursor_movement) {
1215                 DocIterator dit(cur);
1216                 bool inword = false;
1217                 bool intext = dit.inTexted();
1218                 while (!dit.atBegin()) {
1219                         DocIterator prv(dit);
1220                         dit.backwardPosIgnoreCollapsed();
1221                         if (dit.inTexted()) { // no paragraphs in mathed
1222                                 Paragraph const & par = dit.paragraph();
1223                                 pos_type pos = dit.pos();
1224
1225                                 if (!par.isDeleted(pos)) {
1226                                         bool wordsep = par.isWordSeparator(pos);
1227                                         if (inword && wordsep) {
1228                                                 dit = prv;
1229                                                 break; // stop at word begin
1230                                         } else if (!inword && !wordsep)
1231                                                 inword = true;
1232                                 }
1233                                 intext = true;
1234                         } else if (intext) {
1235                                 // move to begin of math
1236                                 while (!dit.inTexted() && !dit.atBegin()) dit.backwardPos();
1237                                 break;
1238                         }
1239                 }
1240                 if (dit == cur) return false; // we didn't move
1241                 Cursor orig(cur);
1242                 cur.setCursor(dit);
1243                 // see comment above cursorForwardOneWord
1244                 cur.bv().checkDepm(cur, orig);
1245                 return true;
1246         } else {
1247                 Paragraph const & par = cur.paragraph();
1248                 pit_type const pit = cur.pit();
1249                 pos_type pos = cur.pos();
1250
1251                 // Paragraph boundary is a word boundary
1252                 if (pos == 0 && pit != 0) {
1253                         Paragraph & prevpar = getPar(pit - 1);
1254                         pos = prevpar.size();
1255                         // Don't stop after an environment separator
1256                         if (pos && prevpar.isEnvSeparator(pos - 1))
1257                                 --pos;
1258                         return setCursor(cur, pit - 1, pos);
1259                 }
1260                 // Skip over white space
1261                 while (pos != 0 && par.isSpace(pos - 1))
1262                         --pos;
1263
1264                 if (pos != 0 && !par.isWordSeparator(pos - 1))
1265                         while (pos != 0 && !par.isWordSeparator(pos - 1))
1266                                 --pos;
1267                 else if (pos != 0 && par.isChar(pos - 1))
1268                         while (pos != 0 && par.isChar(pos - 1))
1269                                 --pos;
1270                 else if (pos != 0 && !par.isSpace(pos - 1)) // non-char inset
1271                         --pos;
1272
1273                 return setCursor(cur, pit, pos);
1274         }
1275 }
1276
1277
1278 bool Text::cursorVisLeftOneWord(Cursor & cur)
1279 {
1280         LBUFERR(this == cur.text());
1281
1282         pos_type left_pos, right_pos;
1283
1284         Cursor temp_cur = cur;
1285
1286         // always try to move at least once...
1287         while (temp_cur.posVisLeft(true /* skip_inset */)) {
1288
1289                 // collect some information about current cursor position
1290                 temp_cur.getSurroundingPos(left_pos, right_pos);
1291                 bool left_is_letter =
1292                         (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1293                 bool right_is_letter =
1294                         (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1295
1296                 // if we're not at a letter/non-letter boundary, continue moving
1297                 if (left_is_letter == right_is_letter)
1298                         continue;
1299
1300                 // we should stop when we have an LTR word on our right or an RTL word
1301                 // on our left
1302                 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1303                                 temp_cur.buffer()->params(), left_pos).isRightToLeft())
1304                         || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1305                                 temp_cur.buffer()->params(), right_pos).isRightToLeft()))
1306                         break;
1307         }
1308
1309         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1310                                          true, temp_cur.boundary());
1311 }
1312
1313
1314 bool Text::cursorVisRightOneWord(Cursor & cur)
1315 {
1316         LBUFERR(this == cur.text());
1317
1318         pos_type left_pos, right_pos;
1319
1320         Cursor temp_cur = cur;
1321
1322         // always try to move at least once...
1323         while (temp_cur.posVisRight(true /* skip_inset */)) {
1324
1325                 // collect some information about current cursor position
1326                 temp_cur.getSurroundingPos(left_pos, right_pos);
1327                 bool left_is_letter =
1328                         (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
1329                 bool right_is_letter =
1330                         (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
1331
1332                 // if we're not at a letter/non-letter boundary, continue moving
1333                 if (left_is_letter == right_is_letter)
1334                         continue;
1335
1336                 // we should stop when we have an LTR word on our right or an RTL word
1337                 // on our left
1338                 if ((left_is_letter && temp_cur.paragraph().getFontSettings(
1339                                 temp_cur.buffer()->params(),
1340                                 left_pos).isRightToLeft())
1341                         || (right_is_letter && !temp_cur.paragraph().getFontSettings(
1342                                 temp_cur.buffer()->params(),
1343                                 right_pos).isRightToLeft()))
1344                         break;
1345         }
1346
1347         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
1348                                          true, temp_cur.boundary());
1349 }
1350
1351
1352 void Text::selectWord(Cursor & cur, word_location loc)
1353 {
1354         LBUFERR(this == cur.text());
1355         CursorSlice from = cur.top();
1356         CursorSlice to;
1357         getWord(from, to, loc);
1358         if (cur.top() != from)
1359                 setCursor(cur, from.pit(), from.pos());
1360         if (to == from)
1361                 return;
1362         if (!cur.selection())
1363                 cur.resetAnchor();
1364         setCursor(cur, to.pit(), to.pos());
1365         cur.setSelection();
1366         cur.setWordSelection(true);
1367 }
1368
1369
1370 void Text::selectAll(Cursor & cur)
1371 {
1372         LBUFERR(this == cur.text());
1373         if (cur.lastpos() == 0 && cur.lastpit() == 0)
1374                 return;
1375         // If the cursor is at the beginning, make sure the cursor ends there
1376         if (cur.pit() == 0 && cur.pos() == 0) {
1377                 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1378                 cur.resetAnchor();
1379                 setCursor(cur, 0, 0);
1380         } else {
1381                 setCursor(cur, 0, 0);
1382                 cur.resetAnchor();
1383                 setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
1384         }
1385         cur.setSelection();
1386 }
1387
1388
1389 // Select the word currently under the cursor when no
1390 // selection is currently set
1391 bool Text::selectWordWhenUnderCursor(Cursor & cur, word_location loc)
1392 {
1393         LBUFERR(this == cur.text());
1394         if (cur.selection())
1395                 return false;
1396         selectWord(cur, loc);
1397         return cur.selection();
1398 }
1399
1400
1401 void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
1402 {
1403         LBUFERR(this == cur.text());
1404
1405         if (!cur.selection()) {
1406                 if (!selectChange(cur))
1407                         return;
1408         }
1409
1410         cur.recordUndoSelection();
1411
1412         pit_type begPit = cur.selectionBegin().pit();
1413         pit_type endPit = cur.selectionEnd().pit();
1414
1415         pos_type begPos = cur.selectionBegin().pos();
1416         pos_type endPos = cur.selectionEnd().pos();
1417
1418         // keep selection info, because endPos becomes invalid after the first loop
1419         bool const endsBeforeEndOfPar = (endPos < pars_[endPit].size());
1420
1421         // first, accept/reject changes within each individual paragraph (do not consider end-of-par)
1422         for (pit_type pit = begPit; pit <= endPit; ++pit) {
1423                 pos_type parSize = pars_[pit].size();
1424
1425                 // ignore empty paragraphs; otherwise, an assertion will fail for
1426                 // acceptChanges(bparams, 0, 0) or rejectChanges(bparams, 0, 0)
1427                 if (parSize == 0)
1428                         continue;
1429
1430                 // do not consider first paragraph if the cursor starts at pos size()
1431                 if (pit == begPit && begPos == parSize)
1432                         continue;
1433
1434                 // do not consider last paragraph if the cursor ends at pos 0
1435                 if (pit == endPit && endPos == 0)
1436                         break; // last iteration anyway
1437
1438                 pos_type const left  = (pit == begPit ? begPos : 0);
1439                 pos_type const right = (pit == endPit ? endPos : parSize);
1440
1441                 if (left == right)
1442                         // there is no change here
1443                         continue;
1444
1445                 if (op == ACCEPT) {
1446                         pars_[pit].acceptChanges(left, right);
1447                 } else {
1448                         pars_[pit].rejectChanges(left, right);
1449                 }
1450         }
1451
1452         // next, accept/reject imaginary end-of-par characters
1453
1454         for (pit_type pit = begPit; pit <= endPit; ++pit) {
1455                 pos_type pos = pars_[pit].size();
1456
1457                 // skip if the selection ends before the end-of-par
1458                 if (pit == endPit && endsBeforeEndOfPar)
1459                         break; // last iteration anyway
1460
1461                 // skip if this is not the last paragraph of the document
1462                 // note: the user should be able to accept/reject the par break of the last par!
1463                 if (pit == endPit && pit + 1 != int(pars_.size()))
1464                         break; // last iteration anway
1465
1466                 if (op == ACCEPT) {
1467                         if (pars_[pit].isInserted(pos)) {
1468                                 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1469                         } else if (pars_[pit].isDeleted(pos)) {
1470                                 if (pit + 1 == int(pars_.size())) {
1471                                         // we cannot remove a par break at the end of the last paragraph;
1472                                         // instead, we mark it unchanged
1473                                         pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1474                                 } else {
1475                                         mergeParagraph(cur.buffer()->params(), pars_, pit);
1476                                         --endPit;
1477                                         --pit;
1478                                 }
1479                         }
1480                 } else {
1481                         if (pars_[pit].isDeleted(pos)) {
1482                                 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1483                         } else if (pars_[pit].isInserted(pos)) {
1484                                 if (pit + 1 == int(pars_.size())) {
1485                                         // we mark the par break at the end of the last paragraph unchanged
1486                                         pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1487                                 } else {
1488                                         mergeParagraph(cur.buffer()->params(), pars_, pit);
1489                                         --endPit;
1490                                         --pit;
1491                                 }
1492                         }
1493                 }
1494         }
1495
1496         // finally, invoke the DEPM
1497         deleteEmptyParagraphMechanism(begPit, endPit, cur.buffer()->params().track_changes);
1498
1499         cur.finishUndo();
1500         cur.clearSelection();
1501         setCursorIntern(cur, begPit, begPos);
1502         cur.screenUpdateFlags(Update::Force);
1503         cur.forceBufferUpdate();
1504 }
1505
1506
1507 void Text::acceptChanges()
1508 {
1509         BufferParams const & bparams = owner_->buffer().params();
1510         lyx::acceptChanges(pars_, bparams);
1511         deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.track_changes);
1512 }
1513
1514
1515 void Text::rejectChanges()
1516 {
1517         BufferParams const & bparams = owner_->buffer().params();
1518         pit_type pars_size = static_cast<pit_type>(pars_.size());
1519
1520         // first, reject changes within each individual paragraph
1521         // (do not consider end-of-par)
1522         for (pit_type pit = 0; pit < pars_size; ++pit) {
1523                 if (!pars_[pit].empty())   // prevent assertion failure
1524                         pars_[pit].rejectChanges(0, pars_[pit].size());
1525         }
1526
1527         // next, reject imaginary end-of-par characters
1528         for (pit_type pit = 0; pit < pars_size; ++pit) {
1529                 pos_type pos = pars_[pit].size();
1530
1531                 if (pars_[pit].isDeleted(pos)) {
1532                         pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1533                 } else if (pars_[pit].isInserted(pos)) {
1534                         if (pit == pars_size - 1) {
1535                                 // we mark the par break at the end of the last
1536                                 // paragraph unchanged
1537                                 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
1538                         } else {
1539                                 mergeParagraph(bparams, pars_, pit);
1540                                 --pit;
1541                                 --pars_size;
1542                         }
1543                 }
1544         }
1545
1546         // finally, invoke the DEPM
1547         deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.track_changes);
1548 }
1549
1550
1551 void Text::deleteWordForward(Cursor & cur, bool const force)
1552 {
1553         LBUFERR(this == cur.text());
1554         if (cur.lastpos() == 0)
1555                 cursorForward(cur);
1556         else {
1557                 cur.resetAnchor();
1558                 cur.selection(true);
1559                 cursorForwardOneWord(cur);
1560                 cur.setSelection();
1561                 if (force || !cur.confirmDeletion()) {
1562                         cutSelection(cur, false);
1563                         cur.checkBufferStructure();
1564                 }
1565         }
1566 }
1567
1568
1569 void Text::deleteWordBackward(Cursor & cur, bool const force)
1570 {
1571         LBUFERR(this == cur.text());
1572         if (cur.lastpos() == 0)
1573                 cursorBackward(cur);
1574         else {
1575                 cur.resetAnchor();
1576                 cur.selection(true);
1577                 cursorBackwardOneWord(cur);
1578                 cur.setSelection();
1579                 if (force || !cur.confirmDeletion()) {
1580                         cutSelection(cur, false);
1581                         cur.checkBufferStructure();
1582                 }
1583         }
1584 }
1585
1586
1587 // Kill to end of line.
1588 void Text::changeCase(Cursor & cur, TextCase action, bool partial)
1589 {
1590         LBUFERR(this == cur.text());
1591         CursorSlice from;
1592         CursorSlice to;
1593
1594         bool const gotsel = cur.selection();
1595         if (gotsel) {
1596                 from = cur.selBegin();
1597                 to = cur.selEnd();
1598         } else {
1599                 from = cur.top();
1600                 getWord(from, to, partial ? PARTIAL_WORD : WHOLE_WORD);
1601                 cursorForwardOneWord(cur);
1602         }
1603
1604         cur.recordUndoSelection();
1605
1606         pit_type begPit = from.pit();
1607         pit_type endPit = to.pit();
1608
1609         pos_type begPos = from.pos();
1610         pos_type endPos = to.pos();
1611
1612         pos_type right = 0; // needed after the for loop
1613
1614         for (pit_type pit = begPit; pit <= endPit; ++pit) {
1615                 Paragraph & par = pars_[pit];
1616                 pos_type const pos = (pit == begPit ? begPos : 0);
1617                 right = (pit == endPit ? endPos : par.size());
1618                 par.changeCase(cur.buffer()->params(), pos, right, action);
1619         }
1620
1621         // the selection may have changed due to logically-only deleted chars
1622         if (gotsel) {
1623                 setCursor(cur, begPit, begPos);
1624                 cur.resetAnchor();
1625                 setCursor(cur, endPit, right);
1626                 cur.setSelection();
1627         } else
1628                 setCursor(cur, endPit, right);
1629
1630         cur.checkBufferStructure();
1631 }
1632
1633
1634 bool Text::handleBibitems(Cursor & cur)
1635 {
1636         if (cur.paragraph().layout().labeltype != LABEL_BIBLIO)
1637                 return false;
1638
1639         if (cur.pos() != 0)
1640                 return false;
1641
1642         BufferParams const & bufparams = cur.buffer()->params();
1643         Paragraph const & par = cur.paragraph();
1644         Cursor prevcur = cur;
1645         if (cur.pit() > 0) {
1646                 --prevcur.pit();
1647                 prevcur.pos() = prevcur.lastpos();
1648         }
1649         Paragraph const & prevpar = prevcur.paragraph();
1650
1651         // if a bibitem is deleted, merge with previous paragraph
1652         // if this is a bibliography item as well
1653         if (cur.pit() > 0 && par.layout() == prevpar.layout()) {
1654                 cur.recordUndo(prevcur.pit());
1655                 mergeParagraph(bufparams, cur.text()->paragraphs(),
1656                                                         prevcur.pit());
1657                 cur.forceBufferUpdate();
1658                 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1659                 cur.screenUpdateFlags(Update::Force);
1660                 return true;
1661         }
1662
1663         // otherwise reset to default
1664         cur.paragraph().setPlainOrDefaultLayout(bufparams.documentClass());
1665         return true;
1666 }
1667
1668
1669 bool Text::erase(Cursor & cur)
1670 {
1671         LASSERT(this == cur.text(), return false);
1672         bool needsUpdate = false;
1673         Paragraph & par = cur.paragraph();
1674
1675         if (cur.pos() != cur.lastpos()) {
1676                 // this is the code for a normal delete, not pasting
1677                 // any paragraphs
1678                 cur.recordUndo(DELETE_UNDO);
1679                 bool const was_inset = cur.paragraph().isInset(cur.pos());
1680                 if(!par.eraseChar(cur.pos(), cur.buffer()->params().track_changes))
1681                         // the character has been logically deleted only => skip it
1682                         cur.top().forwardPos();
1683
1684                 if (was_inset)
1685                         cur.forceBufferUpdate();
1686                 else
1687                         cur.checkBufferStructure();
1688                 needsUpdate = true;
1689         } else {
1690                 if (cur.pit() == cur.lastpit())
1691                         return dissolveInset(cur);
1692
1693                 if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1694                         cur.recordUndo(DELETE_UNDO);
1695                         par.setChange(cur.pos(), Change(Change::DELETED));
1696                         cur.forwardPos();
1697                         needsUpdate = true;
1698                 } else {
1699                         setCursorIntern(cur, cur.pit() + 1, 0);
1700                         needsUpdate = backspacePos0(cur);
1701                 }
1702         }
1703
1704         needsUpdate |= handleBibitems(cur);
1705
1706         if (needsUpdate) {
1707                 // Make sure the cursor is correct. Is this really needed?
1708                 // No, not really... at least not here!
1709                 cur.top().setPitPos(cur.pit(), cur.pos());
1710                 cur.checkBufferStructure();
1711         }
1712
1713         return needsUpdate;
1714 }
1715
1716
1717 bool Text::backspacePos0(Cursor & cur)
1718 {
1719         LBUFERR(this == cur.text());
1720         if (cur.pit() == 0)
1721                 return false;
1722
1723         BufferParams const & bufparams = cur.buffer()->params();
1724         ParagraphList & plist = cur.text()->paragraphs();
1725         Paragraph const & par = cur.paragraph();
1726         Cursor prevcur = cur;
1727         --prevcur.pit();
1728         prevcur.pos() = prevcur.lastpos();
1729         Paragraph const & prevpar = prevcur.paragraph();
1730
1731         // is it an empty paragraph?
1732         if (cur.lastpos() == 0
1733             || (cur.lastpos() == 1 && par.isSeparator(0))) {
1734                 cur.recordUndo(prevcur.pit());
1735                 plist.erase(plist.iterator_at(cur.pit()));
1736         }
1737         // is previous par empty?
1738         else if (prevcur.lastpos() == 0
1739                  || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) {
1740                 cur.recordUndo(prevcur.pit());
1741                 plist.erase(plist.iterator_at(prevcur.pit()));
1742         }
1743         // FIXME: Do we really not want to allow this???
1744         // Pasting is not allowed, if the paragraphs have different
1745         // layouts. I think it is a real bug of all other
1746         // word processors to allow it. It confuses the user.
1747         // Correction: Pasting is always allowed with standard-layout
1748         // or the empty layout.
1749         else {
1750                 cur.recordUndo(prevcur.pit());
1751                 mergeParagraph(bufparams, plist, prevcur.pit());
1752         }
1753
1754         cur.forceBufferUpdate();
1755         setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1756
1757         return true;
1758 }
1759
1760
1761 bool Text::backspace(Cursor & cur)
1762 {
1763         LBUFERR(this == cur.text());
1764         bool needsUpdate = false;
1765         if (cur.pos() == 0) {
1766                 if (cur.pit() == 0)
1767                         return dissolveInset(cur);
1768
1769                 Cursor prev_cur = cur;
1770                 --prev_cur.pit();
1771
1772                 if (!cur.paragraph().empty()
1773                     && !prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
1774                         cur.recordUndo(prev_cur.pit(), prev_cur.pit());
1775                         prev_cur.paragraph().setChange(prev_cur.lastpos(), Change(Change::DELETED));
1776                         setCursorIntern(cur, prev_cur.pit(), prev_cur.lastpos());
1777                         return true;
1778                 }
1779                 // The cursor is at the beginning of a paragraph, so
1780                 // the backspace will collapse two paragraphs into one.
1781                 needsUpdate = backspacePos0(cur);
1782
1783         } else {
1784                 // this is the code for a normal backspace, not pasting
1785                 // any paragraphs
1786                 cur.recordUndo(DELETE_UNDO);
1787                 // We used to do cursorBackwardIntern() here, but it is
1788                 // not a good idea since it triggers the auto-delete
1789                 // mechanism. So we do a cursorBackwardIntern()-lite,
1790                 // without the dreaded mechanism. (JMarc)
1791                 setCursorIntern(cur, cur.pit(), cur.pos() - 1,
1792                                 false, cur.boundary());
1793                 bool const was_inset = cur.paragraph().isInset(cur.pos());
1794                 cur.paragraph().eraseChar(cur.pos(), cur.buffer()->params().track_changes);
1795                 if (was_inset)
1796                         cur.forceBufferUpdate();
1797                 else
1798                         cur.checkBufferStructure();
1799         }
1800
1801         if (cur.pos() == cur.lastpos())
1802                 cur.setCurrentFont();
1803
1804         needsUpdate |= handleBibitems(cur);
1805
1806         // A singlePar update is not enough in this case.
1807         // cur.screenUpdateFlags(Update::Force);
1808         cur.top().setPitPos(cur.pit(), cur.pos());
1809
1810         return needsUpdate;
1811 }
1812
1813
1814 bool Text::dissolveInset(Cursor & cur)
1815 {
1816         LASSERT(this == cur.text(), return false);
1817
1818         if (isMainText() || cur.inset().nargs() != 1)
1819                 return false;
1820
1821         cur.recordUndoInset();
1822         cur.setMark(false);
1823         cur.selHandle(false);
1824         // save position inside inset
1825         pos_type spos = cur.pos();
1826         pit_type spit = cur.pit();
1827         bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
1828         cur.popBackward();
1829         // update cursor offset
1830         if (spit == 0)
1831                 spos += cur.pos();
1832         spit += cur.pit();
1833         // remember position outside inset to delete inset later
1834         // we do not do it now to avoid memory reuse issues (see #10667).
1835         DocIterator inset_it = cur;
1836         // jump over inset
1837         ++cur.pos();
1838
1839         Buffer & b = *cur.buffer();
1840         // Is there anything in this text?
1841         if (inset_non_empty) {
1842                 // see bug 7319
1843                 // we clear the cache so that we won't get conflicts with labels
1844                 // that get pasted into the buffer. we should update this before
1845                 // its being empty matters. if not (i.e., if we encounter bugs),
1846                 // then this should instead be:
1847                 //        cur.buffer().updateBuffer();
1848                 // but we'll try the cheaper solution here.
1849                 cur.buffer()->clearReferenceCache();
1850
1851                 ParagraphList & plist = paragraphs();
1852                 if (!lyxrc.ct_markup_copied)
1853                         // Do not revive deleted text
1854                         lyx::acceptChanges(plist, b.params());
1855
1856                 // ERT paragraphs have the Language latex_language.
1857                 // This is invalid outside of ERT, so we need to
1858                 // change it to the buffer language.
1859                 for (auto & p : plist)
1860                         p.changeLanguage(b.params(), latex_language, b.language());
1861
1862                 /* If the inset is the only thing in paragraph and the layout
1863                  * is not plain, then the layout of the first paragraph of
1864                  * inset should be remembered.
1865                  * FIXME: this does not work as expected when change tracking
1866                  *   is on However, we do not really know what to do in this
1867                  *   case.
1868                  */
1869                 DocumentClass const & tclass = cur.buffer()->params().documentClass();
1870                 if (inset_it.lastpos() == 1
1871                     && !tclass.isPlainLayout(plist[0].layout())
1872                     && !tclass.isDefaultLayout(plist[0].layout())) {
1873                         // Copy all parameters except depth.
1874                         Paragraph & par = cur.paragraph();
1875                         par.setLayout(plist[0].layout());
1876                         depth_type const dpth = par.getDepth();
1877                         par.params() = plist[0].params();
1878                         par.params().depth(dpth);
1879                 }
1880
1881                 pasteParagraphList(cur, plist, b.params().documentClassPtr(),
1882                                    b.errorList("Paste"));
1883         }
1884
1885         // delete the inset now
1886         inset_it.paragraph().eraseChar(inset_it.pos(), b.params().track_changes);
1887
1888         // restore position
1889         cur.pit() = min(cur.lastpit(), spit);
1890         cur.pos() = min(cur.lastpos(), spos);
1891         // Ensure the current language is set correctly (bug 6292)
1892         cur.text()->setCursor(cur, cur.pit(), cur.pos());
1893         cur.clearSelection();
1894         cur.resetAnchor();
1895         cur.forceBufferUpdate();
1896
1897         return true;
1898 }
1899
1900
1901 void Text::getWord(CursorSlice & from, CursorSlice & to,
1902         word_location const loc) const
1903 {
1904         to = from;
1905         pars_[to.pit()].locateWord(from.pos(), to.pos(), loc);
1906 }
1907
1908
1909 void Text::write(ostream & os) const
1910 {
1911         Buffer const & buf = owner_->buffer();
1912         ParagraphList::const_iterator pit = paragraphs().begin();
1913         ParagraphList::const_iterator end = paragraphs().end();
1914         depth_type dth = 0;
1915         for (; pit != end; ++pit)
1916                 pit->write(os, buf.params(), dth);
1917
1918         // Close begin_deeper
1919         for(; dth > 0; --dth)
1920                 os << "\n\\end_deeper";
1921 }
1922
1923
1924 bool Text::read(Lexer & lex,
1925                 ErrorList & errorList, InsetText * insetPtr)
1926 {
1927         Buffer const & buf = owner_->buffer();
1928         depth_type depth = 0;
1929         bool res = true;
1930
1931         while (lex.isOK()) {
1932                 lex.nextToken();
1933                 string const token = lex.getString();
1934
1935                 if (token.empty())
1936                         continue;
1937
1938                 if (token == "\\end_inset")
1939                         break;
1940
1941                 if (token == "\\end_body")
1942                         continue;
1943
1944                 if (token == "\\begin_body")
1945                         continue;
1946
1947                 if (token == "\\end_document") {
1948                         res = false;
1949                         break;
1950                 }
1951
1952                 if (token == "\\begin_layout") {
1953                         lex.pushToken(token);
1954
1955                         Paragraph par;
1956                         par.setInsetOwner(insetPtr);
1957                         par.params().depth(depth);
1958                         par.setFont(0, Font(inherit_font, buf.params().language));
1959                         pars_.push_back(par);
1960                         readParagraph(pars_.back(), lex, errorList);
1961
1962                         // register the words in the global word list
1963                         pars_.back().updateWords();
1964                 } else if (token == "\\begin_deeper") {
1965                         ++depth;
1966                 } else if (token == "\\end_deeper") {
1967                         if (!depth)
1968                                 lex.printError("\\end_deeper: " "depth is already null");
1969                         else
1970                                 --depth;
1971                 } else {
1972                         LYXERR0("Handling unknown body token: `" << token << '\'');
1973                 }
1974         }
1975
1976         // avoid a crash on weird documents (bug 4859)
1977         if (pars_.empty()) {
1978                 Paragraph par;
1979                 par.setInsetOwner(insetPtr);
1980                 par.params().depth(depth);
1981                 par.setFont(0, Font(inherit_font,
1982                                     buf.params().language));
1983                 par.setPlainOrDefaultLayout(buf.params().documentClass());
1984                 pars_.push_back(par);
1985         }
1986
1987         return res;
1988 }
1989
1990
1991 // Returns the current state (font, depth etc.) as a message for status bar.
1992 docstring Text::currentState(CursorData const & cur, bool devel_mode) const
1993 {
1994         LBUFERR(this == cur.text());
1995         Buffer & buf = *cur.buffer();
1996         Paragraph const & par = cur.paragraph();
1997         odocstringstream os;
1998
1999         if (buf.params().track_changes)
2000                 os << _("[Change Tracking] ");
2001
2002         Change change = par.lookupChange(cur.pos());
2003
2004         if (change.changed()) {
2005                 docstring const author =
2006                         buf.params().authors().get(change.author).nameAndEmail();
2007                 docstring const date = formatted_datetime(change.changetime);
2008                 os << bformat(_("Changed by %1$s[[author]] on %2$s[[date]]. "),
2009                               author, date);
2010         }
2011
2012         // I think we should only show changes from the default
2013         // font. (Asger)
2014         // No, from the document font (MV)
2015         Font font = cur.real_current_font;
2016         font.fontInfo().reduce(buf.params().getFont().fontInfo());
2017
2018         os << bformat(_("Font: %1$s"), font.stateText(&buf.params()));
2019
2020         // The paragraph depth
2021         int depth = par.getDepth();
2022         if (depth > 0)
2023                 os << bformat(_(", Depth: %1$d"), depth);
2024
2025         // The paragraph spacing, but only if different from
2026         // buffer spacing.
2027         Spacing const & spacing = par.params().spacing();
2028         if (!spacing.isDefault()) {
2029                 os << _(", Spacing: ");
2030                 switch (spacing.getSpace()) {
2031                 case Spacing::Single:
2032                         os << _("Single");
2033                         break;
2034                 case Spacing::Onehalf:
2035                         os << _("OneHalf");
2036                         break;
2037                 case Spacing::Double:
2038                         os << _("Double");
2039                         break;
2040                 case Spacing::Other:
2041                         os << _("Other (") << from_ascii(spacing.getValueAsString()) << ')';
2042                         break;
2043                 case Spacing::Default:
2044                         // should never happen, do nothing
2045                         break;
2046                 }
2047         }
2048
2049         // Custom text style
2050         InsetLayout const & layout = cur.inset().getLayout();
2051         if (layout.lyxtype() == InsetLyXType::CHARSTYLE)
2052                 os << _(", Style: ") << translateIfPossible(layout.labelstring());
2053
2054         if (devel_mode) {
2055                 os << _(", Inset: ") << &cur.inset();
2056                 if (cur.lastidx() > 0)
2057                         os << _(", Cell: ") << cur.idx();
2058                 os << _(", Paragraph: ") << cur.pit();
2059                 os << _(", Id: ") << par.id();
2060                 os << _(", Position: ") << cur.pos();
2061                 // FIXME: Why is the check for par.size() needed?
2062                 // We are called with cur.pos() == par.size() quite often.
2063                 if (!par.empty() && cur.pos() < par.size()) {
2064                         // Force output of code point, not character
2065                         size_t const c = par.getChar(cur.pos());
2066                         os << _(", Char: 0x") << hex << c;
2067                 }
2068                 os << _(", Boundary: ") << cur.boundary();
2069 //              Row & row = cur.textRow();
2070 //              os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos());
2071         }
2072         return os.str();
2073 }
2074
2075
2076 docstring Text::getPossibleLabel(DocIterator const & cur) const
2077 {
2078         pit_type textpit = cur.pit();
2079         Layout const * layout = &(pars_[textpit].layout());
2080
2081         // Will contain the label prefix.
2082         docstring name;
2083
2084         // For captions, we just take the caption type
2085         Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE);
2086         if (caption_inset) {
2087                 string const & ftype = static_cast<InsetCaption *>(caption_inset)->floattype();
2088                 FloatList const & fl = cur.buffer()->params().documentClass().floats();
2089                 if (fl.typeExist(ftype)) {
2090                         Floating const & flt = fl.getType(ftype);
2091                         name = from_utf8(flt.refPrefix());
2092                 }
2093                 if (name.empty())
2094                         name = from_utf8(ftype.substr(0,3));
2095         } else {
2096                 // For section, subsection, etc...
2097                 if (layout->latextype == LATEX_PARAGRAPH && textpit != 0) {
2098                         Layout const * layout2 = &(pars_[textpit - 1].layout());
2099                         if (layout2->latextype != LATEX_PARAGRAPH) {
2100                                 --textpit;
2101                                 layout = layout2;
2102                         }
2103                 }
2104                 if (layout->latextype != LATEX_PARAGRAPH)
2105                         name = layout->refprefix;
2106
2107                 // If none of the above worked, see if the inset knows.
2108                 if (name.empty()) {
2109                         InsetLayout const & il = cur.inset().getLayout();
2110                         name = il.refprefix();
2111                 }
2112         }
2113
2114         docstring text;
2115         docstring par_text = pars_[textpit].asString(AS_STR_SKIPDELETE);
2116
2117         // The return string of math matrices might contain linebreaks
2118         par_text = subst(par_text, '\n', '-');
2119         int const numwords = 3;
2120         for (int i = 0; i < numwords; ++i) {
2121                 if (par_text.empty())
2122                         break;
2123                 docstring head;
2124                 par_text = split(par_text, head, ' ');
2125                 // Is it legal to use spaces in labels ?
2126                 if (i > 0)
2127                         text += '-';
2128                 text += head;
2129         }
2130
2131         // Make sure it isn't too long
2132         unsigned int const max_label_length = 32;
2133         if (text.size() > max_label_length)
2134                 text.resize(max_label_length);
2135
2136         if (!name.empty())
2137                 text = name + ':' + text;
2138
2139         // We need a unique label
2140         docstring label = text;
2141         int i = 1;
2142         while (cur.buffer()->activeLabel(label)) {
2143                         label = text + '-' + convert<docstring>(i);
2144                         ++i;
2145                 }
2146
2147         return label;
2148 }
2149
2150
2151 docstring Text::asString(int options) const
2152 {
2153         return asString(0, pars_.size(), options);
2154 }
2155
2156
2157 docstring Text::asString(pit_type beg, pit_type end, int options) const
2158 {
2159         size_t i = size_t(beg);
2160         docstring str = pars_[i].asString(options);
2161         for (++i; i != size_t(end); ++i) {
2162                 str += '\n';
2163                 str += pars_[i].asString(options);
2164         }
2165         return str;
2166 }
2167
2168
2169 void Text::shortenForOutliner(docstring & str, size_t const maxlen)
2170 {
2171         support::truncateWithEllipsis(str, maxlen);
2172         for (char_type & c : str)
2173                 if (c == L'\n' || c == L'\t')
2174                         c = L' ';
2175 }
2176
2177
2178 void Text::forOutliner(docstring & os, size_t const maxlen,
2179                        bool const shorten) const
2180 {
2181         pit_type end = pars_.size() - 1;
2182         if (0 <= end && !pars_[0].labelString().empty())
2183                 os += pars_[0].labelString() + ' ';
2184         forOutliner(os, maxlen, 0, end, shorten);
2185 }
2186
2187
2188 void Text::forOutliner(docstring & os, size_t const maxlen,
2189                        pit_type pit_start, pit_type pit_end,
2190                        bool const shorten) const
2191 {
2192         size_t tmplen = shorten ? maxlen + 1 : maxlen;
2193         pit_type end = min(size_t(pit_end), pars_.size() - 1);
2194         bool first = true;
2195         for (pit_type i = pit_start; i <= end && os.length() < tmplen; ++i) {
2196                 if (!first)
2197                         os += ' ';
2198                 // This function lets the first label be treated separately
2199                 pars_[i].forOutliner(os, tmplen, false, !first);
2200                 first = false;
2201         }
2202         if (shorten)
2203                 shortenForOutliner(os, maxlen);
2204 }
2205
2206
2207 void Text::charsTranspose(Cursor & cur)
2208 {
2209         LBUFERR(this == cur.text());
2210
2211         pos_type pos = cur.pos();
2212
2213         // If cursor is at beginning or end of paragraph, do nothing.
2214         if (pos == cur.lastpos() || pos == 0)
2215                 return;
2216
2217         Paragraph & par = cur.paragraph();
2218
2219         // Get the positions of the characters to be transposed.
2220         pos_type pos1 = pos - 1;
2221         pos_type pos2 = pos;
2222
2223         // In change tracking mode, ignore deleted characters.
2224         while (pos2 < cur.lastpos() && par.isDeleted(pos2))
2225                 ++pos2;
2226         if (pos2 == cur.lastpos())
2227                 return;
2228
2229         while (pos1 >= 0 && par.isDeleted(pos1))
2230                 --pos1;
2231         if (pos1 < 0)
2232                 return;
2233
2234         // Don't do anything if one of the "characters" is not regular text.
2235         if (par.isInset(pos1) || par.isInset(pos2))
2236                 return;
2237
2238         // Store the characters to be transposed (including font information).
2239         char_type const char1 = par.getChar(pos1);
2240         Font const font1 =
2241                 par.getFontSettings(cur.buffer()->params(), pos1);
2242
2243         char_type const char2 = par.getChar(pos2);
2244         Font const font2 =
2245                 par.getFontSettings(cur.buffer()->params(), pos2);
2246
2247         // And finally, we are ready to perform the transposition.
2248         // Track the changes if Change Tracking is enabled.
2249         bool const trackChanges = cur.buffer()->params().track_changes;
2250
2251         cur.recordUndo();
2252
2253         par.eraseChar(pos2, trackChanges);
2254         par.eraseChar(pos1, trackChanges);
2255         par.insertChar(pos1, char2, font2, trackChanges);
2256         par.insertChar(pos2, char1, font1, trackChanges);
2257
2258         cur.checkBufferStructure();
2259
2260         // After the transposition, move cursor to after the transposition.
2261         setCursor(cur, cur.pit(), pos2);
2262         cur.forwardPos();
2263 }
2264
2265
2266 DocIterator Text::macrocontextPosition() const
2267 {
2268         return macrocontext_position_;
2269 }
2270
2271
2272 void Text::setMacrocontextPosition(DocIterator const & pos)
2273 {
2274         macrocontext_position_ = pos;
2275 }
2276
2277
2278 bool Text::completionSupported(Cursor const & cur) const
2279 {
2280         Paragraph const & par = cur.paragraph();
2281         return !cur.selection()
2282                 && cur.pos() > 0
2283                 && (cur.pos() >= par.size() || par.isWordSeparator(cur.pos()))
2284                 && !par.isWordSeparator(cur.pos() - 1);
2285 }
2286
2287
2288 CompletionList const * Text::createCompletionList(Cursor const & cur) const
2289 {
2290         WordList const & list = theWordList(cur.getFont().language()->lang());
2291         return new TextCompletionList(cur, list);
2292 }
2293
2294
2295 bool Text::insertCompletion(Cursor & cur, docstring const & s, bool /*finished*/)
2296 {
2297         LBUFERR(cur.bv().cursor() == cur);
2298         cur.insert(s);
2299         cur.bv().cursor() = cur;
2300         if (!(cur.result().screenUpdate() & Update::Force))
2301                 cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
2302         return true;
2303 }
2304
2305
2306 docstring Text::completionPrefix(Cursor const & cur) const
2307 {
2308         CursorSlice from = cur.top();
2309         CursorSlice to = from;
2310         getWord(from, to, PREVIOUS_WORD);
2311
2312         return cur.paragraph().asString(from.pos(), to.pos());
2313 }
2314
2315 } // namespace lyx