]> git.lyx.org Git - lyx.git/blob - src/Text.cpp
a9b111b63550b9ba8666e8afa652e5e1ebbdf053
[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 "FontIterator.h"
38 #include "Language.h"
39 #include "Length.h"
40 #include "Lexer.h"
41 #include "LyXRC.h"
42 #include "Paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "ParIterator.h"
46 #include "TextClass.h"
47 #include "TextMetrics.h"
48 #include "VSpace.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/InsetLine.h"
56 #include "insets/InsetNewline.h"
57 #include "insets/InsetNewpage.h"
58 #include "insets/InsetOptArg.h"
59 #include "insets/InsetSpace.h"
60 #include "insets/InsetSpecialChar.h"
61 #include "insets/InsetTabular.h"
62
63 #include "support/convert.h"
64 #include "support/debug.h"
65 #include "support/docstream.h"
66 #include "support/gettext.h"
67 #include "support/lstrings.h"
68 #include "support/textutils.h"
69
70 #include <boost/next_prior.hpp>
71
72 #include <sstream>
73
74 using namespace std;
75 using namespace lyx::support;
76
77 namespace lyx {
78
79 using cap::cutSelection;
80 using cap::pasteParagraphList;
81
82 namespace {
83
84 void readParToken(Buffer const & buf, Paragraph & par, Lexer & lex,
85         string const & token, Font & font, Change & change, ErrorList & errorList)
86 {
87         BufferParams const & bp = buf.params();
88
89         if (token[0] != '\\') {
90                 docstring dstr = lex.getDocString();
91                 par.appendString(dstr, font, change);
92
93         } else if (token == "\\begin_layout") {
94                 lex.eatLine();
95                 docstring layoutname = lex.getDocString();
96
97                 font = Font(inherit_font, bp.language);
98                 change = Change(Change::UNCHANGED);
99
100                 DocumentClass const & tclass = bp.documentClass();
101
102                 if (layoutname.empty())
103                         layoutname = tclass.defaultLayoutName();
104
105                 if (par.forceEmptyLayout()) {
106                         // in this case only the empty layout is allowed
107                         layoutname = tclass.emptyLayoutName();
108                 } else if (par.useEmptyLayout()) {
109                         // in this case, default layout maps to empty layout 
110                         if (layoutname == tclass.defaultLayoutName())
111                                 layoutname = tclass.emptyLayoutName();
112                 } else { 
113                         // otherwise, the empty layout maps to the default
114                         if (layoutname == tclass.emptyLayoutName())
115                                 layoutname = tclass.defaultLayoutName();
116                 }
117
118                 bool hasLayout = tclass.hasLayout(layoutname);
119
120                 if (!hasLayout) {
121                         errorList.push_back(ErrorItem(_("Unknown layout"),
122                         bformat(_("Layout '%1$s' does not exist in textclass '%2$s'\nTrying to use the default instead.\n"),
123                         layoutname, from_utf8(tclass.name())), par.id(), 0, par.size()));
124                         layoutname = par.useEmptyLayout() ? 
125                                         tclass.emptyLayoutName() :
126                                         tclass.defaultLayoutName();
127                 }
128
129                 par.setLayout(bp.documentClass()[layoutname]);
130
131                 // Test whether the layout is obsolete.
132                 Layout const & layout = par.layout();
133                 if (!layout.obsoleted_by().empty())
134                         par.setLayout(bp.documentClass()[layout.obsoleted_by()]);
135
136                 par.params().read(lex);
137
138         } else if (token == "\\end_layout") {
139                 LYXERR0("Solitary \\end_layout in line " << lex.getLineNo() << "\n"
140                        << "Missing \\begin_layout ?");
141         } else if (token == "\\end_inset") {
142                 LYXERR0("Solitary \\end_inset in line " << lex.getLineNo() << "\n"
143                        << "Missing \\begin_inset ?");
144         } else if (token == "\\begin_inset") {
145                 Inset * inset = readInset(lex, buf);
146                 if (inset)
147                         par.insertInset(par.size(), inset, font, change);
148                 else {
149                         lex.eatLine();
150                         docstring line = lex.getDocString();
151                         errorList.push_back(ErrorItem(_("Unknown Inset"), line,
152                                             par.id(), 0, par.size()));
153                 }
154         } else if (token == "\\family") {
155                 lex.next();
156                 setLyXFamily(lex.getString(), font.fontInfo());
157         } else if (token == "\\series") {
158                 lex.next();
159                 setLyXSeries(lex.getString(), font.fontInfo());
160         } else if (token == "\\shape") {
161                 lex.next();
162                 setLyXShape(lex.getString(), font.fontInfo());
163         } else if (token == "\\size") {
164                 lex.next();
165                 setLyXSize(lex.getString(), font.fontInfo());
166         } else if (token == "\\lang") {
167                 lex.next();
168                 string const tok = lex.getString();
169                 Language const * lang = languages.getLanguage(tok);
170                 if (lang) {
171                         font.setLanguage(lang);
172                 } else {
173                         font.setLanguage(bp.language);
174                         lex.printError("Unknown language `$$Token'");
175                 }
176         } else if (token == "\\numeric") {
177                 lex.next();
178                 font.fontInfo().setNumber(font.setLyXMisc(lex.getString()));
179         } else if (token == "\\emph") {
180                 lex.next();
181                 font.fontInfo().setEmph(font.setLyXMisc(lex.getString()));
182         } else if (token == "\\bar") {
183                 lex.next();
184                 string const tok = lex.getString();
185
186                 if (tok == "under")
187                         font.fontInfo().setUnderbar(FONT_ON);
188                 else if (tok == "no")
189                         font.fontInfo().setUnderbar(FONT_OFF);
190                 else if (tok == "default")
191                         font.fontInfo().setUnderbar(FONT_INHERIT);
192                 else
193                         lex.printError("Unknown bar font flag "
194                                        "`$$Token'");
195         } else if (token == "\\noun") {
196                 lex.next();
197                 font.fontInfo().setNoun(font.setLyXMisc(lex.getString()));
198         } else if (token == "\\color") {
199                 lex.next();
200                 setLyXColor(lex.getString(), font.fontInfo());
201         } else if (token == "\\SpecialChar") {
202                         auto_ptr<Inset> inset;
203                         inset.reset(new InsetSpecialChar);
204                         inset->read(lex);
205                         par.insertInset(par.size(), inset.release(),
206                                         font, change);
207         } else if (token == "\\backslash") {
208                 par.appendChar('\\', font, change);
209         } else if (token == "\\linebreak") {
210                 auto_ptr<Inset> inset(new InsetLinebreak);
211                 inset->read(lex);
212                 par.insertInset(par.size(), inset.release(), font, change);
213         } else if (token == "\\newline") {
214                 auto_ptr<Inset> inset(new InsetNewline);
215                 inset->read(lex);
216                 par.insertInset(par.size(), inset.release(), font, change);
217         } else if (token == "\\LyXTable") {
218                 auto_ptr<Inset> inset(new InsetTabular(buf));
219                 inset->read(lex);
220                 par.insertInset(par.size(), inset.release(), font, change);
221         } else if (token == "\\lyxline") {
222                 par.insertInset(par.size(), new InsetLine, font, change);
223         } else if (token == "\\change_unchanged") {
224                 change = Change(Change::UNCHANGED);
225         } else if (token == "\\change_inserted") {
226                 lex.eatLine();
227                 istringstream is(lex.getString());
228                 unsigned int aid;
229                 time_t ct;
230                 is >> aid >> ct;
231                 if (aid >= bp.author_map.size()) {
232                         errorList.push_back(ErrorItem(_("Change tracking error"),
233                                             bformat(_("Unknown author index for insertion: %1$d\n"), aid),
234                                             par.id(), 0, par.size()));
235                         change = Change(Change::UNCHANGED);
236                 } else
237                         change = Change(Change::INSERTED, bp.author_map[aid], ct);
238         } else if (token == "\\change_deleted") {
239                 lex.eatLine();
240                 istringstream is(lex.getString());
241                 unsigned int aid;
242                 time_t ct;
243                 is >> aid >> ct;
244                 if (aid >= bp.author_map.size()) {
245                         errorList.push_back(ErrorItem(_("Change tracking error"),
246                                             bformat(_("Unknown author index for deletion: %1$d\n"), aid),
247                                             par.id(), 0, par.size()));
248                         change = Change(Change::UNCHANGED);
249                 } else
250                         change = Change(Change::DELETED, bp.author_map[aid], ct);
251         } else {
252                 lex.eatLine();
253                 errorList.push_back(ErrorItem(_("Unknown token"),
254                         bformat(_("Unknown token: %1$s %2$s\n"), from_utf8(token),
255                         lex.getDocString()),
256                         par.id(), 0, par.size()));
257         }
258 }
259
260
261 void readParagraph(Buffer const & buf, Paragraph & par, Lexer & lex,
262         ErrorList & errorList)
263 {
264         lex.nextToken();
265         string token = lex.getString();
266         Font font;
267         Change change(Change::UNCHANGED);
268
269         while (lex.isOK()) {
270                 readParToken(buf, par, lex, token, font, change, errorList);
271
272                 lex.nextToken();
273                 token = lex.getString();
274
275                 if (token.empty())
276                         continue;
277
278                 if (token == "\\end_layout") {
279                         //Ok, paragraph finished
280                         break;
281                 }
282
283                 LYXERR(Debug::PARSER, "Handling paragraph token: `" << token << '\'');
284                 if (token == "\\begin_layout" || token == "\\end_document"
285                     || token == "\\end_inset" || token == "\\begin_deeper"
286                     || token == "\\end_deeper") {
287                         lex.pushToken(token);
288                         lyxerr << "Paragraph ended in line "
289                                << lex.getLineNo() << "\n"
290                                << "Missing \\end_layout.\n";
291                         break;
292                 }
293         }
294         // Final change goes to paragraph break:
295         par.setChange(par.size(), change);
296
297         // Initialize begin_of_body_ on load; redoParagraph maintains
298         par.setBeginOfBody();
299 }
300
301
302 } // namespace anon
303
304 class TextCompletionList : public CompletionList
305 {
306 public:
307         ///
308         TextCompletionList(Cursor const & cur)
309         : buf_(cur.buffer()), pos_(0) {}
310         ///
311         virtual ~TextCompletionList() {}
312         
313         ///
314         virtual bool sorted() const { return true; }
315         ///
316         virtual size_t size() const
317         {
318                 return theWordList().size();
319         }
320         ///
321         virtual docstring const & data(size_t idx) const
322         {
323                 return theWordList().word(idx);
324         }
325         
326 private:
327         ///
328         Buffer const & buf_;
329         ///
330         size_t pos_;
331 };
332
333
334 bool Text::empty() const
335 {
336         return pars_.empty() || (pars_.size() == 1 && pars_[0].empty()
337                 // FIXME: Should we consider the labeled type as empty too? 
338                 && pars_[0].layout().labeltype == LABEL_NO_LABEL);
339 }
340
341
342 double Text::spacing(Buffer const & buffer, Paragraph const & par) const
343 {
344         if (par.params().spacing().isDefault())
345                 return buffer.params().spacing().getValue();
346         return par.params().spacing().getValue();
347 }
348
349
350 void Text::breakParagraph(Cursor & cur, bool inverse_logic)
351 {
352         BOOST_ASSERT(this == cur.text());
353
354         Paragraph & cpar = cur.paragraph();
355         pit_type cpit = cur.pit();
356
357         DocumentClass const & tclass = cur.buffer().params().documentClass();
358         Layout const & layout = cpar.layout();
359
360         // this is only allowed, if the current paragraph is not empty
361         // or caption and if it has not the keepempty flag active
362         if (cur.lastpos() == 0 && !cpar.allowEmpty() &&
363             layout.labeltype != LABEL_SENSITIVE)
364                 return;
365
366         // a layout change may affect also the following paragraph
367         recUndo(cur, cur.pit(), undoSpan(cur.pit()) - 1);
368
369         // Always break behind a space
370         // It is better to erase the space (Dekel)
371         if (cur.pos() != cur.lastpos() && cpar.isLineSeparator(cur.pos()))
372                 cpar.eraseChar(cur.pos(), cur.buffer().params().trackChanges);
373
374         // What should the layout for the new paragraph be?
375         bool keep_layout = inverse_logic ? 
376                 !layout.isEnvironment() 
377                 : layout.isEnvironment();
378
379         // We need to remember this before we break the paragraph, because
380         // that invalidates the layout variable
381         bool sensitive = layout.labeltype == LABEL_SENSITIVE;
382
383         // we need to set this before we insert the paragraph.
384         bool const isempty = cpar.allowEmpty() && cpar.empty();
385
386         lyx::breakParagraph(cur.buffer().params(), paragraphs(), cpit,
387                          cur.pos(), keep_layout);
388
389         // After this, neither paragraph contains any rows!
390
391         cpit = cur.pit();
392         pit_type next_par = cpit + 1;
393
394         // well this is the caption hack since one caption is really enough
395         if (sensitive) {
396                 if (cur.pos() == 0)
397                         // set to standard-layout
398                 //FIXME Check if this should be emptyLayout() in some cases
399                         pars_[cpit].applyLayout(tclass.defaultLayout());
400                 else
401                         // set to standard-layout
402                         //FIXME Check if this should be emptyLayout() in some cases
403                         pars_[next_par].applyLayout(tclass.defaultLayout());
404         }
405
406         while (!pars_[next_par].empty() && pars_[next_par].isNewline(0)) {
407                 if (!pars_[next_par].eraseChar(0, cur.buffer().params().trackChanges))
408                         break; // the character couldn't be deleted physically due to change tracking
409         }
410
411         updateLabels(cur.buffer());
412
413         // A singlePar update is not enough in this case.
414         cur.updateFlags(Update::Force);
415
416         // This check is necessary. Otherwise the new empty paragraph will
417         // be deleted automatically. And it is more friendly for the user!
418         if (cur.pos() != 0 || isempty)
419                 setCursor(cur, cur.pit() + 1, 0);
420         else
421                 setCursor(cur, cur.pit(), 0);
422 }
423
424
425 // insert a character, moves all the following breaks in the
426 // same Paragraph one to the right and make a rebreak
427 void Text::insertChar(Cursor & cur, char_type c)
428 {
429         BOOST_ASSERT(this == cur.text());
430
431         cur.recordUndo(INSERT_UNDO);
432
433         TextMetrics const & tm = cur.bv().textMetrics(this);
434         Buffer const & buffer = cur.buffer();
435         Paragraph & par = cur.paragraph();
436         // try to remove this
437         pit_type const pit = cur.pit();
438
439         bool const freeSpacing = par.layout().free_spacing ||
440                 par.isFreeSpacing();
441
442         if (lyxrc.auto_number) {
443                 static docstring const number_operators = from_ascii("+-/*");
444                 static docstring const number_unary_operators = from_ascii("+-");
445                 static docstring const number_seperators = from_ascii(".,:");
446
447                 if (cur.current_font.fontInfo().number() == FONT_ON) {
448                         if (!isDigit(c) && !contains(number_operators, c) &&
449                             !(contains(number_seperators, c) &&
450                               cur.pos() != 0 &&
451                               cur.pos() != cur.lastpos() &&
452                               tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
453                               tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON)
454                            )
455                                 number(cur); // Set current_font.number to OFF
456                 } else if (isDigit(c) &&
457                            cur.real_current_font.isVisibleRightToLeft()) {
458                         number(cur); // Set current_font.number to ON
459
460                         if (cur.pos() != 0) {
461                                 char_type const c = par.getChar(cur.pos() - 1);
462                                 if (contains(number_unary_operators, c) &&
463                                     (cur.pos() == 1
464                                      || par.isSeparator(cur.pos() - 2)
465                                      || par.isNewline(cur.pos() - 2))
466                                   ) {
467                                         setCharFont(buffer, pit, cur.pos() - 1, cur.current_font,
468                                                 tm.font_);
469                                 } else if (contains(number_seperators, c)
470                                      && cur.pos() >= 2
471                                      && tm.displayFont(pit, cur.pos() - 2).fontInfo().number() == FONT_ON) {
472                                         setCharFont(buffer, pit, cur.pos() - 1, cur.current_font,
473                                                 tm.font_);
474                                 }
475                         }
476                 }
477         }
478
479         // In Bidi text, we want spaces to be treated in a special way: spaces
480         // which are between words in different languages should get the 
481         // paragraph's language; otherwise, spaces should keep the language 
482         // they were originally typed in. This is only in effect while typing;
483         // after the text is already typed in, the user can always go back and
484         // explicitly set the language of a space as desired. But 99.9% of the
485         // time, what we're doing here is what the user actually meant.
486         // 
487         // The following cases are the ones in which the language of the space
488         // should be changed to match that of the containing paragraph. In the
489         // depictions, lowercase is LTR, uppercase is RTL, underscore (_) 
490         // represents a space, pipe (|) represents the cursor position (so the
491         // character before it is the one just typed in). The different cases
492         // are depicted logically (not visually), from left to right:
493         // 
494         // 1. A_a|
495         // 2. a_A|
496         //
497         // Theoretically, there are other situations that we should, perhaps, deal
498         // with (e.g.: a|_A, A|_a). In practice, though, there really isn't any 
499         // point (to understand why, just try to create this situation...).
500
501         if ((cur.pos() >= 2) && (par.isLineSeparator(cur.pos() - 1))) {
502                 // get font in front and behind the space in question. But do NOT 
503                 // use getFont(cur.pos()) because the character c is not inserted yet
504                 Font const pre_space_font  = tm.displayFont(cur.pit(), cur.pos() - 2);
505                 Font const & post_space_font = cur.real_current_font;
506                 bool pre_space_rtl  = pre_space_font.isVisibleRightToLeft();
507                 bool post_space_rtl = post_space_font.isVisibleRightToLeft();
508                 
509                 if (pre_space_rtl != post_space_rtl) {
510                         // Set the space's language to match the language of the 
511                         // adjacent character whose direction is the paragraph's
512                         // direction; don't touch other properties of the font
513                         Language const * lang = 
514                                 (pre_space_rtl == par.isRTL(buffer.params())) ?
515                                 pre_space_font.language() : post_space_font.language();
516
517                         Font space_font = tm.displayFont(cur.pit(), cur.pos() - 1);
518                         space_font.setLanguage(lang);
519                         par.setFont(cur.pos() - 1, space_font);
520                 }
521         }
522         
523         // Next check, if there will be two blanks together or a blank at
524         // the beginning of a paragraph.
525         // I decided to handle blanks like normal characters, the main
526         // difference are the special checks when calculating the row.fill
527         // (blank does not count at the end of a row) and the check here
528
529         // When the free-spacing option is set for the current layout,
530         // disable the double-space checking
531         if (!freeSpacing && isLineSeparatorChar(c)) {
532                 if (cur.pos() == 0) {
533                         static bool sent_space_message = false;
534                         if (!sent_space_message) {
535                                 cur.message(_("You cannot insert a space at the "
536                                                            "beginning of a paragraph. Please read the Tutorial."));
537                                 sent_space_message = true;
538                         }
539                         return;
540                 }
541                 BOOST_ASSERT(cur.pos() > 0);
542                 if ((par.isLineSeparator(cur.pos() - 1) || par.isNewline(cur.pos() - 1))
543                     && !par.isDeleted(cur.pos() - 1)) {
544                         static bool sent_space_message = false;
545                         if (!sent_space_message) {
546                                 cur.message(_("You cannot type two spaces this way. "
547                                                            "Please read the Tutorial."));
548                                 sent_space_message = true;
549                         }
550                         return;
551                 }
552         }
553
554         par.insertChar(cur.pos(), c, cur.current_font, cur.buffer().params().trackChanges);
555         cur.checkBufferStructure();
556
557 //              cur.updateFlags(Update::Force);
558         setCursor(cur.top(), cur.pit(), cur.pos() + 1);
559         charInserted(cur);
560 }
561
562
563 void Text::charInserted(Cursor & cur)
564 {
565         Paragraph & par = cur.paragraph();
566
567         // Here we call finishUndo for every 20 characters inserted.
568         // This is from my experience how emacs does it. (Lgb)
569         static unsigned int counter;
570         if (counter < 20) {
571                 ++counter;
572         } else {
573                 cur.finishUndo();
574                 counter = 0;
575         }
576
577         // register word if a non-letter was entered
578         if (cur.pos() > 1
579             && par.isLetter(cur.pos() - 2)
580             && !par.isLetter(cur.pos() - 1)) {
581                 // get the word in front of cursor
582                 BOOST_ASSERT(this == cur.text());
583                 cur.paragraph().updateWords(cur.top());
584         }
585 }
586
587
588 // the cursor set functions have a special mechanism. When they
589 // realize, that you left an empty paragraph, they will delete it.
590
591 bool Text::cursorForwardOneWord(Cursor & cur)
592 {
593         BOOST_ASSERT(this == cur.text());
594
595         Cursor old = cur;
596
597         if (old.pos() == old.lastpos() && old.pit() != old.lastpit()) {
598                 ++old.pit();
599                 old.pos() = 0;
600         } else {
601                 // Advance through word.
602                 while (old.pos() != old.lastpos() && old.paragraph().isLetter(old.pos()))
603                         ++old.pos();
604                 // Skip through trailing nonword stuff.
605                 while (old.pos() != old.lastpos() && !old.paragraph().isLetter(old.pos()))
606                         ++old.pos();
607         }
608         return setCursor(cur, old.pit(), old.pos());
609 }
610
611
612 bool Text::cursorBackwardOneWord(Cursor & cur)
613 {
614         BOOST_ASSERT(this == cur.text());
615
616         Cursor old = cur;
617
618         if (old.pos() == 0 && old.pit() != 0) {
619                 --old.pit();
620                 old.pos() = old.lastpos();
621         } else {
622                 // Skip through initial nonword stuff.
623                 while (old.pos() != 0 && !old.paragraph().isLetter(old.pos() - 1))
624                         --old.pos();
625                 // Advance through word.
626                 while (old.pos() != 0 && old.paragraph().isLetter(old.pos() - 1))
627                         --old.pos();
628         }
629         return setCursor(cur, old.pit(), old.pos());
630 }
631
632
633 void Text::selectWord(Cursor & cur, word_location loc)
634 {
635         BOOST_ASSERT(this == cur.text());
636         CursorSlice from = cur.top();
637         CursorSlice to = cur.top();
638         getWord(from, to, loc);
639         if (cur.top() != from)
640                 setCursor(cur, from.pit(), from.pos());
641         if (to == from)
642                 return;
643         cur.resetAnchor();
644         setCursor(cur, to.pit(), to.pos());
645         cur.setSelection();
646 }
647
648
649 // Select the word currently under the cursor when no
650 // selection is currently set
651 bool Text::selectWordWhenUnderCursor(Cursor & cur, word_location loc)
652 {
653         BOOST_ASSERT(this == cur.text());
654         if (cur.selection())
655                 return false;
656         selectWord(cur, loc);
657         return cur.selection();
658 }
659
660
661 void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
662 {
663         BOOST_ASSERT(this == cur.text());
664
665         if (!cur.selection())
666                 return;
667
668         cur.recordUndoSelection();
669
670         pit_type begPit = cur.selectionBegin().pit();
671         pit_type endPit = cur.selectionEnd().pit();
672
673         pos_type begPos = cur.selectionBegin().pos();
674         pos_type endPos = cur.selectionEnd().pos();
675
676         // keep selection info, because endPos becomes invalid after the first loop
677         bool endsBeforeEndOfPar = (endPos < pars_[endPit].size());
678
679         // first, accept/reject changes within each individual paragraph (do not consider end-of-par)
680
681         for (pit_type pit = begPit; pit <= endPit; ++pit) {
682                 pos_type parSize = pars_[pit].size();
683
684                 // ignore empty paragraphs; otherwise, an assertion will fail for
685                 // acceptChanges(bparams, 0, 0) or rejectChanges(bparams, 0, 0)
686                 if (parSize == 0)
687                         continue;
688
689                 // do not consider first paragraph if the cursor starts at pos size()
690                 if (pit == begPit && begPos == parSize)
691                         continue;
692
693                 // do not consider last paragraph if the cursor ends at pos 0
694                 if (pit == endPit && endPos == 0)
695                         break; // last iteration anyway
696
697                 pos_type left  = (pit == begPit ? begPos : 0);
698                 pos_type right = (pit == endPit ? endPos : parSize);
699
700                 if (op == ACCEPT) {
701                         pars_[pit].acceptChanges(cur.buffer().params(), left, right);
702                 } else {
703                         pars_[pit].rejectChanges(cur.buffer().params(), left, right);
704                 }
705         }
706
707         // next, accept/reject imaginary end-of-par characters
708
709         for (pit_type pit = begPit; pit <= endPit; ++pit) {
710                 pos_type pos = pars_[pit].size();
711
712                 // skip if the selection ends before the end-of-par
713                 if (pit == endPit && endsBeforeEndOfPar)
714                         break; // last iteration anyway
715
716                 // skip if this is not the last paragraph of the document
717                 // note: the user should be able to accept/reject the par break of the last par!
718                 if (pit == endPit && pit + 1 != int(pars_.size()))
719                         break; // last iteration anway
720
721                 if (op == ACCEPT) {
722                         if (pars_[pit].isInserted(pos)) {
723                                 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
724                         } else if (pars_[pit].isDeleted(pos)) {
725                                 if (pit + 1 == int(pars_.size())) {
726                                         // we cannot remove a par break at the end of the last paragraph;
727                                         // instead, we mark it unchanged
728                                         pars_[pit].setChange(pos, Change(Change::UNCHANGED));
729                                 } else {
730                                         mergeParagraph(cur.buffer().params(), pars_, pit);
731                                         --endPit;
732                                         --pit;
733                                 }
734                         }
735                 } else {
736                         if (pars_[pit].isDeleted(pos)) {
737                                 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
738                         } else if (pars_[pit].isInserted(pos)) {
739                                 if (pit + 1 == int(pars_.size())) {
740                                         // we mark the par break at the end of the last paragraph unchanged
741                                         pars_[pit].setChange(pos, Change(Change::UNCHANGED));
742                                 } else {
743                                         mergeParagraph(cur.buffer().params(), pars_, pit);
744                                         --endPit;
745                                         --pit;
746                                 }
747                         }
748                 }
749         }
750
751         // finally, invoke the DEPM
752
753         deleteEmptyParagraphMechanism(begPit, endPit, cur.buffer().params().trackChanges);
754
755         //
756
757         cur.finishUndo();
758         cur.clearSelection();
759         setCursorIntern(cur, begPit, begPos);
760         cur.updateFlags(Update::Force);
761         updateLabels(cur.buffer());
762 }
763
764
765 void Text::acceptChanges(BufferParams const & bparams)
766 {
767         lyx::acceptChanges(pars_, bparams);
768         deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.trackChanges);
769 }
770
771
772 void Text::rejectChanges(BufferParams const & bparams)
773 {
774         pit_type pars_size = static_cast<pit_type>(pars_.size());
775
776         // first, reject changes within each individual paragraph
777         // (do not consider end-of-par)
778         for (pit_type pit = 0; pit < pars_size; ++pit) {
779                 if (!pars_[pit].empty())   // prevent assertion failure
780                         pars_[pit].rejectChanges(bparams, 0, pars_[pit].size());
781         }
782
783         // next, reject imaginary end-of-par characters
784         for (pit_type pit = 0; pit < pars_size; ++pit) {
785                 pos_type pos = pars_[pit].size();
786
787                 if (pars_[pit].isDeleted(pos)) {
788                         pars_[pit].setChange(pos, Change(Change::UNCHANGED));
789                 } else if (pars_[pit].isInserted(pos)) {
790                         if (pit == pars_size - 1) {
791                                 // we mark the par break at the end of the last
792                                 // paragraph unchanged
793                                 pars_[pit].setChange(pos, Change(Change::UNCHANGED));
794                         } else {
795                                 mergeParagraph(bparams, pars_, pit);
796                                 --pit;
797                                 --pars_size;
798                         }
799                 }
800         }
801
802         // finally, invoke the DEPM
803         deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.trackChanges);
804 }
805
806
807 void Text::deleteWordForward(Cursor & cur)
808 {
809         BOOST_ASSERT(this == cur.text());
810         if (cur.lastpos() == 0)
811                 cursorForward(cur);
812         else {
813                 cur.resetAnchor();
814                 cur.selection() = true;
815                 cursorForwardOneWord(cur);
816                 cur.setSelection();
817                 cutSelection(cur, true, false);
818                 cur.checkBufferStructure();
819         }
820 }
821
822
823 void Text::deleteWordBackward(Cursor & cur)
824 {
825         BOOST_ASSERT(this == cur.text());
826         if (cur.lastpos() == 0)
827                 cursorBackward(cur);
828         else {
829                 cur.resetAnchor();
830                 cur.selection() = true;
831                 cursorBackwardOneWord(cur);
832                 cur.setSelection();
833                 cutSelection(cur, true, false);
834                 cur.checkBufferStructure();
835         }
836 }
837
838
839 // Kill to end of line.
840 void Text::changeCase(Cursor & cur, TextCase action)
841 {
842         BOOST_ASSERT(this == cur.text());
843         CursorSlice from;
844         CursorSlice to;
845
846         bool gotsel = false;
847         if (cur.selection()) {
848                 from = cur.selBegin();
849                 to = cur.selEnd();
850                 gotsel = true;
851         } else {
852                 from = cur.top();
853                 getWord(from, to, PARTIAL_WORD);
854                 cursorForwardOneWord(cur);
855         }
856
857         cur.recordUndoSelection();
858
859         pit_type begPit = from.pit();
860         pit_type endPit = to.pit();
861
862         pos_type begPos = from.pos();
863         pos_type endPos = to.pos();
864
865         pos_type right = 0; // needed after the for loop
866
867         for (pit_type pit = begPit; pit <= endPit; ++pit) {
868                 Paragraph & par = pars_[pit];
869                 pos_type const pos = (pit == begPit ? begPos : 0);
870                 right = (pit == endPit ? endPos : par.size());
871                 par.changeCase(cur.buffer().params(), pos, right, action);
872         }
873
874         // the selection may have changed due to logically-only deleted chars
875         if (gotsel) {
876                 setCursor(cur, begPit, begPos);
877                 cur.resetAnchor();
878                 setCursor(cur, endPit, right);
879                 cur.setSelection();
880         } else
881                 setCursor(cur, endPit, right);
882
883         cur.checkBufferStructure();
884 }
885
886
887 bool Text::handleBibitems(Cursor & cur)
888 {
889         if (cur.paragraph().layout().labeltype != LABEL_BIBLIO)
890                 return false;
891
892         if (cur.pos() != 0)
893                 return false;
894
895         BufferParams const & bufparams = cur.buffer().params();
896         Paragraph const & par = cur.paragraph();
897         Cursor prevcur = cur;
898         if (cur.pit() > 0) {
899                 --prevcur.pit();
900                 prevcur.pos() = prevcur.lastpos();
901         }
902         Paragraph const & prevpar = prevcur.paragraph();
903
904         // if a bibitem is deleted, merge with previous paragraph
905         // if this is a bibliography item as well
906         if (cur.pit() > 0 && par.layout() == prevpar.layout()) {
907                 cur.recordUndo(ATOMIC_UNDO, prevcur.pit());
908                 mergeParagraph(bufparams, cur.text()->paragraphs(),
909                                                         prevcur.pit());
910                 updateLabels(cur.buffer());
911                 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
912                 cur.updateFlags(Update::Force);
913                 return true;
914         } 
915
916         // otherwise reset to default
917         cur.paragraph().setEmptyOrDefaultLayout(bufparams.documentClass());
918         return true;
919 }
920
921
922 bool Text::erase(Cursor & cur)
923 {
924         BOOST_ASSERT(this == cur.text());
925         bool needsUpdate = false;
926         Paragraph & par = cur.paragraph();
927
928         if (cur.pos() != cur.lastpos()) {
929                 // this is the code for a normal delete, not pasting
930                 // any paragraphs
931                 cur.recordUndo(DELETE_UNDO);
932                 bool const was_inset = cur.paragraph().isInset(cur.pos());
933                 if(!par.eraseChar(cur.pos(), cur.buffer().params().trackChanges))
934                         // the character has been logically deleted only => skip it
935                         cur.top().forwardPos();
936
937                 if (was_inset)
938                         updateLabels(cur.buffer());
939                 else
940                         cur.checkBufferStructure();
941                 needsUpdate = true;
942         } else {
943                 if (cur.pit() == cur.lastpit())
944                         return dissolveInset(cur);
945
946                 if (!par.isMergedOnEndOfParDeletion(cur.buffer().params().trackChanges)) {
947                         par.setChange(cur.pos(), Change(Change::DELETED));
948                         cur.forwardPos();
949                         needsUpdate = true;
950                 } else {
951                         setCursorIntern(cur, cur.pit() + 1, 0);
952                         needsUpdate = backspacePos0(cur);
953                 }
954         }
955
956         needsUpdate |= handleBibitems(cur);
957
958         if (needsUpdate) {
959                 // Make sure the cursor is correct. Is this really needed?
960                 // No, not really... at least not here!
961                 cur.text()->setCursor(cur.top(), cur.pit(), cur.pos());
962                 cur.checkBufferStructure();
963         }
964
965         return needsUpdate;
966 }
967
968
969 bool Text::backspacePos0(Cursor & cur)
970 {
971         BOOST_ASSERT(this == cur.text());
972         if (cur.pit() == 0)
973                 return false;
974
975         bool needsUpdate = false;
976
977         BufferParams const & bufparams = cur.buffer().params();
978         DocumentClass const & tclass = bufparams.documentClass();
979         ParagraphList & plist = cur.text()->paragraphs();
980         Paragraph const & par = cur.paragraph();
981         Cursor prevcur = cur;
982         --prevcur.pit();
983         prevcur.pos() = prevcur.lastpos();
984         Paragraph const & prevpar = prevcur.paragraph();
985
986         // is it an empty paragraph?
987         if (cur.lastpos() == 0
988             || (cur.lastpos() == 1 && par.isSeparator(0))) {
989                 cur.recordUndo(ATOMIC_UNDO, prevcur.pit(), cur.pit());
990                 plist.erase(boost::next(plist.begin(), cur.pit()));
991                 needsUpdate = true;
992         }
993         // is previous par empty?
994         else if (prevcur.lastpos() == 0
995                  || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) {
996                 cur.recordUndo(ATOMIC_UNDO, prevcur.pit(), cur.pit());
997                 plist.erase(boost::next(plist.begin(), prevcur.pit()));
998                 needsUpdate = true;
999         }
1000         // Pasting is not allowed, if the paragraphs have different
1001         // layouts. I think it is a real bug of all other
1002         // word processors to allow it. It confuses the user.
1003         // Correction: Pasting is always allowed with standard-layout
1004         // or the empty layout.
1005         else if (par.layout() == prevpar.layout()
1006                  || tclass.isDefaultLayout(par.layout())
1007                  || tclass.isEmptyLayout(par.layout())) {
1008                 cur.recordUndo(ATOMIC_UNDO, prevcur.pit());
1009                 mergeParagraph(bufparams, plist, prevcur.pit());
1010                 needsUpdate = true;
1011         }
1012
1013         if (needsUpdate) {
1014                 updateLabels(cur.buffer());
1015                 setCursorIntern(cur, prevcur.pit(), prevcur.pos());
1016         }
1017
1018         return needsUpdate;
1019 }
1020
1021
1022 bool Text::backspace(Cursor & cur)
1023 {
1024         BOOST_ASSERT(this == cur.text());
1025         bool needsUpdate = false;
1026         if (cur.pos() == 0) {
1027                 if (cur.pit() == 0)
1028                         return dissolveInset(cur);
1029
1030                 Paragraph & prev_par = pars_[cur.pit() - 1];
1031
1032                 if (!prev_par.isMergedOnEndOfParDeletion(cur.buffer().params().trackChanges)) {
1033                         prev_par.setChange(prev_par.size(), Change(Change::DELETED));
1034                         setCursorIntern(cur, cur.pit() - 1, prev_par.size());
1035                         return true;
1036                 }
1037                 // The cursor is at the beginning of a paragraph, so
1038                 // the backspace will collapse two paragraphs into one.
1039                 needsUpdate = backspacePos0(cur);
1040
1041         } else {
1042                 // this is the code for a normal backspace, not pasting
1043                 // any paragraphs
1044                 cur.recordUndo(DELETE_UNDO);
1045                 // We used to do cursorBackwardIntern() here, but it is
1046                 // not a good idea since it triggers the auto-delete
1047                 // mechanism. So we do a cursorBackwardIntern()-lite,
1048                 // without the dreaded mechanism. (JMarc)
1049                 setCursorIntern(cur, cur.pit(), cur.pos() - 1,
1050                                 false, cur.boundary());
1051                 bool const was_inset = cur.paragraph().isInset(cur.pos());
1052                 cur.paragraph().eraseChar(cur.pos(), cur.buffer().params().trackChanges);
1053                 if (was_inset)
1054                         updateLabels(cur.buffer());
1055                 else
1056                         cur.checkBufferStructure();
1057         }
1058
1059         if (cur.pos() == cur.lastpos())
1060                 cur.setCurrentFont();
1061
1062         needsUpdate |= handleBibitems(cur);
1063
1064         // A singlePar update is not enough in this case.
1065 //              cur.updateFlags(Update::Force);
1066         setCursor(cur.top(), cur.pit(), cur.pos());
1067
1068         return needsUpdate;
1069 }
1070
1071
1072 bool Text::dissolveInset(Cursor & cur) {
1073         BOOST_ASSERT(this == cur.text());
1074
1075         if (isMainText(cur.bv().buffer()) || cur.inset().nargs() != 1)
1076                 return false;
1077
1078         cur.recordUndoInset();
1079         cur.mark() = false;
1080         cur.selHandle(false);
1081         // save position
1082         pos_type spos = cur.pos();
1083         pit_type spit = cur.pit();
1084         ParagraphList plist;
1085         if (cur.lastpit() != 0 || cur.lastpos() != 0)
1086                 plist = paragraphs();
1087         cur.popBackward();
1088         // store cursor offset
1089         if (spit == 0)
1090                 spos += cur.pos();
1091         spit += cur.pit();
1092         Buffer & b = cur.buffer();
1093         cur.paragraph().eraseChar(cur.pos(), b.params().trackChanges);
1094         if (!plist.empty()) {
1095                 // ERT paragraphs have the Language latex_language.
1096                 // This is invalid outside of ERT, so we need to
1097                 // change it to the buffer language.
1098                 ParagraphList::iterator it = plist.begin();
1099                 ParagraphList::iterator it_end = plist.end();
1100                 for (; it != it_end; it++)
1101                         it->changeLanguage(b.params(), latex_language, b.language());
1102
1103                 pasteParagraphList(cur, plist, b.params().documentClassPtr(),
1104                                    b.errorList("Paste"));
1105                 // restore position
1106                 cur.pit() = min(cur.lastpit(), spit);
1107                 cur.pos() = min(cur.lastpos(), spos);
1108         }
1109         cur.clearSelection();
1110         cur.resetAnchor();
1111         return true;
1112 }
1113
1114
1115 void Text::getWord(CursorSlice & from, CursorSlice & to,
1116         word_location const loc) const
1117 {
1118         Paragraph const & from_par = pars_[from.pit()];
1119         switch (loc) {
1120         case WHOLE_WORD_STRICT:
1121                 if (from.pos() == 0 || from.pos() == from_par.size()
1122                     || !from_par.isLetter(from.pos())
1123                     || !from_par.isLetter(from.pos() - 1)) {
1124                         to = from;
1125                         return;
1126                 }
1127                 // no break here, we go to the next
1128
1129         case WHOLE_WORD:
1130                 // If we are already at the beginning of a word, do nothing
1131                 if (!from.pos() || !from_par.isLetter(from.pos() - 1))
1132                         break;
1133                 // no break here, we go to the next
1134
1135         case PREVIOUS_WORD:
1136                 // always move the cursor to the beginning of previous word
1137                 while (from.pos() && from_par.isLetter(from.pos() - 1))
1138                         --from.pos();
1139                 break;
1140         case NEXT_WORD:
1141                 LYXERR0("Text::getWord: NEXT_WORD not implemented yet");
1142                 break;
1143         case PARTIAL_WORD:
1144                 // no need to move the 'from' cursor
1145                 break;
1146         }
1147         to = from;
1148         Paragraph const & to_par = pars_[to.pit()];
1149         while (to.pos() < to_par.size() && to_par.isLetter(to.pos()))
1150                 ++to.pos();
1151 }
1152
1153
1154 void Text::write(Buffer const & buf, ostream & os) const
1155 {
1156         ParagraphList::const_iterator pit = paragraphs().begin();
1157         ParagraphList::const_iterator end = paragraphs().end();
1158         depth_type dth = 0;
1159         for (; pit != end; ++pit)
1160                 pit->write(os, buf.params(), dth);
1161
1162         // Close begin_deeper
1163         for(; dth > 0; --dth)
1164                 os << "\n\\end_deeper";
1165 }
1166
1167
1168 bool Text::read(Buffer const & buf, Lexer & lex, 
1169                 ErrorList & errorList, InsetText * insetPtr)
1170 {
1171         depth_type depth = 0;
1172
1173         while (lex.isOK()) {
1174                 lex.nextToken();
1175                 string const token = lex.getString();
1176
1177                 if (token.empty())
1178                         continue;
1179
1180                 if (token == "\\end_inset")
1181                         break;
1182
1183                 if (token == "\\end_body")
1184                         continue;
1185
1186                 if (token == "\\begin_body")
1187                         continue;
1188
1189                 if (token == "\\end_document")
1190                         return false;
1191
1192                 if (token == "\\begin_layout") {
1193                         lex.pushToken(token);
1194
1195                         Paragraph par;
1196                         par.params().depth(depth);
1197                         par.setFont(0, Font(inherit_font, buf.params().language));
1198                         par.setInsetOwner(insetPtr);
1199                         pars_.push_back(par);
1200
1201                         // FIXME: goddamn InsetTabular makes us pass a Buffer
1202                         // not BufferParams
1203                         lyx::readParagraph(buf, pars_.back(), lex, errorList);
1204
1205                         // register the words in the global word list
1206                         CursorSlice sl = CursorSlice(*insetPtr);
1207                         sl.pit() = pars_.size() - 1;
1208                         pars_.back().updateWords(sl);
1209                 } else if (token == "\\begin_deeper") {
1210                         ++depth;
1211                 } else if (token == "\\end_deeper") {
1212                         if (!depth)
1213                                 lex.printError("\\end_deeper: " "depth is already null");
1214                         else
1215                                 --depth;
1216                 } else {
1217                         LYXERR0("Handling unknown body token: `" << token << '\'');
1218                 }
1219         }
1220         return true;
1221 }
1222
1223 // Returns the current font and depth as a message.
1224 docstring Text::currentState(Cursor & cur)
1225 {
1226         BOOST_ASSERT(this == cur.text());
1227         Buffer & buf = cur.buffer();
1228         Paragraph const & par = cur.paragraph();
1229         odocstringstream os;
1230
1231         if (buf.params().trackChanges)
1232                 os << _("[Change Tracking] ");
1233
1234         Change change = par.lookupChange(cur.pos());
1235
1236         if (change.type != Change::UNCHANGED) {
1237                 Author const & a = buf.params().authors().get(change.author);
1238                 os << _("Change: ") << a.name();
1239                 if (!a.email().empty())
1240                         os << " (" << a.email() << ")";
1241                 // FIXME ctime is english, we should translate that
1242                 os << _(" at ") << ctime(&change.changetime);
1243                 os << " : ";
1244         }
1245
1246         // I think we should only show changes from the default
1247         // font. (Asger)
1248         // No, from the document font (MV)
1249         Font font = cur.real_current_font;
1250         font.fontInfo().reduce(buf.params().getFont().fontInfo());
1251
1252         os << bformat(_("Font: %1$s"), font.stateText(&buf.params()));
1253
1254         // The paragraph depth
1255         int depth = cur.paragraph().getDepth();
1256         if (depth > 0)
1257                 os << bformat(_(", Depth: %1$d"), depth);
1258
1259         // The paragraph spacing, but only if different from
1260         // buffer spacing.
1261         Spacing const & spacing = par.params().spacing();
1262         if (!spacing.isDefault()) {
1263                 os << _(", Spacing: ");
1264                 switch (spacing.getSpace()) {
1265                 case Spacing::Single:
1266                         os << _("Single");
1267                         break;
1268                 case Spacing::Onehalf:
1269                         os << _("OneHalf");
1270                         break;
1271                 case Spacing::Double:
1272                         os << _("Double");
1273                         break;
1274                 case Spacing::Other:
1275                         os << _("Other (") << from_ascii(spacing.getValueAsString()) << ')';
1276                         break;
1277                 case Spacing::Default:
1278                         // should never happen, do nothing
1279                         break;
1280                 }
1281         }
1282
1283 #ifdef DEVEL_VERSION
1284         os << _(", Inset: ") << &cur.inset();
1285         os << _(", Paragraph: ") << cur.pit();
1286         os << _(", Id: ") << par.id();
1287         os << _(", Position: ") << cur.pos();
1288         // FIXME: Why is the check for par.size() needed?
1289         // We are called with cur.pos() == par.size() quite often.
1290         if (!par.empty() && cur.pos() < par.size()) {
1291                 // Force output of code point, not character
1292                 size_t const c = par.getChar(cur.pos());
1293                 os << _(", Char: 0x") << hex << c;
1294         }
1295         os << _(", Boundary: ") << cur.boundary();
1296 //      Row & row = cur.textRow();
1297 //      os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos());
1298 #endif
1299         return os.str();
1300 }
1301
1302
1303 docstring Text::getPossibleLabel(Cursor & cur) const
1304 {
1305         pit_type pit = cur.pit();
1306
1307         Layout const * layout = &(pars_[pit].layout());
1308
1309         docstring text;
1310         docstring par_text = pars_[pit].asString(false);
1311         string piece;
1312         // the return string of math matrices might contain linebreaks
1313         par_text = subst(par_text, '\n', '-');
1314         for (int i = 0; i < lyxrc.label_init_length; ++i) {
1315                 if (par_text.empty())
1316                         break;
1317                 docstring head;
1318                 par_text = split(par_text, head, ' ');
1319                 // Is it legal to use spaces in labels ?
1320                 if (i > 0)
1321                         text += '-';
1322                 text += head;
1323         }
1324
1325         // No need for a prefix if the user said so.
1326         if (lyxrc.label_init_length <= 0)
1327                 return text;
1328
1329         // Will contain the label type.
1330         docstring name;
1331
1332         // For section, subsection, etc...
1333         if (layout->latextype == LATEX_PARAGRAPH && pit != 0) {
1334                 Layout const * layout2 = &(pars_[pit - 1].layout());
1335                 if (layout2->latextype != LATEX_PARAGRAPH) {
1336                         --pit;
1337                         layout = layout2;
1338                 }
1339         }
1340         if (layout->latextype != LATEX_PARAGRAPH)
1341                 name = from_ascii(layout->latexname());
1342
1343         // for captions, we just take the caption type
1344         Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE);
1345         if (caption_inset)
1346                 name = from_ascii(static_cast<InsetCaption *>(caption_inset)->type());
1347
1348         // If none of the above worked, we'll see if we're inside various
1349         // types of insets and take our abbreviation from them.
1350         if (name.empty()) {
1351                 InsetCode const codes[] = {
1352                         FLOAT_CODE,
1353                         WRAP_CODE,
1354                         FOOT_CODE
1355                 };
1356                 for (unsigned int i = 0; i < (sizeof codes / sizeof codes[0]); ++i) {
1357                         Inset * float_inset = cur.innerInsetOfType(codes[i]);
1358                         if (float_inset) {
1359                                 name = float_inset->name();
1360                                 break;
1361                         }
1362                 }
1363         }
1364
1365         // Create a correct prefix for prettyref
1366         if (name == "theorem")
1367                 name = from_ascii("thm");
1368         else if (name == "Foot")
1369                 name = from_ascii("fn");
1370         else if (name == "listing")
1371                 name = from_ascii("lst");
1372
1373         if (!name.empty())
1374                 text = name.substr(0, 3) + ':' + text;
1375
1376         return text;
1377 }
1378
1379
1380 void Text::charsTranspose(Cursor & cur)
1381 {
1382         BOOST_ASSERT(this == cur.text());
1383
1384         pos_type pos = cur.pos();
1385
1386         // If cursor is at beginning or end of paragraph, do nothing.
1387         if (pos == cur.lastpos() || pos == 0)
1388                 return;
1389
1390         Paragraph & par = cur.paragraph();
1391
1392         // Get the positions of the characters to be transposed.
1393         pos_type pos1 = pos - 1;
1394         pos_type pos2 = pos;
1395
1396         // In change tracking mode, ignore deleted characters.
1397         while (pos2 < cur.lastpos() && par.isDeleted(pos2))
1398                 ++pos2;
1399         if (pos2 == cur.lastpos())
1400                 return;
1401
1402         while (pos1 >= 0 && par.isDeleted(pos1))
1403                 --pos1;
1404         if (pos1 < 0)
1405                 return;
1406
1407         // Don't do anything if one of the "characters" is not regular text.
1408         if (par.isInset(pos1) || par.isInset(pos2))
1409                 return;
1410
1411         // Store the characters to be transposed (including font information).
1412         char_type char1 = par.getChar(pos1);
1413         Font const font1 =
1414                 par.getFontSettings(cur.buffer().params(), pos1);
1415
1416         char_type char2 = par.getChar(pos2);
1417         Font const font2 =
1418                 par.getFontSettings(cur.buffer().params(), pos2);
1419
1420         // And finally, we are ready to perform the transposition.
1421         // Track the changes if Change Tracking is enabled.
1422         bool const trackChanges = cur.buffer().params().trackChanges;
1423
1424         cur.recordUndo();
1425
1426         par.eraseChar(pos2, trackChanges);
1427         par.eraseChar(pos1, trackChanges);
1428         par.insertChar(pos1, char2, font2, trackChanges);
1429         par.insertChar(pos2, char1, font1, trackChanges);
1430
1431         cur.checkBufferStructure();
1432
1433         // After the transposition, move cursor to after the transposition.
1434         setCursor(cur, cur.pit(), pos2);
1435         cur.forwardPos();
1436 }
1437
1438
1439 DocIterator Text::macrocontextPosition() const
1440 {
1441         return macrocontext_position_;
1442 }
1443
1444
1445 void Text::setMacrocontextPosition(DocIterator const & pos)
1446 {
1447         macrocontext_position_ = pos;
1448 }
1449
1450
1451 docstring Text::previousWord(CursorSlice const & sl) const
1452 {
1453         CursorSlice from = sl;
1454         CursorSlice to = sl;
1455         getWord(from, to, PREVIOUS_WORD);
1456         if (sl == from || to == from)
1457                 return docstring();
1458         
1459         Paragraph const & par = sl.paragraph();
1460         return par.asString(from.pos(), to.pos(), false);
1461 }
1462
1463
1464 bool Text::completionSupported(Cursor const & cur) const
1465 {
1466         Paragraph const & par = cur.paragraph();
1467         return cur.pos() > 0
1468                 && (cur.pos() >= par.size() || !par.isLetter(cur.pos()))
1469                 && par.isLetter(cur.pos() - 1);
1470 }
1471
1472
1473 CompletionList const * Text::createCompletionList(Cursor const & cur) const
1474 {
1475         return new TextCompletionList(cur);
1476 }
1477
1478
1479 bool Text::insertCompletion(Cursor & cur, docstring const & s, bool /*finished*/)
1480 {       
1481         BOOST_ASSERT(cur.bv().cursor() == cur);
1482         cur.insert(s);
1483         cur.bv().cursor() = cur;
1484         if (!(cur.disp_.update() & Update::Force))
1485                 cur.updateFlags(cur.disp_.update() | Update::SinglePar);
1486         return true;
1487 }
1488         
1489         
1490 docstring Text::completionPrefix(Cursor const & cur) const
1491 {
1492         return previousWord(cur.top());
1493 }
1494
1495 } // namespace lyx