]> git.lyx.org Git - lyx.git/blobdiff - src/insets/InsetListings.cpp
Fixup 572b06d6: reduce cache size for breakString
[lyx.git] / src / insets / InsetListings.cpp
index 033b72ea2cba69eacd55871a73c58a238f529a22..67a046208542ce222b671301be29e2c78a73379e 100644 (file)
 #include "Buffer.h"
 #include "BufferView.h"
 #include "BufferParams.h"
-#include "Counters.h"
 #include "Cursor.h"
-#include "DispatchResult.h"
 #include "Encoding.h"
 #include "FuncRequest.h"
 #include "FuncStatus.h"
 #include "InsetCaption.h"
+#include "InsetLabel.h"
+#include "InsetLayout.h"
 #include "Language.h"
 #include "LaTeXFeatures.h"
 #include "Lexer.h"
 #include "output_latex.h"
+#include "output_docbook.h"
 #include "output_xhtml.h"
-#include "OutputParams.h"
-#include "TextClass.h"
 #include "TexRow.h"
 #include "texstream.h"
 
@@ -42,8 +41,8 @@
 #include "frontends/alert.h"
 #include "frontends/Application.h"
 
-#include "support/regex.h"
-
+#include <cstring>
+#include <regex>
 #include <sstream>
 
 using namespace std;
@@ -66,9 +65,18 @@ InsetListings::~InsetListings()
 }
 
 
-Inset::DisplayType InsetListings::display() const
+int InsetListings::rowFlags() const
+{
+       return params().isInline() || params().isFloat() ? Inline : Display | AlignLeft;
+}
+
+
+docstring InsetListings::layoutName() const
 {
-       return params().isInline() || params().isFloat() ? Inline : AlignLeft;
+       if (buffer().params().use_minted)
+               return from_ascii("MintedListings");
+       else
+               return from_ascii("Listings");
 }
 
 
@@ -110,6 +118,27 @@ void InsetListings::read(Lexer & lex)
 }
 
 
