]> git.lyx.org Git - lyx.git/blob - src/paragraph_pimpl.C
do not define boost::throw_exceptions if we are compiling with exceptions
[lyx.git] / src / paragraph_pimpl.C
1 /**
2  * \file paragraph_pimpl.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Jean-Marc Lasgouttes
8  * \author John Levon
9  * \author André Pönitz
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "paragraph_pimpl.h"
17
18 #include "bufferparams.h"
19 #include "debug.h"
20 #include "encoding.h"
21 #include "language.h"
22 #include "LaTeXFeatures.h"
23 #include "LColor.h"
24 #include "lyxlength.h"
25 #include "lyxrc.h"
26 #include "outputparams.h"
27 #include "texrow.h"
28
29 #include <boost/next_prior.hpp>
30
31 using lyx::pos_type;
32
33 using std::endl;
34 using std::upper_bound;
35 using std::lower_bound;
36 using std::string;
37 using std::ostream;
38
39
40 // Initialization of the counter for the paragraph id's,
41 unsigned int Paragraph::Pimpl::paragraph_id = 0;
42
43 namespace {
44
45 struct special_phrase {
46         string phrase;
47         string macro;
48         bool builtin;
49 };
50
51 special_phrase special_phrases[] = {
52         { "LyX", "\\LyX{}", false },
53         { "TeX", "\\TeX{}", true },
54         { "LaTeX2e", "\\LaTeXe{}", true },
55         { "LaTeX", "\\LaTeX{}", true },
56 };
57
58 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
59
60 } // namespace anon
61
62
63 Paragraph::Pimpl::Pimpl(Paragraph * owner)
64         : owner_(owner)
65 {
66         inset_owner = 0;
67         id_ = paragraph_id++;
68 }
69
70
71 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
72         : params(p.params), owner_(owner)
73 {
74         inset_owner = p.inset_owner;
75         fontlist = p.fontlist;
76         id_ = paragraph_id++;
77
78         if (p.tracking())
79                 changes_.reset(new Changes(*p.changes_.get()));
80 }
81
82
83 void Paragraph::Pimpl::setContentsFromPar(Paragraph const & par)
84 {
85         owner_->text_ = par.text_;
86         if (par.pimpl_->tracking()) {
87                 changes_.reset(new Changes(*(par.pimpl_->changes_.get())));
88         }
89 }
90
91
92 void Paragraph::Pimpl::trackChanges(Change::Type type)
93 {
94         if (tracking()) {
95                 lyxerr[Debug::CHANGES] << "already tracking for par " << id_ << endl;
96                 return;
97         }
98
99         lyxerr[Debug::CHANGES] << "track changes for par "
100                 << id_ << " type " << type << endl;
101         changes_.reset(new Changes(type));
102         changes_->set(type, 0, size() + 1);
103 }
104
105
106 void Paragraph::Pimpl::untrackChanges()
107 {
108         changes_.reset(0);
109 }
110
111
112 void Paragraph::Pimpl::cleanChanges()
113 {
114         // if we're not tracking, we don't want to reset...
115         if (!tracking())
116                 return;
117
118         changes_.reset(new Changes(Change::INSERTED));
119         changes_->set(Change::INSERTED, 0, size() + 1);
120 }
121
122
123 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
124 {
125         if (!tracking())
126                 return false;
127
128         return changes_->isChange(start, end);
129 }
130
131
132 bool Paragraph::Pimpl::isChangeEdited(pos_type start, pos_type end) const
133 {
134         if (!tracking())
135                 return false;
136
137         return changes_->isChangeEdited(start, end);
138 }
139
140
141 void Paragraph::Pimpl::setChange(pos_type pos, Change::Type type)
142 {
143         if (!tracking())
144                 return;
145
146         changes_->set(type, pos);
147 }
148
149
150 void Paragraph::Pimpl::setChangeFull(pos_type pos, Change change)
151 {
152         if (!tracking())
153                 return;
154
155         changes_->set(change, pos);
156 }
157
158 Change::Type Paragraph::Pimpl::lookupChange(pos_type pos) const
159 {
160         if (!tracking())
161                 return Change::UNCHANGED;
162
163         return changes_->lookup(pos);
164 }
165
166
167 Change const Paragraph::Pimpl::lookupChangeFull(pos_type pos) const
168 {
169         if (!tracking())
170                 return Change(Change::UNCHANGED);
171
172         return changes_->lookupFull(pos);
173 }
174
175
176 void Paragraph::Pimpl::markErased(bool erased)
177 {
178         BOOST_ASSERT(tracking());
179
180         if (erased) {
181                 erase(0, size());
182         } else {
183                 for (pos_type i = 0; i < size(); ++i) {
184                         changes_->set(Change::UNCHANGED, i);
185                         if (owner_->isInset(i))
186                                 owner_->getInset(i)->markErased(false);
187                 }
188         }
189 }
190
191
192 void Paragraph::Pimpl::acceptChange(pos_type start, pos_type end)
193 {
194         if (!tracking())
195                 return;
196
197         if (!size()) {
198                 changes_.reset(new Changes(Change::UNCHANGED));
199                 return;
200         }
201
202         lyxerr[Debug::CHANGES] << "acceptchange" << endl;
203         pos_type i = start;
204
205         for (; i < end; ++i) {
206                 switch (lookupChange(i)) {
207                         case Change::UNCHANGED:
208                                 break;
209
210                         case Change::INSERTED:
211                                 changes_->set(Change::UNCHANGED, i);
212                                 break;
213
214                         case Change::DELETED:
215                                 // Suppress access to nonexistent 
216                                 // "end-of-paragraph char":
217                                 if (i < size()) {
218                                         eraseIntern(i);
219                                         changes_->erase(i);
220                                         --end;
221                                         --i;
222                                 }
223                                 break;
224                 }
225         }
226
227         lyxerr[Debug::CHANGES] << "endacceptchange" << endl;
228         changes_->reset(Change::UNCHANGED);
229 }
230
231
232 void Paragraph::Pimpl::rejectChange(pos_type start, pos_type end)
233 {
234         if (!tracking())
235                 return;
236
237         if (!size()) {
238                 changes_.reset(new Changes(Change::UNCHANGED));
239                 return;
240         }
241
242         pos_type i = start;
243
244         for (; i < end; ++i) {
245                 switch (lookupChange(i)) {
246                         case Change::UNCHANGED:
247                                 break;
248
249                         case Change::INSERTED:
250                                 if (i < size()) {
251                                         eraseIntern(i);
252                                         changes_->erase(i);
253                                         --end;
254                                         --i;
255                                 }
256                                 break;
257
258                         case Change::DELETED:
259                                 changes_->set(Change::UNCHANGED, i);
260                                 // No real char at position size():
261                                 if (i < size() && owner_->isInset(i))
262                                         owner_->getInset(i)->markErased(false);
263                                 break;
264                 }
265         }
266         changes_->reset(Change::UNCHANGED);
267 }
268
269
270 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
271 {
272         return owner_->getChar(pos);
273 }
274
275
276 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change change)
277 {
278         BOOST_ASSERT(pos <= size());
279
280         if (tracking()) {
281                 changes_->record(change, pos);
282         }
283
284         // This is actually very common when parsing buffers (and
285         // maybe inserting ascii text)
286         if (pos == size()) {
287                 // when appending characters, no need to update tables
288                 owner_->text_.push_back(c);
289                 return;
290         }
291
292         owner_->text_.insert(owner_->text_.begin() + pos, c);
293
294         // Update the font table.
295         FontTable search_font(pos, LyXFont());
296         for (FontList::iterator it = lower_bound(fontlist.begin(),
297                                                       fontlist.end(),
298                                                       search_font, matchFT());
299              it != fontlist.end(); ++it)
300         {
301                 it->pos(it->pos() + 1);
302         }
303
304         // Update the insets
305         owner_->insetlist.increasePosAfterPos(pos);
306 }
307
308
309 void Paragraph::Pimpl::insertInset(pos_type pos,
310                                    InsetBase * inset, Change change)
311 {
312         BOOST_ASSERT(inset);
313         BOOST_ASSERT(pos <= size());
314
315         insertChar(pos, META_INSET, change);
316         BOOST_ASSERT(owner_->text_[pos] == META_INSET);
317
318         // Add a new entry in the insetlist.
319         owner_->insetlist.insert(inset, pos);
320 }
321
322
323 void Paragraph::Pimpl::eraseIntern(pos_type pos)
324 {
325         // if it is an inset, delete the inset entry
326         if (owner_->text_[pos] == Paragraph::META_INSET) {
327                 owner_->insetlist.erase(pos);
328         }
329
330         owner_->text_.erase(owner_->text_.begin() + pos);
331
332         // Erase entries in the tables.
333         FontTable search_font(pos, LyXFont());
334
335         FontList::iterator it =
336                 lower_bound(fontlist.begin(),
337                             fontlist.end(),
338                             search_font, matchFT());
339         if (it != fontlist.end() && it->pos() == pos &&
340             (pos == 0 ||
341              (it != fontlist.begin()
342               && boost::prior(it)->pos() == pos - 1))) {
343                 // If it is a multi-character font
344                 // entry, we just make it smaller
345                 // (see update below), otherwise we
346                 // should delete it.
347                 unsigned int const i = it - fontlist.begin();
348                 fontlist.erase(fontlist.begin() + i);
349                 it = fontlist.begin() + i;
350                 if (i > 0 && i < fontlist.size() &&
351                     fontlist[i - 1].font() == fontlist[i].font()) {
352                         fontlist.erase(fontlist.begin() + i - 1);
353                         it = fontlist.begin() + i - 1;
354                 }
355         }
356
357         // Update all other entries.
358         FontList::iterator fend = fontlist.end();
359         for (; it != fend; ++it)
360                 it->pos(it->pos() - 1);
361
362         // Update the insetlist.
363         owner_->insetlist.decreasePosAfterPos(pos);
364 }
365
366
367 bool Paragraph::Pimpl::erase(pos_type pos)
368 {
369         BOOST_ASSERT(pos <= size());
370
371         if (tracking()) {
372                 Change::Type changetype(changes_->lookup(pos));
373                 changes_->record(Change(Change::DELETED), pos);
374
375                 // only allow the actual removal if it was /new/ text
376                 if (changetype != Change::INSERTED) {
377                         if (pos < size() && owner_->isInset(pos))
378                                 owner_->getInset(pos)->markErased(true);
379                         return false;
380                 }
381         }
382
383         // Don't physically access nonexistent end-of-paragraph char
384         if (pos < size()) {
385                 eraseIntern(pos);
386                 return true;
387         }
388
389         return false;
390 }
391
392
393 int Paragraph::Pimpl::erase(pos_type start, pos_type end)
394 {
395         pos_type i = start;
396         for (pos_type count = end - start; count; --count) {
397                 if (!erase(i))
398                         ++i;
399         }
400         return end - i;
401 }
402
403
404 void Paragraph::Pimpl::simpleTeXBlanks(ostream & os, TexRow & texrow,
405                                        pos_type const i,
406                                        unsigned int & column,
407                                        LyXFont const & font,
408                                        LyXLayout const & style)
409 {
410         if (style.pass_thru)
411                 return;
412
413         if (column > lyxrc.ascii_linelen
414             && i
415             && getChar(i - 1) != ' '
416             && (i < size() - 1)
417             // same in FreeSpacing mode
418             && !owner_->isFreeSpacing()
419             // In typewriter mode, we want to avoid
420             // ! . ? : at the end of a line
421             && !(font.family() == LyXFont::TYPEWRITER_FAMILY
422                  && (getChar(i - 1) == '.'
423                      || getChar(i - 1) == '?'
424                      || getChar(i - 1) == ':'
425                      || getChar(i - 1) == '!'))) {
426                 os << '\n';
427                 texrow.newline();
428                 texrow.start(owner_->id(), i + 1);
429                 column = 0;
430         } else if (style.free_spacing) {
431                 os << '~';
432         } else {
433                 os << ' ';
434         }
435 }
436
437
438 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
439 {
440         pos_type const len = str.length();
441
442         // is the paragraph large enough?
443         if (pos + len > size())
444                 return false;
445
446         // does the wanted text start at point?
447         for (string::size_type i = 0; i < str.length(); ++i) {
448                 if (str[i] != owner_->text_[pos + i])
449                         return false;
450         }
451
452         // is there a font change in middle of the word?
453         FontList::const_iterator cit = fontlist.begin();
454         FontList::const_iterator end = fontlist.end();
455         for (; cit != end; ++cit) {
456                 if (cit->pos() >= pos)
457                         break;
458         }
459         if (cit != end && pos + len - 1 > cit->pos())
460                 return false;
461
462         return true;
463 }
464
465
466 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
467                                              BufferParams const & bparams,
468                                              ostream & os,
469                                              TexRow & texrow,
470                                              OutputParams const & runparams,
471                                              LyXFont & font,
472                                              LyXFont & running_font,
473                                              LyXFont & basefont,
474                                              LyXFont const & outerfont,
475                                              bool & open_font,
476                                              Change::Type & running_change,
477                                              LyXLayout const & style,
478                                              pos_type & i,
479                                              unsigned int & column,
480                                              value_type const c)
481 {
482         if (style.pass_thru) {
483                 if (c != Paragraph::META_INSET) {
484                         if (c != '\0')
485                                 os << c;
486                 } else {
487                         owner_->getInset(i)->plaintext(buf, os, runparams);
488                 }
489                 return;
490         }
491
492         // Two major modes:  LaTeX or plain
493         // Handle here those cases common to both modes
494         // and then split to handle the two modes separately.
495         switch (c) {
496         case Paragraph::META_INSET: {
497                 InsetBase * inset = owner_->getInset(i);
498
499                 // FIXME: remove this check
500                 if (!inset)
501                         break;
502
503                 // FIXME: move this to InsetNewline::latex
504                 if (inset->lyxCode() == InsetBase::NEWLINE_CODE) {
505                         // newlines are handled differently here than
506                         // the default in simpleTeXSpecialChars().
507                         if (!style.newline_allowed) {
508                                 os << '\n';
509                         } else {
510                                 if (open_font) {
511                                         column += running_font.latexWriteEndChanges(os, basefont, basefont);
512                                         open_font = false;
513                                 }
514                                 basefont = owner_->getLayoutFont(bparams, outerfont);
515                                 running_font = basefont;
516
517                                 if (font.family() == LyXFont::TYPEWRITER_FAMILY)
518                                         os << '~';
519
520                                 if (runparams.moving_arg)
521                                         os << "\\protect ";
522
523                                 os << "\\\\\n";
524                         }
525                         texrow.newline();
526                         texrow.start(owner_->id(), i + 1);
527                         column = 0;
528                         break;
529                 }
530
531                 // output change tracking marks only if desired,
532                 // if dvipost is installed,
533                 // and with dvi/ps (other formats don't work)
534                 LaTeXFeatures features(buf, bparams, runparams);
535                 bool const output = bparams.output_changes
536                         && runparams.flavor == OutputParams::LATEX
537                         && features.isAvailable("dvipost");
538
539                 if (inset->canTrackChanges()) {
540                         column += Changes::latexMarkChange(os, running_change,
541                                 Change::UNCHANGED, output);
542                         running_change = Change::UNCHANGED;
543                 }
544
545                 bool close = false;
546                 ostream::pos_type const len = os.tellp();
547
548                 if ((inset->lyxCode() == InsetBase::GRAPHICS_CODE
549                      || inset->lyxCode() == InsetBase::MATH_CODE
550                      || inset->lyxCode() == InsetBase::URL_CODE)
551                     && running_font.isRightToLeft()) {
552                         os << "\\L{";
553                         close = true;
554                 }
555
556 #ifdef WITH_WARNINGS
557 #warning Bug: we can have an empty font change here!
558 // if there has just been a font change, we are going to close it
559 // right now, which means stupid latex code like \textsf{}. AFAIK,
560 // this does not harm dvi output. A minor bug, thus (JMarc)
561 #endif
562                 // some insets cannot be inside a font change command
563                 if (open_font && inset->noFontChange()) {
564                         column +=running_font.
565                                 latexWriteEndChanges(os,
566                                                      basefont,
567                                                      basefont);
568                         open_font = false;
569                         basefont = owner_->getLayoutFont(bparams, outerfont);
570                         running_font = basefont;
571                 }
572
573                 int tmp = inset->latex(buf, os, runparams);
574
575                 if (close)
576                         os << '}';
577
578                 if (tmp) {
579                         for (int j = 0; j < tmp; ++j) {
580                                 texrow.newline();
581                         }
582                         texrow.start(owner_->id(), i + 1);
583                         column = 0;
584                 } else {
585                         column += os.tellp() - len;
586                 }
587         }
588         break;
589
590         default:
591                 // And now for the special cases within each mode
592
593                 switch (c) {
594                 case '\\':
595                         os << "\\textbackslash{}";
596                         column += 15;
597                         break;
598
599                 case '±': case '²': case '³':
600                 case '×': case '÷': case '¹':
601                 case '¬': case 'µ':
602                         if ((bparams.inputenc == "latin1" ||
603                              bparams.inputenc == "latin9") ||
604                             (bparams.inputenc == "auto" &&
605                              (font.language()->encoding()->LatexName()
606                               == "latin1" ||
607                               font.language()->encoding()->LatexName()
608                               == "latin9"))) {
609                                 os << "\\ensuremath{"
610                                    << c
611                                    << '}';
612                                 column += 13;
613                         } else {
614                                 os << c;
615                         }
616                         break;
617
618                 case '|': case '<': case '>':
619                         // In T1 encoding, these characters exist
620                         if (lyxrc.fontenc == "T1") {
621                                 os << c;
622                                 //... but we should avoid ligatures
623                                 if ((c == '>' || c == '<')
624                                     && i <= size() - 2
625                                     && getChar(i + 1) == c) {
626                                         //os << "\\textcompwordmark{}";
627                                         // Jean-Marc, have a look at
628                                         // this. I think this works
629                                         // equally well:
630                                         os << "\\,{}";
631                                         // Lgb
632                                         column += 19;
633                                 }
634                                 break;
635                         }
636                         // Typewriter font also has them
637                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
638                                 os << c;
639                                 break;
640                         }
641                         // Otherwise, we use what LaTeX
642                         // provides us.
643                         switch (c) {
644                         case '<':
645                                 os << "\\textless{}";
646                                 column += 10;
647                                 break;
648                         case '>':
649                                 os << "\\textgreater{}";
650                                 column += 13;
651                                 break;
652                         case '|':
653                                 os << "\\textbar{}";
654                                 column += 9;
655                                 break;
656                         }
657                         break;
658
659                 case '-': // "--" in Typewriter mode -> "-{}-"
660                         if (i <= size() - 2
661                             && getChar(i + 1) == '-'
662                             && font.family() == LyXFont::TYPEWRITER_FAMILY) {
663                                 os << "-{}";
664                                 column += 2;
665                         } else {
666                                 os << '-';
667                         }
668                         break;
669
670                 case '\"':
671                         os << "\\char`\\\"{}";
672                         column += 9;
673                         break;
674
675                 case '£':
676                         if (bparams.inputenc == "default") {
677                                 os << "\\pounds{}";
678                                 column += 8;
679                         } else {
680                                 os << c;
681                         }
682                         break;
683
684                 case '$': case '&':
685                 case '%': case '#': case '{':
686                 case '}': case '_':
687                         os << '\\' << c;
688                         column += 1;
689                         break;
690
691                 case '~':
692                         os << "\\textasciitilde{}";
693                         column += 16;
694                         break;
695
696                 case '^':
697                         os << "\\textasciicircum{}";
698                         column += 17;
699                         break;
700
701                 case '*': case '[':
702                         // avoid being mistaken for optional arguments
703                         os << '{' << c << '}';
704                         column += 2;
705                         break;
706
707                 case ' ':
708                         // Blanks are printed before font switching.
709                         // Sure? I am not! (try nice-latex)
710                         // I am sure it's correct. LyX might be smarter
711                         // in the future, but for now, nothing wrong is
712                         // written. (Asger)
713                         break;
714
715                 default:
716
717                         // I assume this is hack treating typewriter as verbatim
718                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
719                                 if (c != '\0') {
720                                         os << c;
721                                 }
722                                 break;
723                         }
724
725                         // LyX, LaTeX etc.
726
727                         // FIXME: if we have "LaTeX" with a font
728                         // change in the middle (before the 'T', then
729                         // the "TeX" part is still special cased.
730                         // Really we should only operate this on
731                         // "words" for some definition of word
732
733                         size_t pnr = 0;
734
735                         for (; pnr < phrases_nr; ++pnr) {
736                                 if (isTextAt(special_phrases[pnr].phrase, i)) {
737                                         os << special_phrases[pnr].macro;
738                                         i += special_phrases[pnr].phrase.length() - 1;
739                                         column += special_phrases[pnr].macro.length() - 1;
740                                         break;
741                                 }
742                         }
743
744                         if (pnr == phrases_nr && c != '\0') {
745                                 os << c;
746                         }
747                         break;
748                 }
749         }
750 }
751
752
753 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
754                                 LyXLayout const & layout) const
755 {
756         BufferParams const & bparams = features.bufferParams();
757
758         // check the params.
759         if (!params.spacing().isDefault())
760                 features.require("setspace");
761
762         // then the layouts
763         features.useLayout(layout.name());
764
765         // then the fonts
766         Language const * doc_language = bparams.language;
767
768         FontList::const_iterator fcit = fontlist.begin();
769         FontList::const_iterator fend = fontlist.end();
770         for (; fcit != fend; ++fcit) {
771                 if (fcit->font().noun() == LyXFont::ON) {
772                         lyxerr[Debug::LATEX] << "font.noun: "
773                                              << fcit->font().noun()
774                                              << endl;
775                         features.require("noun");
776                         lyxerr[Debug::LATEX] << "Noun enabled. Font: "
777                                              << fcit->font().stateText(0)
778                                              << endl;
779                 }
780                 switch (fcit->font().color()) {
781                 case LColor::none:
782                 case LColor::inherit:
783                 case LColor::ignore:
784                         // probably we should put here all interface colors used for
785                         // font displaying! For now I just add this ones I know of (Jug)
786                 case LColor::latex:
787                 case LColor::note:
788                         break;
789                 default:
790                         features.require("color");
791                         lyxerr[Debug::LATEX] << "Color enabled. Font: "
792                                              << fcit->font().stateText(0)
793                                              << endl;
794                 }
795
796                 Language const * language = fcit->font().language();
797                 if (language->babel() != doc_language->babel() &&
798                     language != ignore_language &&
799                     language != latex_language)
800                 {
801                         features.useLanguage(language);
802                         lyxerr[Debug::LATEX] << "Found language "
803                                              << language->babel() << endl;
804                 }
805         }
806
807         if (!params.leftIndent().zero())
808                 features.require("ParagraphLeftIndent");
809
810         // then the insets
811         InsetList::const_iterator icit = owner_->insetlist.begin();
812         InsetList::const_iterator iend = owner_->insetlist.end();
813         for (; icit != iend; ++icit) {
814                 if (icit->inset) {
815                         icit->inset->validate(features);
816                         if (layout.needprotect &&
817                             icit->inset->lyxCode() == InsetBase::FOOT_CODE)
818                                 features.require("NeedLyXFootnoteCode");
819                 }
820         }
821
822         // then the contents
823         for (pos_type i = 0; i < size() ; ++i) {
824                 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
825                         if (!special_phrases[pnr].builtin
826                             && isTextAt(special_phrases[pnr].phrase, i)) {
827                                 features.require(special_phrases[pnr].phrase);
828                                 break;
829                         }
830                 }
831         }
832 }