]> git.lyx.org Git - lyx.git/blob - src/paragraph_funcs.C
more cursor dispatch
[lyx.git] / src / paragraph_funcs.C
1 /**
2  * \file paragraph_funcs.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  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "paragraph_funcs.h"
14
15 #include "buffer.h"
16 #include "bufferparams.h"
17
18 #include "debug.h"
19 #include "encoding.h"
20 #include "errorlist.h"
21 #include "factory.h"
22 #include "gettext.h"
23 #include "iterators.h"
24 #include "language.h"
25 #include "lyxlex.h"
26 #include "lyxrc.h"
27 #include "outputparams.h"
28 #include "paragraph_pimpl.h"
29 #include "sgml.h"
30 #include "texrow.h"
31 #include "vspace.h"
32
33 #include "insets/insetbibitem.h"
34 #include "insets/insethfill.h"
35 #include "insets/insetlatexaccent.h"
36 #include "insets/insetline.h"
37 #include "insets/insetnewline.h"
38 #include "insets/insetpagebreak.h"
39 #include "insets/insetoptarg.h"
40 #include "insets/insetspace.h"
41 #include "insets/insetspecialchar.h"
42 #include "insets/insettabular.h"
43
44 #include "support/filetools.h"
45 #include "support/lstrings.h"
46 #include "support/lyxlib.h"
47 #include "support/std_sstream.h"
48
49 #include <vector>
50
51 using lyx::pos_type;
52
53 using lyx::support::ascii_lowercase;
54 using lyx::support::atoi;
55 using lyx::support::bformat;
56 using lyx::support::compare_ascii_no_case;
57 using lyx::support::compare_no_case;
58 using lyx::support::contains;
59 using lyx::support::split;
60 using lyx::support::subst;
61
62 using std::auto_ptr;
63 using std::endl;
64 using std::string;
65 using std::vector;
66 using std::istringstream;
67 using std::ostream;
68 using std::pair;
69
70
71 namespace {
72
73 bool moveItem(Paragraph & from, Paragraph & to,
74         BufferParams const & params, pos_type i, pos_type j)
75 {
76         char const tmpchar = from.getChar(i);
77         LyXFont tmpfont = from.getFontSettings(params, i);
78
79         if (tmpchar == Paragraph::META_INSET) {
80                 InsetBase * tmpinset = 0;
81                 if (from.getInset(i)) {
82                         // the inset is not in a paragraph anymore
83                         tmpinset = from.insetlist.release(i);
84                 }
85
86                 if (!to.insetAllowed(tmpinset->lyxCode()))
87                         return false;
88                 to.insertInset(j, tmpinset, tmpfont);
89         } else {
90                 if (!to.checkInsertChar(tmpfont))
91                         return false;
92                 to.insertChar(j, tmpchar, tmpfont);
93         }
94         return true;
95 }
96
97 }
98
99
100 void breakParagraph(BufferParams const & bparams,
101                     ParagraphList & paragraphs,
102                     ParagraphList::iterator par,
103                     pos_type pos,
104                     int flag)
105 {
106         // create a new paragraph, and insert into the list
107         ParagraphList::iterator tmp = paragraphs.insert(boost::next(par),
108                                                         Paragraph());
109
110         // without doing that we get a crash when typing <Return> at the
111         // end of a paragraph
112         tmp->layout(bparams.getLyXTextClass().defaultLayout());
113         // remember to set the inset_owner
114         tmp->setInsetOwner(par->inInset());
115
116         if (bparams.tracking_changes)
117                 tmp->trackChanges();
118
119         // this is an idea for a more userfriendly layout handling, I will
120         // see what the users say
121
122         // layout stays the same with latex-environments
123         if (flag) {
124                 tmp->layout(par->layout());
125                 tmp->setLabelWidthString(par->params().labelWidthString());
126         }
127
128         bool const isempty = (par->allowEmpty() && par->empty());
129
130         if (!isempty && (par->size() > pos || par->empty() || flag == 2)) {
131                 tmp->layout(par->layout());
132                 tmp->params().align(par->params().align());
133                 tmp->setLabelWidthString(par->params().labelWidthString());
134
135                 tmp->params().depth(par->params().depth());
136                 tmp->params().noindent(par->params().noindent());
137
138                 // copy everything behind the break-position
139                 // to the new paragraph
140
141 #ifdef WITH_WARNINGS
142 #warning this seems wrong
143 #endif
144                 /* FIXME: if !keepempty, empty() == true, then we reach
145                  * here with size() == 0. So pos_end becomes - 1. Why
146                  * doesn't this cause problems ???
147                  */
148                 pos_type pos_end = par->size() - 1;
149                 pos_type i = pos;
150                 pos_type j = pos;
151
152                 for (; i <= pos_end; ++i) {
153                         Change::Type change = par->lookupChange(i);
154                         if (moveItem(*par, *tmp, bparams, i, j - pos)) {
155                                 tmp->setChange(j - pos, change);
156                                 ++j;
157                         }
158                 }
159
160                 for (i = pos_end; i >= pos; --i)
161                         par->eraseIntern(i);
162         }
163
164         if (pos)
165                 return;
166
167         par->params().clear();
168
169         par->layout(bparams.getLyXTextClass().defaultLayout());
170
171         // layout stays the same with latex-environments
172         if (flag) {
173                 par->layout(tmp->layout());
174                 par->setLabelWidthString(tmp->params().labelWidthString());
175                 par->params().depth(tmp->params().depth());
176         }
177
178         // subtle, but needed to get empty pars working right
179         if (bparams.tracking_changes) {
180                 if (!par->size()) {
181                         par->cleanChanges();
182                 } else if (!tmp->size()) {
183                         tmp->cleanChanges();
184                 }
185         }
186 }
187
188
189 void breakParagraphConservative(BufferParams const & bparams,
190                                 ParagraphList & paragraphs,
191                                 ParagraphList::iterator par,
192                                 pos_type pos)
193 {
194         // create a new paragraph
195         ParagraphList::iterator tmp = paragraphs.insert(boost::next(par),
196                                                         Paragraph());
197         tmp->makeSameLayout(*par);
198
199         // When can pos > size()?
200         // I guess pos == size() is possible.
201         if (par->size() > pos) {
202                 // copy everything behind the break-position to the new
203                 // paragraph
204                 pos_type pos_end = par->size() - 1;
205
206                 for (pos_type i = pos, j = pos; i <= pos_end; ++i)
207                         if (moveItem(*par, *tmp, bparams, i, j - pos))
208                                 ++j;
209
210                 for (pos_type k = pos_end; k >= pos; --k)
211                         par->erase(k);
212         }
213 }
214
215
216 void mergeParagraph(BufferParams const & bparams,
217                     ParagraphList & paragraphs,
218                     ParagraphList::iterator par)
219 {
220         ParagraphList::iterator the_next = boost::next(par);
221
222         pos_type pos_end = the_next->size() - 1;
223         pos_type pos_insert = par->size();
224
225         // ok, now copy the paragraph
226         for (pos_type i = 0, j = 0; i <= pos_end; ++i)
227                 if (moveItem(*the_next, *par, bparams, i, pos_insert + j))
228                         ++j;
229
230         paragraphs.erase(the_next);
231 }
232
233
234 ParagraphList::iterator depthHook(ParagraphList::iterator pit,
235                                   ParagraphList const & plist,
236                                   Paragraph::depth_type depth)
237 {
238         ParagraphList::iterator newpit = pit;
239         ParagraphList::iterator beg = const_cast<ParagraphList&>(plist).begin();
240
241         if (newpit != beg)
242                 --newpit;
243
244         while (newpit != beg && newpit->getDepth() > depth) {
245                 --newpit;
246         }
247
248         if (newpit->getDepth() > depth)
249                 return pit;
250
251         return newpit;
252 }
253
254
255 ParagraphList::iterator outerHook(ParagraphList::iterator pit,
256                                   ParagraphList const & plist)
257 {
258         if (!pit->getDepth())
259                 return const_cast<ParagraphList&>(plist).end();
260         return depthHook(pit, plist,
261                          Paragraph::depth_type(pit->getDepth() - 1));
262 }
263
264
265 bool isFirstInSequence(ParagraphList::iterator pit,
266                        ParagraphList const & plist)
267 {
268         ParagraphList::iterator dhook = depthHook(pit, plist, pit->getDepth());
269         return (dhook == pit
270                 || dhook->layout() != pit->layout()
271                 || dhook->getDepth() != pit->getDepth());
272 }
273
274
275 int getEndLabel(ParagraphList::iterator p, ParagraphList const & plist)
276 {
277         ParagraphList::iterator pit = p;
278         Paragraph::depth_type par_depth = p->getDepth();
279         while (pit != const_cast<ParagraphList&>(plist).end()) {
280                 LyXLayout_ptr const & layout = pit->layout();
281                 int const endlabeltype = layout->endlabeltype;
282
283                 if (endlabeltype != END_LABEL_NO_LABEL) {
284                         if (boost::next(p) == const_cast<ParagraphList&>(plist).end())
285                                 return endlabeltype;
286
287                         Paragraph::depth_type const next_depth = boost::next(p)->getDepth();
288                         if (par_depth > next_depth ||
289                             (par_depth == next_depth &&
290                              layout != boost::next(p)->layout()))
291                                 return endlabeltype;
292                         break;
293                 }
294                 if (par_depth == 0)
295                         break;
296                 pit = outerHook(pit, plist);
297                 if (pit != const_cast<ParagraphList&>(plist).end())
298                         par_depth = pit->getDepth();
299         }
300         return END_LABEL_NO_LABEL;
301 }
302
303
304 namespace {
305
306 int readParToken(Buffer const & buf, Paragraph & par, LyXLex & lex,
307         string const & token)
308 {
309         static LyXFont font;
310         static Change change;
311
312         BufferParams const & bp = buf.params();
313
314         if (token[0] != '\\') {
315                 string::const_iterator cit = token.begin();
316                 for (; cit != token.end(); ++cit) {
317                         par.insertChar(par.size(), (*cit), font, change);
318                 }
319         } else if (token == "\\begin_layout") {
320                 lex.eatLine();
321                 string layoutname = lex.getString();
322
323                 font = LyXFont(LyXFont::ALL_INHERIT, bp.language);
324                 change = Change();
325
326                 LyXTextClass const & tclass = bp.getLyXTextClass();
327
328                 if (layoutname.empty()) {
329                         layoutname = tclass.defaultLayoutName();
330                 }
331
332                 bool hasLayout = tclass.hasLayout(layoutname);
333
334                 if (!hasLayout) {
335                         lyxerr << "Layout '" << layoutname << "' does not"
336                                << " exist in textclass '" << tclass.name()
337                                << "'." << endl;
338                         lyxerr << "Trying to use default layout instead."
339                                << endl;
340                         layoutname = tclass.defaultLayoutName();
341                 }
342
343                 par.layout(bp.getLyXTextClass()[layoutname]);
344
345                 // Test whether the layout is obsolete.
346                 LyXLayout_ptr const & layout = par.layout();
347                 if (!layout->obsoleted_by().empty())
348                         par.layout(bp.getLyXTextClass()[layout->obsoleted_by()]);
349
350                 par.params().read(lex);
351
352         } else if (token == "\\end_layout") {
353                 lyxerr << "Solitary \\end_layout in line " << lex.getLineNo() << "\n"
354                        << "Missing \\begin_layout?.\n";
355         } else if (token == "\\end_inset") {
356                 lyxerr << "Solitary \\end_inset in line " << lex.getLineNo() << "\n"
357                        << "Missing \\begin_inset?.\n";
358         } else if (token == "\\begin_inset") {
359                 InsetBase * inset = readInset(lex, buf);
360                 if (inset)
361                         par.insertInset(par.size(), inset, font, change);
362                 else {
363                         lex.eatLine();
364                         string line = lex.getString();
365                         buf.error(ErrorItem(_("Unknown Inset"), line,
366                                             par.id(), 0, par.size()));
367                         return 1;
368                 }
369         } else if (token == "\\family") {
370                 lex.next();
371                 font.setLyXFamily(lex.getString());
372         } else if (token == "\\series") {
373                 lex.next();
374                 font.setLyXSeries(lex.getString());
375         } else if (token == "\\shape") {
376                 lex.next();
377                 font.setLyXShape(lex.getString());
378         } else if (token == "\\size") {
379                 lex.next();
380                 font.setLyXSize(lex.getString());
381         } else if (token == "\\lang") {
382                 lex.next();
383                 string const tok = lex.getString();
384                 Language const * lang = languages.getLanguage(tok);
385                 if (lang) {
386                         font.setLanguage(lang);
387                 } else {
388                         font.setLanguage(bp.language);
389                         lex.printError("Unknown language `$$Token'");
390                 }
391         } else if (token == "\\numeric") {
392                 lex.next();
393                 font.setNumber(font.setLyXMisc(lex.getString()));
394         } else if (token == "\\emph") {
395                 lex.next();
396                 font.setEmph(font.setLyXMisc(lex.getString()));
397         } else if (token == "\\bar") {
398                 lex.next();
399                 string const tok = lex.getString();
400
401                 if (tok == "under")
402                         font.setUnderbar(LyXFont::ON);
403                 else if (tok == "no")
404                         font.setUnderbar(LyXFont::OFF);
405                 else if (tok == "default")
406                         font.setUnderbar(LyXFont::INHERIT);
407                 else
408                         lex.printError("Unknown bar font flag "
409                                        "`$$Token'");
410         } else if (token == "\\noun") {
411                 lex.next();
412                 font.setNoun(font.setLyXMisc(lex.getString()));
413         } else if (token == "\\color") {
414                 lex.next();
415                 font.setLyXColor(lex.getString());
416         } else if (token == "\\InsetSpace" || token == "\\SpecialChar") {
417
418                 // Insets don't make sense in a free-spacing context! ---Kayvan
419                 if (par.isFreeSpacing()) {
420                         if (token == "\\InsetSpace")
421                                 par.insertChar(par.size(), ' ', font, change);
422                         else if (lex.isOK()) {
423                                 lex.next();
424                                 string const next_token = lex.getString();
425                                 if (next_token == "\\-")
426                                         par.insertChar(par.size(), '-', font, change);
427                                 else {
428                                         lex.printError("Token `$$Token' "
429                                                        "is in free space "
430                                                        "paragraph layout!");
431                                 }
432                         }
433                 } else {
434                         auto_ptr<InsetBase> inset;
435                         if (token == "\\SpecialChar" )
436                                 inset.reset(new InsetSpecialChar);
437                         else
438                                 inset.reset(new InsetSpace);
439                         inset->read(buf, lex);
440                         par.insertInset(par.size(), inset.release(),
441                                         font, change);
442                 }
443         } else if (token == "\\i") {
444                 auto_ptr<InsetBase> inset(new InsetLatexAccent);
445                 inset->read(buf, lex);
446                 par.insertInset(par.size(), inset.release(), font, change);
447         } else if (token == "\\backslash") {
448                 par.insertChar(par.size(), '\\', font, change);
449         } else if (token == "\\newline") {
450                 auto_ptr<InsetBase> inset(new InsetNewline);
451                 inset->read(buf, lex);
452                 par.insertInset(par.size(), inset.release(), font, change);
453         } else if (token == "\\LyXTable") {
454                 auto_ptr<InsetBase> inset(new InsetTabular(buf));
455                 inset->read(buf, lex);
456                 par.insertInset(par.size(), inset.release(), font, change);
457         } else if (token == "\\bibitem") {
458                 InsetCommandParams p("bibitem", "dummy");
459                 auto_ptr<InsetBibitem> inset(new InsetBibitem(p));
460                 inset->read(buf, lex);
461                 par.insertInset(par.size(), inset.release(), font, change);
462         } else if (token == "\\hfill") {
463                 par.insertInset(par.size(), new InsetHFill, font, change);
464         } else if (token == "\\lyxline") {
465                 par.insertInset(par.size(), new InsetLine, font, change);
466         } else if (token == "\\newpage") {
467                 par.insertInset(par.size(), new InsetPagebreak, font, change);
468         } else if (token == "\\change_unchanged") {
469                 // Hack ! Needed for empty paragraphs :/
470                 // FIXME: is it still ??
471                 if (!par.size())
472                         par.cleanChanges();
473                 change = Change(Change::UNCHANGED);
474         } else if (token == "\\change_inserted") {
475                 lex.nextToken();
476                 istringstream is(lex.getString());
477                 int aid;
478                 lyx::time_type ct;
479                 is >> aid >> ct;
480                 change = Change(Change::INSERTED, bp.author_map[aid], ct);
481         } else if (token == "\\change_deleted") {
482                 lex.nextToken();
483                 istringstream is(lex.getString());
484                 int aid;
485                 lyx::time_type ct;
486                 is >> aid >> ct;
487                 change = Change(Change::DELETED, bp.author_map[aid], ct);
488         } else {
489                 lex.eatLine();
490                 string const s = bformat(_("Unknown token: %1$s %2$s\n"),
491                         token, lex.getString());
492
493                 buf.error(ErrorItem(_("Unknown token"), s,
494                                     par.id(), 0, par.size()));
495                 return 1;
496         }
497         return 0;
498 }
499
500 }
501
502
503 int readParagraph(Buffer const & buf, Paragraph & par, LyXLex & lex)
504 {
505         int unknown = 0;
506
507         lex.nextToken();
508         string token = lex.getString();
509
510         while (lex.isOK()) {
511
512                 unknown += readParToken(buf, par, lex, token);
513
514                 lex.nextToken();
515                 token = lex.getString();
516
517                 if (token.empty())
518                         continue;
519
520                 if (token == "\\end_layout") {
521                         //Ok, paragraph finished
522                         break;
523                 }
524
525                 lyxerr[Debug::PARSER] << "Handling paragraph token: `"
526                                       << token << '\'' << endl;
527                 if (token == "\\begin_layout" || token == "\\end_document"
528                     || token == "\\end_inset" || token == "\\begin_deeper"
529                     || token == "\\end_deeper") {
530                         lex.pushToken(token);
531                         lyxerr << "Paragraph ended in line "
532                                << lex.getLineNo() << "\n"
533                                << "Missing \\end_layout.\n";
534                         break;
535                 }
536         }
537
538         return unknown;
539 }
540
541
542 LyXFont const outerFont(ParagraphList::iterator pit,
543                         ParagraphList const & plist)
544 {
545         Paragraph::depth_type par_depth = pit->getDepth();
546         LyXFont tmpfont(LyXFont::ALL_INHERIT);
547
548         // Resolve against environment font information
549         while (pit != const_cast<ParagraphList&>(plist).end() &&
550                par_depth && !tmpfont.resolved()) {
551                 pit = outerHook(pit, plist);
552                 if (pit != const_cast<ParagraphList&>(plist).end()) {
553                         tmpfont.realize(pit->layout()->font);
554                         par_depth = pit->getDepth();
555                 }
556         }
557
558         return tmpfont;
559 }
560
561
562 ParagraphList::iterator outerPar(Buffer const & buf, InsetBase const * inset)
563 {
564         ParIterator pit = const_cast<Buffer &>(buf).par_iterator_begin();
565         ParIterator end = const_cast<Buffer &>(buf).par_iterator_end();
566         for ( ; pit != end; ++pit) {
567                 LyXText * text;
568                 // the second '=' below is intentional
569                 for (int i = 0; (text = inset->getText(i)); ++i)
570                         if (&text->paragraphs() == &pit.plist())
571                                 return pit.outerPar();
572
573                 InsetList::iterator ii = pit->insetlist.begin();
574                 InsetList::iterator iend = pit->insetlist.end();
575                 for ( ; ii != iend; ++ii)
576                         if (ii->inset == inset)
577                                 return pit.outerPar();
578         }
579         lyxerr << "outerPar: should not happen" << endl;
580         BOOST_ASSERT(false);
581         return const_cast<Buffer &>(buf).paragraphs().end(); // shut up compiler
582 }
583
584
585 Paragraph const & ownerPar(Buffer const & buf, InsetBase const * inset)
586 {
587         ParConstIterator pit = buf.par_iterator_begin();
588         ParConstIterator end = buf.par_iterator_end();
589         for ( ; pit != end; ++pit) {
590                 LyXText * text;
591                 // the second '=' below is intentional
592                 for (int i = 0; (text = inset->getText(i)); ++i)
593                         if (&text->paragraphs() == &pit.plist())
594                                 return *pit.pit();
595
596                 InsetList::const_iterator ii = pit->insetlist.begin();
597                 InsetList::const_iterator iend = pit->insetlist.end();
598                 for ( ; ii != iend; ++ii)
599                         if (ii->inset == inset)
600                                 return *pit.pit();
601         }
602         lyxerr << "ownerPar: should not happen" << endl;
603         BOOST_ASSERT(false);
604         return buf.paragraphs().front(); // shut up compiler
605 }
606
607
608 /// return the range of pars [beg, end[ owning the range of y [ystart, yend] 
609 void getParsInRange(ParagraphList & pl,
610                                 int ystart, int yend,
611                                 ParagraphList::iterator & beg,
612                                 ParagraphList::iterator & end)
613 {
614         ParagraphList::iterator const endpar = pl.end();
615         ParagraphList::iterator const begpar = pl.begin();
616
617         BOOST_ASSERT(begpar != endpar);
618
619         beg = endpar;
620         for (--beg; beg != begpar && beg->y > ystart; --beg)
621                 ;
622
623         for (end = beg ; end != endpar && end->y <= yend; ++end)
624                 ;
625 }
626