]> git.lyx.org Git - lyx.git/blob - src/insets/InsetListingsParams.cpp
Add support for listings package. Two listings command \lstinline, \lstinputlisting...
[lyx.git] / src / insets / InsetListingsParams.cpp
1 /**
2  * \file InsetListingsParams.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Bo Peng
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "Lexer.h"
14 #include "InsetListingsParams.h"
15
16 #include "gettext.h"
17 #include "Length.h"
18
19 #include <sstream>
20 #include <boost/assert.hpp>
21
22 #include "support/lstrings.h"
23 #include "support/convert.h"
24
25 using std::vector;
26 using std::ostream;
27 using std::string;
28 using std::exception;
29 using lyx::support::trim;
30
31 namespace lyx
32 {
33
34 enum param_type {
35         ALL,
36         TRUEFALSE,
37         INTEGER,
38         LENGTH,
39         ONEOF,
40         SUBSETOF,
41 };
42
43
44 /** Information about each parameter
45  */
46 struct listings_param_info {
47         /// name of the parameter
48         char const * name;
49         /// default value
50         char const * value;
51         // for option with value "true", "false", 
52         // if onoff is true,
53         //   "true":  option
54         //   "false": 
55         //   "other": option="other"
56         // onoff is false,
57         //   "true":  option=true
58         //   "false": option=false
59         bool onoff;
60         /// validator type
61         param_type type;
62         // ALL:
63         // TRUEFALSE:
64         // INTEGER:
65         // LENGTH:
66         //     info is ignored.
67         // ONEOF
68         //     info is a \n separated string with allowed values
69         // SUBSETOF
70         //     info is a string from which par is composed of
71         //     (e.g. floatplacement can be one or more of tbph)
72         char const * info;
73         //
74         char const * hint;
75 };
76
77
78 char const * allowed_languages = 
79         "no language\nBAP\nACSL\nAda\nALGOL\nC\nC++\nCaml\nClean\nCobol\n"
80         "Comal 80\ncsh\nDelphi\nEiffel\nElan\nEuphoria\nFortran\nHaskell\n"
81         "HTML\nIDL\nJava\nLisp\nLogo\nmake\nMathematica\nMatlab\nMercury\n"
82         "Miranda\nML\nModula-2\nOberon-2\nOCL\nPascal\nPerl\nPHP\nPL/I\nPOV\n"
83         "Python\nProlog\nR\nS\nSAS\nSHELXL\nSimula\ntcl\nSQL\nTeX\nVBScript\n"
84         "VHDL\nXML";
85
86 char const * style_hint = "Use \\footnotessize, \\small, \\itshape, \\ttfamily or something like that";
87 char const * frame_hint = "none, leftline, topline, bottomline, lines, single, shadowbox or subset of trblTRBL";
88 char const * frameround_hint = "The foru letters (t or f) attached to top right, bottom right, bottom left and top left corner.";
89 char const * color_hint = "Enter something like \\color{white}";
90
91 /// options copied from page 26 of listings manual
92 // FIXME: add default parameters ... (which is not used now)
93 listings_param_info const listings_param_table[] = {
94         { "float", "false", true,  SUBSETOF, "tbph", "" },
95         { "floatplacement", "tbp", false, SUBSETOF, "tbph", "" },
96         { "aboveskip", "\\medskipamount", false, LENGTH, "", "" },
97         { "belowskip", "\\medskipamount", false, LENGTH, "", "" },
98         { "lineskip", "", false, LENGTH, "", "" },
99         { "boxpos", "", false, SUBSETOF, "bct", "" },
100         { "print", "", false, TRUEFALSE, "", "" },
101         { "firstline", "", false, INTEGER, "", "" },
102         { "lastline", "", false, INTEGER, "", "" },
103         { "showlines", "", false, TRUEFALSE, "", "" },
104         { "emptylines", "", false, ALL, "", "Expect a number with an optional * before it" },
105         { "gobble", "", false, INTEGER, "", "" },
106         { "style", "", false, ALL, "", "" },
107         { "language", "", false, ONEOF, allowed_languages, "" },
108         { "alsolanguage", "", false, ONEOF, allowed_languages, "" },
109         { "defaultdialect", "", false, ONEOF, allowed_languages, "" },
110         { "printpod", "", false, TRUEFALSE, "", "" },
111         { "usekeywordsintag", "", false, TRUEFALSE, "", "" },
112         { "tagstyle", "", false, ALL, "", style_hint },
113         { "markfirstintag", "", false, ALL, "", style_hint },
114         { "makemacrouse", "", false, TRUEFALSE, "", "" },
115         { "basicstyle", "", false, ALL, "", style_hint },
116         { "identifierstyle", "", false, ALL, "", style_hint },
117         { "commentstyle", "", false, ALL, "", style_hint },
118         { "stringstyle", "", false, ALL, "", style_hint },
119         { "keywordstyle", "", false, ALL, "", style_hint },
120         { "ndkeywordstyle", "", false, ALL, "", style_hint },
121         { "classoffset", "", false, INTEGER, "", "" },
122         { "texcsstyle", "", false, ALL, "", style_hint },
123         { "directivestyle", "", false, ALL, "", style_hint },
124         { "emph", "", false, ALL, "", "" },
125         { "moreemph", "", false, ALL, "", "" },
126         { "deleteemph", "", false, ALL, "", "" },
127         { "emphstyle", "", false, ALL, "", "" },
128         { "delim", "", false, ALL, "", "" },
129         { "moredelim", "", false, ALL, "", "" },
130         { "deletedelim", "", false, ALL, "", "" },
131         { "extendedchars", "", false, TRUEFALSE, "", "" },
132         { "inputencoding", "", false, ALL, "", "" },
133         { "upquote", "", false, TRUEFALSE, "", "" },
134         { "tabsize", "", false, INTEGER, "", "" },
135         { "showtabs", "", false, ALL, "", "" },
136         { "tab", "", false, ALL, "", "" },
137         { "showspaces", "", false, TRUEFALSE, "", "" },
138         { "showstringspaces", "", false, TRUEFALSE, "", "" },
139         { "formfeed", "", false, ALL, "", "" },
140         { "numbers", "", false, ONEOF, "none\nleft\nright", "" },
141         { "stepnumber", "", false, INTEGER, "", "" },
142         { "numberfirstline", "", false, TRUEFALSE, "", "" },
143         { "numberstyle", "", false, ALL, "", style_hint },
144         { "numbersep", "", false, LENGTH, "", "" },
145         { "numberblanklines", "", false, ALL, "", "" },
146         { "firstnumber", "", false, ALL, "", "auto, last or a number" },
147         { "name", "", false, ALL, "", "" },
148         { "thelstnumber", "", false, ALL, "", "" },
149         { "title", "", false, ALL, "", "" },
150         { "caption", "", false, ALL, "", "" },
151         { "label", "", false, ALL, "", "" },
152         { "nolol", "", false, TRUEFALSE, "", "" },
153         { "captionpos", "", false, SUBSETOF, "tb", "" },
154         { "abovecaptionskip", "", false, LENGTH, "", "" },
155         { "belowcaptionskip", "", false, LENGTH, "", "" },
156         { "linewidth", "", false, LENGTH, "", "" },
157         { "xleftmargin", "", false, LENGTH, "", "" },
158         { "xrightmargin", "", false, LENGTH, "", "" },
159         { "resetmargin", "", false, TRUEFALSE, "", "" },
160         { "breaklines", "", false, TRUEFALSE, "", "" },
161         { "prebreak", "", false, ALL, "", "" },
162         { "postbreak", "", false, ALL, "", "" },
163         { "breakindent", "", false, LENGTH, "", "" },
164         { "breakautoindent", "", false, TRUEFALSE, "", "" },
165         { "frame", "", false, ALL, "", frame_hint },
166         { "frameround", "", false, SUBSETOF, "tf", frameround_hint },
167         { "framesep", "", false, LENGTH, "", "" },
168         { "rulesep", "", false, LENGTH, "", "" },
169         { "framerule", "", false, LENGTH, "", "" },
170         { "framexleftmargin", "", false, LENGTH, "", "" },
171         { "framexrightmargin", "", false, LENGTH, "", "" },
172         { "framextopmargin", "", false, LENGTH, "", "" },
173         { "framexbottommargin", "", false, LENGTH, "", "" },
174         { "backgroundcolor", "", false, ALL, "", color_hint },
175         { "rulecolor", "", false, ALL, "", color_hint },
176         { "fillcolor", "", false, ALL, "", color_hint },
177         { "rulesepcolor", "", false, ALL, "", color_hint },
178         { "frameshape", "", false, ALL, "", "" },
179         { "index", "", false, ALL, "", "" },
180         { "moreindex", "", false, ALL, "", "" },
181         { "deleteindex", "", false, ALL, "", "" },
182         { "indexstyle", "", false, ALL, "", "" },
183         { "columns", "", false, ALL, "", "" },
184         { "flexiblecolumns", "", false, ALL, "", "" },
185         { "keepspaces", "", false, TRUEFALSE, "", "" },
186         { "basewidth", "", false, LENGTH, "", "" },
187         { "fontadjust", "", true, TRUEFALSE, "", "" },
188         { "texcl", "", false, TRUEFALSE, "", "" },
189         { "mathescape", "", false, TRUEFALSE, "", "" },
190         { "escapechar", "", false, ALL, "", "" },
191         { "escapeinside", "", false, ALL, "", "" },
192         { "escepeinside", "", false, ALL, "", "" },
193         { "escepebegin", "", false, ALL, "", "" },
194         { "escepeend", "", false, ALL, "", "" },
195         { "fancyvrb", "", false, TRUEFALSE, "", "" },
196         { "fvcmdparams", "", false, ALL, "", "" },
197         { "morefvcmdparams", "", false, ALL, "", "" },
198         { "keywordsprefix", "", false, ALL, "", "" },
199         { "keywords", "", false, ALL, "", "" },
200         { "morekeywords", "", false, ALL, "", "" },
201         { "deletekeywords", "", false, ALL, "", "" },
202         { "ndkeywords", "", false, ALL, "", "" },
203         { "morendkeywords", "", false, ALL, "", "" },
204         { "deletendkeywords", "", false, ALL, "", "" },
205         { "texcs", "", false, ALL, "", "" },
206         { "moretexcs", "", false, ALL, "", "" },
207         { "deletetexcs", "", false, ALL, "", "" },
208         { "directives", "", false, ALL, "", "" },
209         { "moredirectives", "", false, ALL, "", "" },
210         { "deletedirectives", "", false, ALL, "", "" },
211         { "sensitive", "", false, ALL, "", "" },
212         { "alsoletter", "", false, ALL, "", "" },
213         { "alsodigit", "", false, ALL, "", "" },
214         { "alsoother", "", false, ALL, "", "" },
215         { "otherkeywords", "", false, ALL, "", "" },
216         { "tag", "", false, ALL, "", "" },
217         { "string", "", false, ALL, "", "" },
218         { "morestring", "", false, ALL, "", "" },
219         { "deletestring", "", false, ALL, "", "" },
220         { "comment", "", false, ALL, "", "" },
221         { "morecomment", "", false, ALL, "", "" },
222         { "deletecomment", "", false, ALL, "", "" },
223         { "keywordcomment", "", false, ALL, "", "" },
224         { "morekeywordcomment", "", false, ALL, "", "" },
225         { "deletekeywordcomment", "", false, ALL, "", "" },
226         { "keywordcommentsemicolon", "", false, ALL, "", "" },
227         { "podcomment", "", false, ALL, "", "" },
228         { "", "", false, ALL, "", ""}
229 };
230
231
232 class parValidator
233 {
234 public:
235         parValidator(string const & name);
236
237         /// validate given parameter
238         /// invalidParam will be thrown if invalid 
239         /// parameter is found.
240         void validate(std::string const & par) const;
241
242 private:
243         /// parameter name
244         string const & name;
245         ///
246         listings_param_info const * info;
247 };
248
249
250 parValidator::parValidator(string const & n)
251         : name(n), info(0)
252 {
253         if (name.empty())
254                 throw invalidParam("Invalid (empty) listings param name.");
255         else if (name == "?") {
256                 string pars;
257                 size_t idx = 0;
258                 while (listings_param_table[idx].name != string()) {
259                         if (!pars.empty())
260                                 pars += ", ";
261                         pars += listings_param_table[idx].name;
262                         ++idx;
263                 }
264                 throw invalidParam("Available listings parameters are " + pars);
265         }
266         // locate name in parameter table
267         size_t idx = 0;
268         while (listings_param_table[idx].name != name && listings_param_table[idx].name != string())
269                 ++idx;
270         // found the name
271         if (listings_param_table[idx].name != "") {
272                 info = &listings_param_table[idx];
273                 return;
274         }
275         // otherwise, produce a meaningful error message.
276         string matching_names;
277         for (size_t i = 0; i < idx; ++i) {
278                 string n(listings_param_table[i].name);
279                 if (n.size() >= name.size() && n.substr(0, name.size()) == name) {
280                         if (matching_names.empty())
281                                 matching_names += n;
282                         else
283                                 matching_names += ", " + n;
284                 }
285         }
286         if (matching_names.empty())
287                 throw invalidParam("Unknown listings param name: " + name);
288         else
289                 throw invalidParam("Parameters starting with '" + name + 
290                         "': " + matching_names);
291 }
292
293
294 void parValidator::validate(std::string const & par) const
295 {
296         switch (info->type) {
297         case ALL:
298                 if (par.empty() && !info->onoff) {
299                         if (info->hint != "")
300                                 throw invalidParam(info->hint);
301                         else
302                                 throw invalidParam("An value is expected");
303                 }
304                 return;
305         case TRUEFALSE: {
306                 if (par.empty() && !info->onoff) {
307                         if (info->hint != "")
308                                 throw invalidParam(info->hint);
309                         else
310                                 throw invalidParam("Please specify true or false");
311                 }
312                 if (par != "true" && par != "false")
313                         throw invalidParam("Only true or false is allowed for parameter" + name);
314                 return;
315         }
316         case INTEGER: {
317                 if (par.empty() && !info->onoff) {
318                         if (info->hint != "")
319                                 throw invalidParam(info->hint);
320                         else
321                                 throw invalidParam("Please specify an integer value");
322                 }
323                 if (convert<int>(par) == 0 && par[0] != '0')
324                         throw invalidParam("An integer is expected for parameter " + name);
325                 return;
326         }
327         case LENGTH: {
328                 if (par.empty() && !info->onoff) {
329                         if (info->hint != "")
330                                 throw invalidParam(info->hint);
331                         else
332                                 throw invalidParam("Please specify a latex length expression");
333                 }
334                 if (!isValidLength(par))
335                         throw invalidParam("Invalid latex length expression for parameter " + name);
336                 return;
337         }
338         case ONEOF: {
339                 if (par.empty() && !info->onoff) {
340                         if (info->hint != "")
341                                 throw invalidParam(info->hint);
342                         else
343                                 throw invalidParam("Please specify one of " + string(info->info));
344                 }
345                 // break value to allowed strings
346                 vector<string> lists;
347                 string v;
348                 for (size_t i = 0; info->info[i] != '\0'; ++i) {
349                         if (info->info[i] == '\n') {
350                                 lists.push_back(v);
351                                 v = string();
352                         } else
353                                 v += info->info[i];
354                 }
355                 if (!v.empty())
356                         lists.push_back(v);
357
358                 // good, find the string
359                 if (std::find(lists.begin(), lists.end(), par) != lists.end())
360                         return;
361                 // otherwise, produce a meaningful error message.
362                 string matching_names;
363                 for (vector<string>::iterator it = lists.begin(); 
364                         it != lists.end(); ++it) {
365                         if (it->size() >= par.size() && it->substr(0, par.size()) == par) {
366                                 if (matching_names.empty())
367                                         matching_names += *it;
368                                 else
369                                         matching_names += ", " + *it;
370                         }
371                 }
372                 if (matching_names.empty())
373                         throw invalidParam("Try one of " + string(info->info));
374                 else
375                         throw invalidParam("I guess you mean " + matching_names);
376                 return;
377         }
378         case SUBSETOF: {
379                 if (par.empty() && !info->onoff) {
380                         if (info->hint != "")
381                                 throw invalidParam(info->hint);
382                         else
383                                 throw invalidParam("Please specify one or more of " + string(info->info));
384                 }
385                 for (size_t i = 0; i < par.size(); ++i)
386                         if (string(info->info).find(par[i], 0) == string::npos)
387                                 throw invalidParam("Parameter " + name + 
388                                         " should be composed of one or more of " + info->info);
389                 return;
390         }
391         }
392 }
393
394
395 InsetListingsParams::InsetListingsParams() :
396         inline_(false), status_(InsetCollapsable::Open), params_()
397 {
398 }
399
400
401 InsetListingsParams::InsetListingsParams(string const & par, bool in, InsetCollapsable::CollapseStatus s)
402         : inline_(in), status_(s)
403 {
404         // this will activate parameter validation.
405         fromEncodedString(par);
406 }
407
408
409 void InsetListingsParams::write(ostream & os) const
410 {
411         if (inline_)
412                 os << "true ";
413         else
414                 os << "false ";
415         os << status_ << " \""  << encodedString() << "\"";
416 }
417
418
419 void InsetListingsParams::read(Lexer & lex)
420 {
421         lex >> inline_;
422         int s;
423         lex >> s;
424         if (lex)
425                 status_ = static_cast<InsetCollapsable::CollapseStatus>(s);
426         string par;
427         lex >> par;
428         fromEncodedString(par);
429 }
430
431
432 void InsetListingsParams::addParam(string const & key, string const & value)
433 {       
434         if (key.empty())
435                 return;
436         // exception may be thown.
437         parValidator(key.c_str()).validate(value);
438         if (!params_.empty())
439                 params_ += ',';
440         if (value.empty())
441                 params_ += key;
442         else {
443                 // check onoff flag
444                 size_t idx = 0;
445                 while (listings_param_table[idx].name != key)
446                         ++idx;
447                 BOOST_ASSERT(listings_param_table[idx].name == key);
448                 if (listings_param_table[idx].onoff && value == "false")
449                         params_ += key;
450                 else
451                         params_ += key + '=' + value;
452         }
453 }
454
455
456 void InsetListingsParams::setParams(string const & par)
457 {
458         string key;
459         string value;
460         bool isValue = false;
461         params_.clear();
462         for (size_t i = 0; i < par.size(); ++i) {
463                 // end of par
464                 if (par[i] == '\n' || par[i] == ',') {
465                         addParam(trim(key), trim(value));
466                         key = string();
467                         value = string();
468                         isValue = false;
469                 } else if (par[i] == '=')
470                         isValue = true;
471                 else if (isValue)
472                         value += par[i];
473                 else
474                         key += par[i];
475         }
476         if (!trim(key).empty())
477                 addParam(trim(key), trim(value));
478 }
479
480
481 string InsetListingsParams::encodedString() const
482 {
483         // Encode string!
484         // FIXME:
485         // '"' should be handled differently because it will 
486         // terminate a lyx token. Right now, it is silently ignored. 
487         string par;
488         for (size_t i = 0; i < params_.size(); ++i) {
489                 BOOST_ASSERT(params_[i] != '\n');
490                 if (params_[i] != '"')
491                         par += params_[i];
492         }
493         return par;
494 }
495
496
497 string InsetListingsParams::separatedParams(bool keepComma) const
498 {
499         // , might be used as regular parameter option so 
500         // the prcess might be more complicated than what I am doing here
501         string opt;
502         for (size_t i = 0; i < params_.size(); ++i)
503                 if (params_[i] == ',') {
504                         if (keepComma)
505                                 opt += ",\n";
506                         else
507                                 opt += "\n";
508                 } else
509                         opt += params_[i];
510         return opt;
511 }
512
513
514 void InsetListingsParams::fromEncodedString(string const & in)
515 {
516         // Decode string! 
517         // Do nothing because " was silently ignored.
518         setParams(in);
519 }
520
521
522
523 } // namespace lyx