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"
27 #include "MetricsInfo.h"
28 #include "output_latex.h"
29 #include "TextClass.h"
31 #include "support/debug.h"
32 #include "support/docstream.h"
33 #include "support/gettext.h"
34 #include "support/lstrings.h"
35 #include "support/lassert.h"
37 #include "frontends/alert.h"
38 #include "frontends/Application.h"
40 #include <boost/regex.hpp>
45 using namespace lyx::support
;
51 char const lstinline_delimiters
[] =
52 "!*()-=+|;:'\"`,<.>/?QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm";
54 InsetListings::InsetListings(Buffer
const & buf
, InsetListingsParams
const & par
)
55 : InsetCollapsable(buf
)
57 status_
= par
.status();
61 InsetListings::~InsetListings()
63 hideDialogs("listings", this);
67 Inset::DisplayType
InsetListings::display() const
69 return params().isInline() || params().isFloat() ? Inline
: AlignLeft
;
73 void InsetListings::updateLabels(ParIterator
const & it
)
75 Counters
& cnts
= buffer().masterBuffer()->params().documentClass().counters();
76 string
const saveflt
= cnts
.current_float();
78 // Tell to captions what the current float is
79 cnts
.current_float("listing");
81 InsetCollapsable::updateLabels(it
);
84 cnts
.current_float(saveflt
);
88 void InsetListings::write(ostream
& os
) const
90 os
<< "listings" << "\n";
91 InsetListingsParams
const & par
= params();
92 // parameter string is encoded to be a valid lyx token.
93 string opt
= par
.encodedString();
95 os
<< "lstparams \"" << opt
<< "\"\n";
97 os
<< "inline true\n";
99 os
<< "inline false\n";
100 InsetCollapsable::write(os
);
104 void InsetListings::read(Lexer
& lex
)
108 string token
= lex
.getString();
109 if (token
== "lstparams") {
111 string
const value
= lex
.getString();
112 params().fromEncodedString(value
);
113 } else if (token
== "inline") {
115 params().setInline(lex
.getBool());
117 // no special option, push back 'status' etc
118 lex
.pushToken(token
);
122 InsetCollapsable::read(lex
);
126 docstring
InsetListings::editMessage() const
128 return _("Opened Listing Inset");
132 int InsetListings::latex(odocstream
& os
, OutputParams
const & runparams
) const
134 string param_string
= params().params();
135 // NOTE: I use {} to quote text, which is an experimental feature
136 // of the listings package (see page 25 of the manual)
138 bool const isInline
= params().isInline();
139 // get the paragraphs. We can not output them directly to given odocstream
140 // because we can not yet determine the delimiter character of \lstinline
143 ParagraphList::const_iterator par
= paragraphs().begin();
144 ParagraphList::const_iterator end
= paragraphs().end();
146 bool encoding_switched
= false;
147 Encoding
const * const save_enc
= runparams
.encoding
;
149 if (!runparams
.encoding
->hasFixedWidth()) {
150 // We need to switch to a singlebyte encoding, since the listings
151 // package cannot deal with multiple-byte-encoded glyphs
152 Language
const * const outer_language
=
153 (runparams
.local_font
!= 0) ?
154 runparams
.local_font
->language()
155 : buffer().params().language
;
156 // We try if there's a singlebyte encoding for the current
157 // language; if not, fall back to latin1.
158 Encoding
const * const lstenc
=
159 (outer_language
->encoding()->hasFixedWidth()) ?
160 outer_language
->encoding()
161 : encodings
.fromLyXName("iso8859-1");
162 pair
<bool, int> const c
= switchEncoding(os
, buffer().params(),
163 runparams
, *lstenc
, true);
164 runparams
.encoding
= lstenc
;
165 encoding_switched
= true;
169 pos_type siz
= par
->size();
170 bool captionline
= false;
171 for (pos_type i
= 0; i
< siz
; ++i
) {
172 if (i
== 0 && par
->isInset(i
) && i
+ 1 == siz
)
174 // ignore all struck out text and (caption) insets
175 if (par
->isDeleted(i
) || par
->isInset(i
))
177 char_type c
= par
->getChar(i
);
178 // we can only output characters covered by the current
181 if (runparams
.encoding
->latexChar(c
) == docstring(1, c
))
183 else if (runparams
.dryrun
) {
184 code
+= "<" + _("LyX Warning: ")
185 + _("uncodable character") + " '";
186 code
+= docstring(1, c
);
190 } catch (EncodingException
& /* e */) {
191 if (runparams
.dryrun
) {
192 code
+= "<" + _("LyX Warning: ")
193 + _("uncodable character") + " '";
194 code
+= docstring(1, c
);
201 // for the inline case, if there are multiple paragraphs
202 // they are simply joined. Otherwise, expect latex errors.
203 if (par
!= end
&& !isInline
&& !captionline
) {
209 char const * delimiter
= lstinline_delimiters
;
210 for (; delimiter
!= '\0'; ++delimiter
)
211 if (!contains(code
, *delimiter
))
213 // This code piece contains all possible special character? !!!
214 // Replace ! with a warning message and use ! as delimiter.
215 if (*delimiter
== '\0') {
216 docstring delim_error
= "<" + _("LyX Warning: ")
217 + _("no more lstline delimiters available") + ">";
218 code
= subst(code
, from_ascii("!"), delim_error
);
219 delimiter
= lstinline_delimiters
;
220 if (!runparams
.dryrun
) {
221 // FIXME: warning should be passed to the error dialog
222 frontend::Alert::warning(_("Running out of delimiters"),
223 _("For inline program listings, one character must be reserved\n"
224 "as a delimiter. One of the listings, however, uses all available\n"
225 "characters, so none is left for delimiting purposes.\n"
226 "For the time being, I have replaced '!' by a warning, but you\n"
227 "must investigate!"));
230 if (param_string
.empty())
231 os
<< "\\lstinline" << *delimiter
;
233 os
<< "\\lstinline[" << from_utf8(param_string
) << "]" << *delimiter
;
237 OutputParams rp
= runparams
;
238 rp
.moving_arg
= true;
239 docstring
const caption
= getCaption(rp
);
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::xhtml(odocstream
& os
, OutputParams
const & rp
) const
278 odocstringstream out
;
280 bool const isInline
= params().isInline();
284 out
<< "<div class='float float-listings'>\n";
285 docstring caption
= getCaptionHTML(rp
);
286 if (!caption
.empty())
287 out
<< "<div class='float-caption'>" << caption
<< "</div>\n";
291 OutputParams newrp
= rp
;
292 newrp
.disable_captions
= true;
293 docstring def
= InsetText::xhtml(out
, newrp
);
301 // In this case, this needs to be deferred, but we'll put it
302 // before anything the text itself deferred.
303 def
= out
.str() + '\n' + def
;
309 docstring
InsetListings::contextMenu(BufferView
const &, int, int) const
311 return from_ascii("context-listings");
315 void InsetListings::doDispatch(Cursor
& cur
, FuncRequest
& cmd
)
317 switch (cmd
.action
) {
319 case LFUN_INSET_MODIFY
: {
320 InsetListings::string2params(to_utf8(cmd
.argument()), params());
324 case LFUN_INSET_DIALOG_UPDATE
:
325 cur
.bv().updateDialog("listings", params2string(params()));
328 case LFUN_TAB_INSERT
: {
329 bool const multi_par_selection
= cur
.selection() &&
330 cur
.selBegin().pit() != cur
.selEnd().pit();
331 if (multi_par_selection
) {
332 // If there is a multi-paragraph selection, a tab is inserted
333 // at the beginning of each paragraph.
334 cur
.recordUndoSelection();
335 pit_type
const pit_end
= cur
.selEnd().pit();
336 for (pit_type pit
= cur
.selBegin().pit(); pit
<= pit_end
; pit
++) {
337 paragraphs()[pit
].insertChar(0, '\t',
338 buffer().params().trackChanges
);
339 // Update the selection pos to make sure the selection does not
340 // change as the inserted tab will increase the logical pos.
341 if (cur
.anchor_
.pit() == pit
)
342 cur
.anchor_
.forwardPos();
343 if (cur
.pit() == pit
)
348 // Maybe we shouldn't allow tabs within a line, because they
349 // are not (yet) aligned as one might do expect.
350 FuncRequest
cmd(LFUN_SELF_INSERT
, from_ascii("\t"));
356 case LFUN_TAB_DELETE
:
357 if (cur
.selection()) {
358 // If there is a selection, a tab (if present) is removed from
359 // the beginning of each paragraph.
360 cur
.recordUndoSelection();
361 pit_type
const pit_end
= cur
.selEnd().pit();
362 for (pit_type pit
= cur
.selBegin().pit(); pit
<= pit_end
; pit
++) {
363 Paragraph
& par
= paragraphs()[pit
];
364 if (par
.getChar(0) == '\t') {
365 if (cur
.pit() == pit
)
367 if (cur
.anchor_
.pit() == pit
&& cur
.anchor_
.pos() > 0 )
368 cur
.anchor_
.backwardPos();
370 par
.eraseChar(0, buffer().params().trackChanges
);
372 // If no tab was present, try to remove up to four spaces.
373 for (int n_spaces
= 0;
374 par
.getChar(0) == ' ' && n_spaces
< 4; ++n_spaces
) {
375 if (cur
.pit() == pit
)
377 if (cur
.anchor_
.pit() == pit
&& cur
.anchor_
.pos() > 0 )
378 cur
.anchor_
.backwardPos();
380 par
.eraseChar(0, buffer().params().trackChanges
);
385 // If there is no selection, try to remove a tab or some spaces
386 // before the position of the cursor.
387 Paragraph
& par
= paragraphs()[cur
.pit()];
388 pos_type
const pos
= cur
.pos();
393 char_type
const c
= par
.getChar(pos
- 1);
397 par
.eraseChar(cur
.pos(), buffer().params().trackChanges
);
399 for (int n_spaces
= 0; cur
.pos() > 0
400 && par
.getChar(cur
.pos() - 1) == ' ' && n_spaces
< 4;
403 par
.eraseChar(cur
.pos(), buffer().params().trackChanges
);
409 InsetCollapsable::doDispatch(cur
, cmd
);
415 bool InsetListings::getStatus(Cursor
& cur
, FuncRequest
const & cmd
,
416 FuncStatus
& status
) const
418 switch (cmd
.action
) {
419 case LFUN_INSET_MODIFY
:
420 case LFUN_INSET_DIALOG_UPDATE
:
421 status
.setEnabled(true);
423 case LFUN_CAPTION_INSERT
:
424 status
.setEnabled(!params().isInline());
426 case LFUN_TAB_INSERT
:
427 case LFUN_TAB_DELETE
:
428 status
.setEnabled(true);
431 return InsetCollapsable::getStatus(cur
, cmd
, status
);
436 docstring
const InsetListings::buttonLabel(BufferView
const & bv
) const
439 if (decoration() == InsetLayout::CLASSIC
)
440 return isOpen(bv
) ? _("Listing") : getNewLabel(_("Listing"));
442 return getNewLabel(_("Listing"));
446 void InsetListings::validate(LaTeXFeatures
& features
) const
448 features
.require("listings");
449 string param_string
= params().params();
450 if (param_string
.find("\\color") != string::npos
)
451 features
.require("color");
452 InsetCollapsable::validate(features
);
456 bool InsetListings::showInsetDialog(BufferView
* bv
) const
458 bv
->showDialog("listings", params2string(params()),
459 const_cast<InsetListings
*>(this));
464 docstring
InsetListings::getCaption(OutputParams
const & runparams
) const
466 if (paragraphs().empty())
469 InsetCaption
const * ins
= getCaptionInset();
473 odocstringstream ods
;
474 ins
->getOptArg(ods
, runparams
);
475 ins
->getArgument(ods
, runparams
);
476 // the caption may contain \label{} but the listings
477 // package prefer caption={}, label={}
478 docstring cap
= ods
.str();
479 if (!contains(to_utf8(cap
), "\\label{"))
482 // blah1\label{blah2} blah3
484 // blah1 blah3},label={blah2
486 // caption={blah1 blah3},label={blah2}
488 // NOTE that } is not allowed in blah2.
489 regex
const reg("(.*)\\\\label\\{(.*?)\\}(.*)");
490 string
const new_cap("\\1\\3},label={\\2");
491 return from_utf8(regex_replace(to_utf8(cap
), reg
, new_cap
));
495 void InsetListings::string2params(string
const & in
,
496 InsetListingsParams
& params
)
498 params
= InsetListingsParams();
501 istringstream
data(in
);
504 // discard "listings", which is only used to determine inset
510 string
InsetListings::params2string(InsetListingsParams
const & params
)
513 data
<< "listings" << ' ';