+Encoding const * InsetListings::forcedEncoding(Encoding const * inner_enc,
+                                              Encoding const * outer_enc) const
+{
+       // The listings package cannot deal with multi-byte-encoded
+       // glyphs, except for Xe/LuaTeX (with non-TeX fonts) or pLaTeX.
+       // Minted can deal with all encodings.
+       if (buffer().params().use_minted
+               || inner_enc->name() == "utf8-plain"
+               || inner_enc->package() == Encoding::japanese
+               || inner_enc->hasFixedWidth())
+               return 0;
+
+       // We try if there's a singlebyte encoding for the outer
+       // language; if not, fall back to latin1.
+       // Power-users can set inputenc to utf8-plain to bypass this workaround
+       // and provide alternatives in the user-preamble.
+       return (outer_enc->hasFixedWidth()) ?
+                       outer_enc : encodings.fromLyXName("iso8859-1");
+}
+
+
 void InsetListings::latex(otexstream & os, OutputParams const & runparams) const
 {
        string param_string = params().params();
@@ -117,6 +146,60 @@ void InsetListings::latex(otexstream & os, OutputParams const & runparams) const
        // of the listings package (see page 25 of the manual)
        bool const isInline = params().isInline();
        bool const use_minted = buffer().params().use_minted;
+       static regex const reg1("(.*)(basicstyle=\\{)([^\\}]*)(\\\\ttfamily)([^\\}]*)(\\})(.*)");
+       static regex const reg2("(.*)(basicstyle=\\{)([^\\}]*)(\\\\rmfamily)([^\\}]*)(\\})(.*)");
+       static regex const reg3("(.*)(basicstyle=\\{)([^\\}]*)(\\\\sffamily)([^\\}]*)(\\})(.*)");
+       static regex const reg4("(.*)(basicstyle=\\{)([^\\}]*)(\\\\(tiny|scriptsize|footnotesize|small|normalsize|large|Large))([^\\}]*)(\\})(.*)");
+       static regex const reg5("(.*)(fontfamily=)(tt|sf|rm)(.*)");
+       static regex const reg6("(.*)(fontsize=\\{)(\\\\(tiny|scriptsize|footnotesize|small|normalsize|large|Large))(\\})(.*)");
+       if (use_minted) {
+               // If params have been entered with "listings", and then the user switched to "minted",
+               // we have params that need to be translated.
+               // FIXME: We should use a backend-abstract syntax in listings params instead!
+               // Substitute fontstyle option
+               smatch sub;
+               if (regex_match(param_string, sub, reg1))
+                       param_string = sub.str(1) + "fontfamily=tt," + sub.str(2) + sub.str(3)
+                                       + sub.str(5) + sub.str(6) + sub.str(7);
+               if (regex_match(param_string, sub, reg2))
+                       param_string = sub.str(1) + "fontfamily=rm," + sub.str(2) + sub.str(3)
+                                       + sub.str(5) + sub.str(6) + sub.str(7);
+               if (regex_match(param_string, sub, reg3))
+                       param_string = sub.str(1) + "fontfamily=sf," + sub.str(2) + sub.str(3)
+                                       + sub.str(5) + sub.str(6) + sub.str(7);
+               // as well as fontsize option
+               if (regex_match(param_string, sub, reg4))
+                       param_string = sub.str(1) + "fontsize={" + sub.str(4) + sub.str(3) + sub.str(7) + sub.str(8);
+       } else {
+               // And the same vice versa
+               // Substitute fontstyle option
+               smatch sub;
+               string basicstyle;
+               if (regex_match(param_string, sub, reg5)) {
+                       basicstyle = "\\" + sub.str(3) + "family";
+                       param_string = sub.str(1) + sub.str(4);
+               }
+               // as well as fontsize option
+               if (regex_match(param_string, sub, reg6)) {
+                       basicstyle += sub.str(3);
+                       param_string = sub.str(1) + sub.str(6);
+               }
+               if (!basicstyle.empty())
+                       param_string = rtrim(param_string, ",") + ",basicstyle={" + basicstyle + "}";
+       }
+       if (runparams.use_polyglossia && runparams.local_font->isRightToLeft()) {
+               // We need to use the *latin switches (#11554)
+               smatch sub;
+               if (regex_match(param_string, sub, reg1))
+                       param_string = sub.str(1) + sub.str(2) + sub.str(3) + sub.str(4)
+                                       + "latin"  + sub.str(5) + sub.str(6) + sub.str(7);
+               if (regex_match(param_string, sub, reg2))
+                       param_string = sub.str(1) + sub.str(2) + sub.str(3) + sub.str(4)
+                                       + "latin"  + sub.str(5) + sub.str(6) + sub.str(7);
+               if (regex_match(param_string, sub, reg3))
+                       param_string = sub.str(1) + sub.str(2) + sub.str(3) + sub.str(4)
+                                       + "latin"  + sub.str(5) + sub.str(6) + sub.str(7);
+       }
        string minted_language;
        string float_placement;
        bool const isfloat = params().isFloat();
@@ -129,18 +212,30 @@ void InsetListings::latex(otexstream & os, OutputParams const & runparams) const
                        if (prefixIs(opts[i], "float")) {
                                if (prefixIs(opts[i], "float="))
                                        float_placement = opts[i].substr(6);
-                               opts.erase(opts.begin() + i--);
+                               opts.erase(opts.begin() + int(i--));
                        }
                        else if (prefixIs(opts[i], "language=")) {
                                minted_language = opts[i].substr(9);
-                               opts.erase(opts.begin() + i--);
+                               opts.erase(opts.begin() + int(i--));
                        }
                }
                param_string = getStringFromVector(opts, ",");
        }
        // Minted needs a language specification
-       if (minted_language.empty())
-               minted_language = "TeX";
+       if (minted_language.empty()) {
+               // If a language has been set globally, use that,
+               // otherwise use TeX by default
+               string const & blp = buffer().params().listings_params;
+               size_t start = blp.find("language=");
+               if (start != string::npos) {
+                       start += strlen("language=");
+                       size_t len = blp.find(",", start);
+                       if (len != string::npos)
+                               len -= start;
+                       minted_language = blp.substr(start, len);
+               } else
+                       minted_language = "TeX";
+       }
 
        // get the paragraphs. We can not output them directly to given odocstream
        // because we can not yet determine the delimiter character of \lstinline
@@ -151,35 +246,30 @@ void InsetListings::latex(otexstream & os, OutputParams const & runparams) const
 
        bool encoding_switched = false;
        Encoding const * const save_enc = runparams.encoding;
-       // The listings package cannot deal with multi-byte-encoded
-       // glyphs, except if full-unicode aware backends
-       // such as XeTeX or LuaTeX are used, and with pLaTeX.
-       bool const multibyte_possible = use_minted || runparams.isFullUnicode()
-           || (buffer().params().encoding().package() == Encoding::japanese
-               && runparams.encoding->package() == Encoding::japanese);
 
