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