2 * \file InsetListings.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
7 * \author Jürgen Spitzmüller
9 * Full author contact details are available in file CREDITS.
14 #include "InsetListings.h"
17 #include "BufferView.h"
18 #include "BufferParams.h"
21 #include "DispatchResult.h"
23 #include "FuncRequest.h"
24 #include "FuncStatus.h"
25 #include "InsetCaption.h"
26 #include "InsetList.h"
28 #include "MetricsInfo.h"
29 #include "output_latex.h"
30 #include "TextClass.h"
32 #include "support/debug.h"
33 #include "support/docstream.h"
34 #include "support/gettext.h"
35 #include "support/lstrings.h"
36 #include "support/lassert.h"
38 #include "frontends/alert.h"
39 #include "frontends/Application.h"
41 #include <boost/regex.hpp>
46 using namespace lyx::support;
52 char const lstinline_delimiters[] =
53 "!*()-=+|;:'\"`,<.>/?QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm";
55 InsetListings::InsetListings(Buffer const & buf, InsetListingsParams const & par)
56 : InsetCollapsable(buf, par.status())
60 InsetListings::~InsetListings()
62 hideDialogs("listings", this);
66 Inset::DisplayType InsetListings::display() const
68 return params().isInline() || params().isFloat() ? Inline : AlignLeft;
72 void InsetListings::updateLabels(ParIterator const & it)
74 Counters & cnts = buffer().params().documentClass().counters();
75 string const saveflt = cnts.current_float();
77 // Tell to captions what the current float is
78 cnts.current_float("listing");
80 InsetCollapsable::updateLabels(it);
83 cnts.current_float(saveflt);
87 void InsetListings::write(ostream & os) const
89 os << "listings" << "\n";
90 InsetListingsParams const & par = params();
91 // parameter string is encoded to be a valid lyx token.
92 string opt = par.encodedString();
94 os << "lstparams \"" << opt << "\"\n";
96 os << "inline true\n";
98 os << "inline false\n";
99 InsetCollapsable::write(os);
103 void InsetListings::read(Lexer & lex)
107 string token = lex.getString();
108 if (token == "lstparams") {
110 string const value = lex.getString();
111 params().fromEncodedString(value);
112 } else if (token == "inline") {
114 params().setInline(lex.getBool());
116 // no special option, push back 'status' etc
117 lex.pushToken(token);
121 InsetCollapsable::read(lex);
125 docstring InsetListings::editMessage() const
127 return _("Opened Listing Inset");
131 int InsetListings::latex(odocstream & os, OutputParams const & runparams) const
133 string param_string = params().params();
134 // NOTE: I use {} to quote text, which is an experimental feature
135 // of the listings package (see page 25 of the manual)
137 bool isInline = params().isInline();
138 // get the paragraphs. We can not output them directly to given odocstream
139 // because we can not yet determine the delimiter character of \lstinline
142 ParagraphList::const_iterator par = paragraphs().begin();
143 ParagraphList::const_iterator end = paragraphs().end();
145 bool encoding_switched = false;
146 Encoding const * const save_enc = runparams.encoding;
148 if (!runparams.encoding->hasFixedWidth()) {
149 // We need to switch to a singlebyte encoding, since the listings
150 // package cannot deal with multiple-byte-encoded glyphs
151 Language const * const outer_language =
152 (runparams.local_font != 0) ?
153 runparams.local_font->language()
154 : buffer().params().language;
155 // We try if there's a singlebyte encoding for the current
156 // language; if not, fall back to latin1.
157 Encoding const * const lstenc =
158 (outer_language->encoding()->hasFixedWidth()) ?
159 outer_language->encoding()
160 : encodings.fromLyXName("iso8859-1");
161 pair<bool, int> const c = switchEncoding(os, buffer().params(),
162 runparams, *lstenc, true);
163 runparams.encoding = lstenc;
164 encoding_switched = true;
168 pos_type siz = par->size();
169 bool captionline = false;
170 for (pos_type i = 0; i < siz; ++i) {
171 if (i == 0 && par->isInset(i) && i + 1 == siz)
173 // ignore all struck out text and (caption) insets
174 if (par->isDeleted(i) || par->isInset(i))
176 char_type c = par->getChar(i);
177 // we can only output characters covered by the current
180 if (runparams.encoding->latexChar(c) == docstring(1, c))
182 else if (runparams.dryrun) {
183 code += "<" + _("LyX Warning: ")
184 + _("uncodable character") + " '";
185 code += docstring(1, c);
189 } catch (EncodingException & /* e */) {
190 if (runparams.dryrun) {
191 code += "<" + _("LyX Warning: ")
192 + _("uncodable character") + " '";
193 code += docstring(1, c);
200 // for the inline case, if there are multiple paragraphs
201 // they are simply joined. Otherwise, expect latex errors.
202 if (par != end && !isInline && !captionline) {
208 char const * delimiter = lstinline_delimiters;
209 for (; delimiter != '\0'; ++delimiter)
210 if (!contains(code, *delimiter))
212 // This code piece contains all possible special character? !!!
213 // Replace ! with a warning message and use ! as delimiter.
214 if (*delimiter == '\0') {
215 docstring delim_error = "<" + _("LyX Warning: ")
216 + _("no more lstline delimiters available") + ">";
217 code = subst(code, from_ascii("!"), delim_error);
218 delimiter = lstinline_delimiters;
219 if (!runparams.dryrun) {
220 // FIXME: warning should be passed to the error dialog
221 frontend::Alert::warning(_("Running out of delimiters"),
222 _("For inline program listings, one character must be reserved\n"
223 "as a delimiter. One of the listings, however, uses all available\n"
224 "characters, so none is left for delimiting purposes.\n"
225 "For the time being, I have replaced '!' by a warning, but you\n"
226 "must investigate!"));
229 if (param_string.empty())
230 os << "\\lstinline" << *delimiter;
232 os << "\\lstinline[" << from_utf8(param_string) << "]" << *delimiter;
236 OutputParams rp = runparams;
237 rp.moving_arg = true;
238 docstring const caption = getCaption(rp);
239 runparams.encoding = rp.encoding;
240 if (param_string.empty() && caption.empty())
241 os << "\n\\begin{lstlisting}\n";
243 os << "\n\\begin{lstlisting}[";
244 if (!caption.empty()) {
245 os << "caption={" << caption << '}';
246 if (!param_string.empty())
249 os << from_utf8(param_string) << "]\n";
252 os << code << "\n\\end{lstlisting}\n";
256 if (encoding_switched){
258 pair<bool, int> const c = switchEncoding(os, buffer().params(),
259 runparams, *save_enc, true);
260 runparams.encoding = save_enc;
263 if (!uncodable.empty()) {
264 // issue a warning about omitted characters
265 // FIXME: should be passed to the error dialog
266 frontend::Alert::warning(_("Uncodable characters in listings inset"),
267 bformat(_("The following characters in one of the program listings are\n"
268 "not representable in the current encoding and have been omitted:\n%1$s."),
276 docstring InsetListings::contextMenu(BufferView const &, int, int) const
278 return from_ascii("context-listings");
282 void InsetListings::doDispatch(Cursor & cur, FuncRequest & cmd)
284 switch (cmd.action) {
286 case LFUN_INSET_MODIFY: {
287 InsetListings::string2params(to_utf8(cmd.argument()), params());
290 case LFUN_INSET_DIALOG_UPDATE:
291 cur.bv().updateDialog("listings", params2string(params()));
293 case LFUN_TAB_INSERT:
294 if (cur.selection()) {
295 // If there is a selection, a tab is inserted at the
296 // beginning of each paragraph.
297 cur.recordUndoSelection();
298 pit_type const pit_end = cur.selEnd().pit();
299 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
300 paragraphs()[pit].insertChar(0, '\t',
301 buffer().params().trackChanges);
302 // Update the selection pos to make sure the selection does not
303 // change as the inserted tab will increase the logical pos.
304 if (cur.anchor_.pit() == pit)
305 cur.anchor_.forwardPos();
306 if (cur.pit() == pit)
311 // Maybe we shouldn't allow tabs within a line, because they
312 // are not (yet) aligned as one might do expect.
314 cur.insert(from_ascii("\t"));
318 case LFUN_TAB_DELETE:
319 if (cur.selection()) {
320 // If there is a selection, a tab (if present) is removed from
321 // the beginning of each paragraph.
322 cur.recordUndoSelection();
323 pit_type const pit_end = cur.selEnd().pit();
324 for (pit_type pit = cur.selBegin().pit(); pit <= pit_end; pit++) {
325 Paragraph & par = paragraphs()[pit];
326 if (par.getChar(0) == '\t') {
327 if (cur.pit() == pit)
329 if (cur.anchor_.pit() == pit && cur.anchor_.pos() > 0 )
330 cur.anchor_.backwardPos();
332 par.eraseChar(0, buffer().params().trackChanges);
334 // If no tab was present, try to remove up to four spaces.
335 for (int n_spaces = 0;
336 par.getChar(0) == ' ' && n_spaces < 4; ++n_spaces) {
337 if (cur.pit() == pit)
339 if (cur.anchor_.pit() == pit && cur.anchor_.pos() > 0 )
340 cur.anchor_.backwardPos();
342 par.eraseChar(0, buffer().params().trackChanges);
347 // If there is no selection, try to remove a tab or some spaces
348 // before the position of the cursor.
349 Paragraph & par = paragraphs()[cur.pit()];
350 pos_type const pos = cur.pos();
355 char_type const c = par.getChar(pos - 1);
359 par.eraseChar(cur.pos(), buffer().params().trackChanges);
361 for (int n_spaces = 0; cur.pos() > 0
362 && par.getChar(cur.pos() - 1) == ' ' && n_spaces < 4;
365 par.eraseChar(cur.pos(), buffer().params().trackChanges);
371 InsetCollapsable::doDispatch(cur, cmd);
377 bool InsetListings::getStatus(Cursor & cur, FuncRequest const & cmd,
378 FuncStatus & status) const
380 switch (cmd.action) {
381 case LFUN_INSET_MODIFY:
382 case LFUN_INSET_DIALOG_UPDATE:
383 status.setEnabled(true);
385 case LFUN_CAPTION_INSERT:
386 status.setEnabled(!params().isInline());
388 case LFUN_TAB_INSERT:
389 case LFUN_TAB_DELETE:
390 status.setEnabled(true);
393 return InsetCollapsable::getStatus(cur, cmd, status);
398 void InsetListings::setButtonLabel()
401 if (decoration() == InsetLayout::Classic)
402 setLabel(isOpen() ? _("Listing") : getNewLabel(_("Listing")));
404 setLabel(getNewLabel(_("Listing")));
408 void InsetListings::validate(LaTeXFeatures & features) const
410 features.require("listings");
411 string param_string = params().params();
412 if (param_string.find("\\color") != string::npos)
413 features.require("color");
414 InsetCollapsable::validate(features);
418 bool InsetListings::showInsetDialog(BufferView * bv) const
420 bv->showDialog("listings", params2string(params()),
421 const_cast<InsetListings *>(this));
426 docstring InsetListings::getCaption(OutputParams const & runparams) const
428 if (paragraphs().empty())
431 ParagraphList::const_iterator pit = paragraphs().begin();
432 for (; pit != paragraphs().end(); ++pit) {
433 InsetList::const_iterator it = pit->insetList().begin();
434 for (; it != pit->insetList().end(); ++it) {
435 Inset & inset = *it->inset;
436 if (inset.lyxCode() == CAPTION_CODE) {
437 odocstringstream ods;
439 static_cast<InsetCaption *>(it->inset);
440 ins->getOptArg(ods, runparams);
441 ins->getArgument(ods, runparams);
442 // the caption may contain \label{} but the listings
443 // package prefer caption={}, label={}
444 docstring cap = ods.str();
445 if (!contains(to_utf8(cap), "\\label{"))
448 // blah1\label{blah2} blah3
450 // blah1 blah3},label={blah2
452 // caption={blah1 blah3},label={blah2}
454 // NOTE that } is not allowed in blah2.
455 regex const reg("(.*)\\\\label\\{(.*?)\\}(.*)");
456 string const new_cap("\\1\\3},label={\\2");
457 return from_utf8(regex_replace(to_utf8(cap), reg, new_cap));
465 void InsetListings::string2params(string const & in,
466 InsetListingsParams & params)
468 params = InsetListingsParams();
471 istringstream data(in);
474 // discard "listings", which is only used to determine inset
480 string InsetListings::params2string(InsetListingsParams const & params)
483 data << "listings" << ' ';