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