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