-       if (!multibyte_possible && !runparams.encoding->hasFixedWidth()) {
+       Encoding const * const outer_encoding =
+               (runparams.local_font != 0) ?
+                       runparams.local_font->language()->encoding()
+                       : buffer().params().language->encoding();
+       Encoding const * fixedlstenc = forcedEncoding(runparams.encoding, outer_encoding);
+       if (fixedlstenc) {
                // We need to switch to a singlebyte encoding, due to
                // the restrictions of the listings package (see above).
                // This needs to be consistent with
                // LaTeXFeatures::getTClassI18nPreamble().
-               Language const * const outer_language =
-                       (runparams.local_font != 0) ?
-                               runparams.local_font->language()
-                               : buffer().params().language;
-               // We try if there's a singlebyte encoding for the current
-               // language; if not, fall back to latin1.
-               Encoding const * const lstenc =
-                       (outer_language->encoding()->hasFixedWidth()) ?
-                               outer_language->encoding()
-                               : encodings.fromLyXName("iso8859-1");
-               switchEncoding(os.os(), buffer().params(), runparams, *lstenc, true);
-               runparams.encoding = lstenc;
+               // We need to put this into a group in order to prevent encoding leaks
+               // (happens with cprotect).
+               os << "\\bgroup";
+               switchEncoding(os.os(), buffer().params(), runparams, *fixedlstenc, true);
+               runparams.encoding = fixedlstenc;
                encoding_switched = true;
        }
 
+       bool const captionfirst = !isfloat && par->isInset(0)
+                               && par->getInset(0)->lyxCode() == CAPTION_CODE;
+
        while (par != end) {
-               pos_type siz = par->size();
+               pos_type const siz = par->size();
                bool captionline = false;
                for (pos_type i = 0; i < siz; ++i) {
                        if (i == 0 && par->isInset(i) && i + 1 == siz)
@@ -223,9 +313,11 @@ void InsetListings::latex(otexstream & os, OutputParams const & runparams) const
                        }
                }
                ++par;
-               // for the inline case, if there are multiple paragraphs
+               // Add new line between paragraphs in displayed listings.
+               // Exception: merged paragraphs in change tracking mode.
+               // Also, for the inline case, if there are multiple paragraphs
                // they are simply joined. Otherwise, expect latex errors.
-               if (par != end && !isInline && !captionline)
+               if (par != end && !isInline && !captionline && !par->parEndChange().deleted())
                        code += "\n";
        }
        if (isInline) {
@@ -256,7 +348,7 @@ void InsetListings::latex(otexstream & os, OutputParams const & runparams) const
                        os << "\\mintinline";
                        if (!param_string.empty())
                                os << "[" << from_utf8(param_string) << "]";
-                       os << "{" << minted_language << "}";
+                       os << "{" << ascii_lowercase(minted_language) << "}";
                } else {
                        os << "\\lstinline";
                        if (!param_string.empty())
@@ -268,23 +360,29 @@ void InsetListings::latex(otexstream & os, OutputParams const & runparams) const
                }
                os << delim << code << delim;
        } else if (use_minted) {
+               OutputParams rp = runparams;
+               rp.moving_arg = true;
+               TexString caption = getCaption(rp);
                if (isfloat) {
                        os << breakln << "\\begin{listing}";
                        if (!float_placement.empty())
                                os << '[' << float_placement << "]";
+               } else if (captionfirst && !caption.str.empty()) {
+                       os << breakln << "\\lyxmintcaption[t]{"
+                          << move(caption) << "}\n";
                }
                os << breakln << "\\begin{minted}";
                if (!param_string.empty())
                        os << "[" << param_string << "]";
-               os << "{" << minted_language << "}\n"
+               os << "{" << ascii_lowercase(minted_language) << "}\n"
                   << code << breakln << "\\end{minted}\n";
                if (isfloat) {
-                       OutputParams rp = runparams;
-                       rp.moving_arg = true;
-                       TexString caption = getCaption(rp);
                        if (!caption.str.empty())
                                os << "\\caption{" << move(caption) << "}\n";
                        os << "\\end{listing}\n";
+               } else if (!captionfirst && !caption.str.empty()) {
+                       os << breakln << "\\lyxmintcaption[b]{"
+                          << move(caption) << "}";
                }
        } else {
                OutputParams rp = runparams;
@@ -309,14 +407,16 @@ void InsetListings::latex(otexstream & os, OutputParams const & runparams) const
 
        if (encoding_switched){
                // Switch back
-               switchEncoding(os.os(), buffer().params(), runparams, *save_enc, true);
+               switchEncoding(os.os(), buffer().params(),
+                              runparams, *save_enc, true, true);
+               os << "\\egroup" << breakln;
                runparams.encoding = save_enc;
        }
 
        if (!uncodable.empty() && !runparams.silent) {
                // issue a warning about omitted characters
                // FIXME: should be passed to the error dialog
-               if (!multibyte_possible && !runparams.encoding->hasFixedWidth())
+               if (fixedlstenc)
                        frontend::Alert::warning(_("Uncodable characters in listings inset"),
                                bformat(_("The following characters in one of the program listings are\n"
                                          "not representable in the current encoding and have been omitted:\n%1$s.\n"
@@ -334,21 +434,21 @@ void InsetListings::latex(otexstream & os, OutputParams const & runparams) const
 }
 
 
-docstring InsetListings::xhtml(XHTMLStream & os, OutputParams const & rp) const
+docstring InsetListings::xhtml(XMLStream & os, OutputParams const & rp) const
 {
        odocstringstream ods;
-       XHTMLStream out(ods);
+       XMLStream out(ods);
 
        bool const isInline = params().isInline();
        if (isInline)
-               out << html::CompTag("br");
+               out << xml::CompTag("br");
        else {
-               out << html::StartTag("div", "class='float-listings'");
+               out << xml::StartTag("div", "class='float-listings'");
                docstring caption = getCaptionHTML(rp);
                if (!caption.empty())
-                       out << html::StartTag("div", "class='listings-caption'")
-                           << XHTMLStream::ESCAPE_NONE
-                           << caption << html::EndTag("div");
+                       out << xml::StartTag("div", "class='listings-caption'")
+                           << XMLStream::ESCAPE_NONE
+                           << caption << xml::EndTag("div");
        }
 
        InsetLayout const & il = getLayout();
@@ -358,21 +458,21 @@ docstring InsetListings::xhtml(XHTMLStream & os, OutputParams const & rp) const
        if (!lang.empty())
                attr += " " + lang;
        attr += "'";
-       out << html::StartTag(tag, attr);
+       out << xml::StartTag(tag, attr);
        OutputParams newrp = rp;
        newrp.html_disable_captions = true;
        // We don't want to convert dashes here. That's the only conversion we
        // do for XHTML, so this is safe.
        newrp.pass_thru = true;
        docstring def = InsetText::insetAsXHTML(out, newrp, InsetText::JustText);
-       out << html::EndTag(tag);
+       out << xml::EndTag(tag);
 
        if (isInline) {
-               out << html::CompTag("br");
+               out << xml::CompTag("br");
                // escaping will already have been done
-               os << XHTMLStream::ESCAPE_NONE << ods.str();
+               os << XMLStream::ESCAPE_NONE << ods.str();
        } else {
-               out << html::EndTag("div");
+               out << xml::EndTag("div");
                // In this case, this needs to be deferred, but we'll put it
                // before anything the text itself deferred.
                def = ods.str() + '\n' + def;
@@ -381,6 +481,80 @@ docstring InsetListings::xhtml(XHTMLStream & os, OutputParams const & rp) const
 }
 
 
+void InsetListings::docbook(XMLStream & xs, OutputParams const & rp) const
+{
+       InsetLayout const & il = getLayout();
+       bool isInline = params().isInline();
+
+       if (!isInline && !xs.isLastTagCR())
+               xs << xml::CR();
+
+       // In case of caption, the code must be wrapped, for instance in a figure. Also detect if there is a label.
+       // http://www.sagehill.net/docbookxsl/ProgramListings.html
+       // TODO: parts of this code could be merged with InsetFloat and findLabelInParagraph.
+       InsetCaption const * caption = getCaptionInset();
+       if (caption) {
+               InsetLabel const * label = getLabelInset();
+
+               // Ensure that the label will not be output a second time as an anchor.
+               OutputParams rpNoLabel = rp;
+               if (label)
+                       rpNoLabel.docbook_anchors_to_ignore.emplace(label->screenLabel());
+
+               // Prepare the right set of attributes, including the label.
+               docstring attr = from_ascii("type='listing'");
+               if (label)
+                       attr += " xml:id=\"" + xml::cleanID(label->screenLabel()) + "\"";
+
+               // Finally, generate the wrapper (including the label).
+               xs << xml::StartTag("figure", attr);
+               xs << xml::CR();
+               xs << xml::StartTag("title");
+               xs << XMLStream::ESCAPE_NONE << getCaptionDocBook(rpNoLabel);
+               xs << xml::EndTag("title");
+               xs << xml::CR();
+       }
+
+       // Forge the attributes.
+       std::string attrs;
+       if (!il.docbookattr().empty())
+               attrs += " role=\"" + il.docbookattr() + "\"";
+       std::string const lang = params().getParamValue("language");
+       if (!lang.empty())
+               attrs += " language=\"" + lang + "\"";
+
+       // Determine the tag to use. Use the layout-defined value if outside a paragraph.
+       std::string tag = il.docbooktag();
+       if (isInline)
+               tag = "code";
+
+       // Start the listing.
+       xs << xml::StartTag(tag, attrs);
+       xs.startDivision(false);
+
+       // Deal with the content of the listing.
+       OutputParams newrp = rp;
+       newrp.pass_thru = true;
+       newrp.docbook_make_pars = false;
+       newrp.par_begin = 0;
+       newrp.par_end = text().paragraphs().size();
+       newrp.docbook_in_listing = true;
+
+       docbookParagraphs(text(), buffer(), xs, newrp);
+
+       // Done with the listing.
+       xs.endDivision();
+       xs << xml::EndTag(tag);
+       if (!isInline)
+               xs << xml::CR();
+
+       if (caption) {
+               xs << xml::EndTag("figure");
+               xs << xml::CR();
+       }
+}
+
+
 string InsetListings::contextMenuName() const
 {
        return "context-listings";
@@ -418,12 +592,12 @@ bool InsetListings::getStatus(Cursor & cur, FuncRequest const & cmd,
                        return true;
                case LFUN_CAPTION_INSERT: {
                        // the inset outputs at most one caption
-                       if (params().isInline() || !params().isFloat()
-                                               || getCaptionInset()) {
+                       if (params().isInline() || getCaptionInset()) {
                                status.setEnabled(false);
                                return true;
                        }
                }
+               // fall through
                default:
                        return InsetCaptionable::getStatus(cur, cmd, status);
        }
@@ -433,23 +607,31 @@ bool InsetListings::getStatus(Cursor & cur, FuncRequest const & cmd,
 docstring const InsetListings::buttonLabel(BufferView const & bv) const
 {
        // FIXME UNICODE
-       if (decoration() == InsetLayout::CLASSIC)
-               return isOpen(bv) ? _("Listing") : getNewLabel(_("Listing"));
-       else
-               return getNewLabel(_("Listing"));
+       docstring const locked = tempfile_ ? docstring(1, 0x1F512) : docstring();
+       if (decoration() == InsetDecoration::CLASSIC)
+               return locked + (isOpen(bv) ? _("Listing") : getNewLabel(_("Listing")));
+       return locked + getNewLabel(_("Listing"));
 }
 
 
 void InsetListings::validate(LaTeXFeatures & features) const
 {
-       if (buffer().params().use_minted)
-               features.require("minted");
-       else
-               features.require("listings");
        features.useInsetLayout(getLayout());
        string param_string = params().params();
-       if (param_string.find("\\color") != string::npos)
-               features.require("color");
+       if (buffer().params().use_minted) {
+               features.require("minted");
+               OutputParams rp = features.runparams();
+               if (!params().isFloat() && !getCaption(rp).str.empty())
+                       features.require("lyxmintcaption");
+               if (features.usePolyglossia() && features.hasRTLLanguage())
+                       // minted loads color, but color must be loaded before bidi
+                       // (i.e., polyglossia)
+                       features.require("color");
+       } else {
+               features.require("listings");
+               if (contains(param_string, "\\color"))
+                       features.require("color");
+       }
        InsetCaptionable::validate(features);
 }
 
@@ -491,10 +673,17 @@ TexString InsetListings::getCaption(OutputParams const & runparams) const
        // NOTE that } is not allowed in blah2.
        regex const reg("(.*)\\\\label\\{(.*?)\\}(.*)");
        string const new_cap("$1$3},label={$2");
+       // Remove potential \protect'ion of \label.
+       docstring capstr = subst(cap.str, from_ascii("\\protect\\label"),
+                                from_ascii("\\label"));
        // TexString validity: the substitution preserves the number of newlines.
        // Moreover we assume that $2 does not contain newlines, so that the texrow
        // information remains accurate.
-       cap.str = from_utf8(regex_replace(to_utf8(cap.str), reg, new_cap));
+       // Replace '\n' with an improbable character from Private Use Area-A
+       // and then return to '\n' after the regex replacement.
+       capstr = subst(capstr, char_type('\n'), 0xffffd);
+       cap.str = subst(from_utf8(regex_replace(to_utf8(capstr), reg, new_cap)),
+                       0xffffd, char_type('\n'));
        return cap;
 }