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