#include "BufferView.h"
#include "ColorSet.h"
#include "Cursor.h"
+#include "CutAndPaste.h"
#include "DispatchResult.h"
#include "Encoding.h"
#include "ErrorList.h"
#include "LaTeX.h"
#include "LaTeXFeatures.h"
#include "Lexer.h"
+#include "LyX.h"
#include "output_latex.h"
#include "output_xhtml.h"
#include "xml.h"
using namespace std;
using namespace lyx::support;
+// Uncomment to enable InsetIndex-specific debugging mode: the tree for the index will be printed to std::cout.
+// #define LYX_INSET_INDEX_DEBUG
+
namespace lyx {
namespace {
{
OutputParams runparams(runparams_in);
runparams.inIndexEntry = true;
+ if (runparams_in.postpone_fragile_stuff)
+ // This is not needed and would impact sorting
+ runparams.moving_arg = false;
otexstringstream os;
getSortkey(os, runparams);
os << "@";
os << ourlatex.str();
- getSubentries(os, runparams);
+ getSubentries(os, runparams, ourlatex.str());
if (hasSeeRef()) {
os << "|";
os << insetindexpagerangetranslator_latex().find(params_.range);
getSeeRefs(os, runparams);
+ } else if (!params_.pagefmt.empty() && params_.pagefmt != "default") {
+ os << "|";
+ os << insetindexpagerangetranslator_latex().find(params_.range);
+ os << from_utf8(params_.pagefmt);
+ } else if (params_.range != InsetIndexParams::PageRange::None) {
+ os << "|";
+ os << insetindexpagerangetranslator_latex().find(params_.range);
}
} else {
// We check whether we need a sort key.
odocstringstream subentries;
otexstream otsub(subentries);
- getSubentries(otsub, runparams);
+ getSubentries(otsub, runparams, ourlatex.str());
if (subentries.str().empty()) {
// Separate the entries and subentries, i.e., split on "!".
// This goes wrong on an escaped "!", but as the escape
vector<docstring>::const_iterator it2 = levels_plain.begin();
bool first = true;
for (; it != end; ++it) {
+ if ((*it).empty()) {
+ emptySubentriesWarning(ourlatex.str());
+ if (it2 < levels_plain.end())
+ ++it2;
+ continue;
+ }
// The separator needs to be put back when
// writing the levels, except for the first level
if (!first)
os << "|"
<< insetindexpagerangetranslator_latex().find(params_.range)
<< cmd;
+ } else if (params_.range != InsetIndexParams::PageRange::None) {
+ os << "|";
+ os << insetindexpagerangetranslator_latex().find(params_.range);
}
}
os << '}';
// Handle primary, secondary, and tertiary terms (entries, subentries, and subsubentries, for LaTeX).
vector<docstring> terms;
- if (const vector<docstring> potential_terms = getSubentriesAsText(runparams); !potential_terms.empty()) {
+ const vector<docstring> potential_terms = getSubentriesAsText(runparams);
+ if (!potential_terms.empty()) {
terms = potential_terms;
// The main term is not present in the vector, as it's not a subentry. The main index term is inserted raw in
// the index inset. Considering that the user either uses the new or the legacy mechanism, the main term is the
}
// Handle ranges. Happily, in the raw LaTeX mode, (| and |) can only be at the end of the string!
- bool hasInsetRange = params_.range != InsetIndexParams::PageRange::None;
- bool hasStartRange = params_.range == InsetIndexParams::PageRange::Start ||
+ const bool hasInsetRange = params_.range != InsetIndexParams::PageRange::None;
+ const bool hasStartRange = params_.range == InsetIndexParams::PageRange::Start ||
latexString.find(from_ascii("|(")) != lyx::docstring::npos;
- bool hasEndRange = params_.range == InsetIndexParams::PageRange::End ||
+ const bool hasEndRange = params_.range == InsetIndexParams::PageRange::End ||
latexString.find(from_ascii("|)")) != lyx::docstring::npos;
if (hasInsetRange) {
xs << XMLStream::ESCAPE_NONE << (from_utf8("<!-- Output Error: ") + error + from_utf8(" -->\n"));
}
- // Write all of this down.
+ // Write all of this down.
if (terms.empty() && !hasEndRange) {
docstring error = from_utf8("No index term found! Complete entry: \"") + latexString + from_utf8("\"");
LYXERR0(error);
params_.index = from_utf8(cmd.getArg(1));
break;
}
+ if (cmd.getArg(0) == "changeparam") {
+ string const p = cmd.getArg(1);
+ string const v = cmd.getArg(2);
+ cur.recordUndoInset(this);
+ if (p == "range")
+ params_.range = insetindexpagerangetranslator().find(v);
+ if (p == "pagefmt") {
+ if (v == "default" || v == "textbf"
+ || v == "textit" || v == "emph")
+ params_.pagefmt = v;
+ else
+ lyx::dispatch(FuncRequest(LFUN_INSET_SETTINGS, "index"));
+ }
+ break;
+ }
InsetIndexParams params;
InsetIndex::string2params(to_utf8(cmd.argument()), params);
cur.recordUndoInset(this);
cur.bv().updateDialog("index", params2string(params_));
break;
+ case LFUN_PARAGRAPH_BREAK: {
+ // Since this inset in single-par anyway, let's use
+ // return to enter subentries
+ FuncRequest fr(LFUN_INDEXMACRO_INSERT, "subentry");
+ lyx::dispatch(fr);
+ break;
+ }
+
+ case LFUN_INSET_INSERT_COPY: {
+ Cursor & bvcur = cur.bv().cursor();
+ if (cmd.origin() == FuncRequest::TOC && bvcur.inTexted()) {
+ cap::copyInsetToTemp(cur, clone());
+ cap::pasteFromTemp(bvcur, bvcur.buffer()->errorList("Paste"));
+ } else
+ cur.undispatched();
+ break;
+ }
+
default:
InsetCollapsible::doDispatch(cur, cmd);
break;
from_utf8(cmd.getArg(1)) == params_.index);
return true;
}
+ if (cmd.getArg(0) == "changeparam") {
+ string const p = cmd.getArg(1);
+ string const v = cmd.getArg(2);
+ if (p == "range") {
+ flag.setEnabled(v == "none" || v == "start" || v == "end");
+ flag.setOnOff(params_.range == insetindexpagerangetranslator().find(v));
+ }
+ if (p == "pagefmt") {
+ flag.setEnabled(!v.empty());
+ if (params_.pagefmt == "default" || params_.pagefmt == "textbf"
+ || params_.pagefmt == "textit" || params_.pagefmt == "emph")
+ flag.setOnOff(params_.pagefmt == v);
+ else
+ flag.setOnOff(v == "custom");
+ }
+ return true;
+ }
return InsetCollapsible::getStatus(cur, cmd, flag);
case LFUN_INSET_DIALOG_UPDATE: {
flag.setEnabled(realbuffer.params().use_indices);
return true;
}
-
+
+ case LFUN_INSET_INSERT_COPY:
+ // This can only be invoked by ToC widget
+ flag.setEnabled(cmd.origin() == FuncRequest::TOC
+ && cur.bv().cursor().inset().insetAllowed(lyxCode()));
+ return true;
+
+ case LFUN_PARAGRAPH_BREAK:
+ return macrosPossible("subentry");
+
case LFUN_INDEXMACRO_INSERT:
return macrosPossible(cmd.getArg(0));
+ case LFUN_INDEX_TAG_ALL:
+ return true;
+
default:
return InsetCollapsible::getStatus(cur, cmd, flag);
}
}
-void InsetIndex::getSubentries(otexstream & os, OutputParams const & runparams) const
+void InsetIndex::emptySubentriesWarning(docstring const & mainentry) const
+{
+ // Empty subentries crash makeindex. So warn and ignore this.
+ TeXErrors terr;
+ ErrorList & errorList = buffer().errorList("Export");
+ docstring const s = bformat(_("There is an empty index subentry in the entry '%1$s'.\n"
+ "It will be ignored in the output."), mainentry);
+ Paragraph const & par = buffer().paragraphs().front();
+ errorList.push_back(ErrorItem(_("Empty index subentry!"), s,
+ {par.id(), 0}, {par.id(), -1}));
+ buffer().bufferErrors(terr, errorList);
+}
+
+
+void InsetIndex::getSubentries(otexstream & os, OutputParams const & runparams,
+ docstring const & mainentry) const
{
Paragraph const & par = paragraphs().front();
InsetList::const_iterator it = par.insetList().begin();
if (inset.lyxCode() == INDEXMACRO_CODE) {
InsetIndexMacro const & iim =
static_cast<InsetIndexMacro const &>(inset);
- if (iim.params().type == InsetIndexMacroParams::Subindex) {
+ if (iim.params().type == InsetIndexMacroParams::Subentry) {
+ if (iim.hasNoContent()) {
+ emptySubentriesWarning(mainentry);
+ continue;
+ }
++i;
if (i > 2)
return;
}
-std::vector<docstring> InsetIndex::getSubentriesAsText(OutputParams const & runparams) const
+std::vector<docstring> InsetIndex::getSubentriesAsText(OutputParams const & runparams,
+ bool const asLabel) const
{
std::vector<docstring> subentries;
if (inset.lyxCode() == INDEXMACRO_CODE) {
InsetIndexMacro const & iim =
static_cast<InsetIndexMacro const &>(inset);
- if (iim.params().type == InsetIndexMacroParams::Subindex) {
+ if (iim.params().type == InsetIndexMacroParams::Subentry) {
++i;
if (i > 2)
break;
-
- otexstringstream os;
- iim.getLatex(os, runparams);
- subentries.emplace_back(os.str());
+ if (asLabel) {
+ docstring const l;
+ docstring const sl = iim.getNewLabel(l);
+ subentries.emplace_back(sl);
+ } else {
+ otexstringstream os;
+ iim.getLatex(os, runparams);
+ subentries.emplace_back(os.str());
+ }
}
}
}
}
-docstring InsetIndex::getSeeAsText(OutputParams const & runparams) const
+docstring InsetIndex::getSeeAsText(OutputParams const & runparams,
+ bool const asLabel) const
{
Paragraph const & par = paragraphs().front();
InsetList::const_iterator it = par.insetList().begin();
InsetIndexMacro const & iim =
static_cast<InsetIndexMacro const &>(inset);
if (iim.params().type == InsetIndexMacroParams::See) {
- otexstringstream os;
- iim.getLatex(os, runparams);
- return os.str();
+ if (asLabel) {
+ docstring const l;
+ return iim.getNewLabel(l);
+ } else {
+ otexstringstream os;
+ iim.getLatex(os, runparams);
+ return os.str();
+ }
}
}
}
}
-std::vector<docstring> InsetIndex::getSeeAlsoesAsText(OutputParams const & runparams) const
+std::vector<docstring> InsetIndex::getSeeAlsoesAsText(OutputParams const & runparams,
+ bool const asLabel) const
{
std::vector<docstring> seeAlsoes;
InsetIndexMacro const & iim =
static_cast<InsetIndexMacro const &>(inset);
if (iim.params().type == InsetIndexMacroParams::Seealso) {
- otexstringstream os;
- iim.getLatex(os, runparams);
- seeAlsoes.emplace_back(os.str());
+ if (asLabel) {
+ docstring const l;
+ seeAlsoes.emplace_back(iim.getNewLabel(l));
+ } else {
+ otexstringstream os;
+ iim.getLatex(os, runparams);
+ seeAlsoes.emplace_back(os.str());
+ }
}
}
}
bool InsetIndex::hasSubentries() const
{
- return hasInsetWithCode(this, INDEXMACRO_CODE, {InsetIndexMacroParams::Subindex});
+ return hasInsetWithCode(this, INDEXMACRO_CODE, {InsetIndexMacroParams::Subentry});
}
bool InsetIndex::macrosPossible(string const type) const
{
if (type != "see" && type != "seealso"
- && type != "sortkey" && type != "subindex")
+ && type != "sortkey" && type != "subentry")
return false;
Paragraph const & par = paragraphs().front();
&& (iim.params().type == InsetIndexMacroParams::See
|| iim.params().type == InsetIndexMacroParams::Seealso))
return false;
- if (type == "subindex"
- && iim.params().type == InsetIndexMacroParams::Subindex) {
+ if (type == "subentry"
+ && iim.params().type == InsetIndexMacroParams::Subentry) {
++subidxs;
if (subidxs > 1)
return false;
docstring res;
if (!il.contentaslabel() || geometry(bv) != ButtonOnly)
res = label;
- else
+ else {
res = getNewLabel(label);
+ OutputParams const rp(0);
+ vector<docstring> sublbls = getSubentriesAsText(rp, true);
+ for (auto const & sublbl : sublbls) {
+ res += " " + docstring(1, char_type(0x2023));// TRIANGULAR BULLET
+ res += " " + sublbl;
+ }
+ docstring see = getSeeAsText(rp, true);
+ if (see.empty() && !getSeeAlsoesAsText(rp, true).empty())
+ see = getSeeAlsoesAsText(rp, true).front();
+ if (!see.empty()) {
+ res += " " + docstring(1, char_type(0x261e));// WHITE RIGHT POINTING INDEX
+ res += " " + see;
+ }
+ }
if (!insetindexpagerangetranslator_latex().find(params_.range).empty())
res += " " + from_ascii(insetindexpagerangetranslator_latex().find(params_.range));
return res;
DocIterator pit = cpit;
pit.push_back(CursorSlice(const_cast<InsetIndex &>(*this)));
docstring str;
+ InsetLayout const & il = getLayout();
+ docstring label = translateIfPossible(il.labelstring());
+ if (!il.contentaslabel())
+ str = label;
+ else {
+ str = getNewLabel(label);
+ OutputParams const rp(0);
+ vector<docstring> sublbls = getSubentriesAsText(rp, true);
+ for (auto const & sublbl : sublbls) {
+ str += " " + docstring(1, char_type(0x2023));// TRIANGULAR BULLET
+ str += " " + sublbl;
+ }
+ docstring see = getSeeAsText(rp, true);
+ if (see.empty() && !getSeeAlsoesAsText(rp, true).empty())
+ see = getSeeAlsoesAsText(rp, true).front();
+ if (!see.empty()) {
+ str += " " + docstring(1, char_type(0x261e));// WHITE RIGHT POINTING INDEX
+ str += " " + see;
+ }
+ }
string type = "index";
if (buffer().masterBuffer()->params().use_indices)
type += ":" + to_utf8(params_.index);
- // this is unlikely to be terribly long
- text().forOutliner(str, INT_MAX);
TocBuilder & b = backend.builder(type);
b.pushItem(pit, str, output_active);
// Proceed with the rest of the inset.
private:
bool isModern()
{
+#ifdef LYX_INSET_INDEX_DEBUG
std::cout << to_utf8(entry_) << std::endl;
+#endif // LYX_INSET_INDEX_DEBUG
// If a modern parameter is present, this is definitely a modern index inset. Similarly, if it contains the
// usual LaTeX symbols (!|@), then it is definitely a legacy index inset. Otherwise, if it has features of
if (lhs.terms_.empty())
return false;
- for (int i = 0; i < min(rhs.terms_.size(), lhs.terms_.size()); ++i) {
+ for (unsigned i = 0; i < min(rhs.terms_.size(), lhs.terms_.size()); ++i) {
int comp = compare_no_case(lhs.terms_[i], rhs.terms_[i]);
if (comp != 0)
return comp < 0;
void insertIntoNode(const IndexEntry& entry, IndexNode* node, unsigned depth = 0)
{
+ // Do not insert empty entries.
+ if (entry.terms().empty())
+ return;
+
// depth == 0 is for the root, not yet the index, hence the increase when going to vector size.
for (IndexNode* child : node->children) {
if (entry.terms()[depth] == termAtLevel(child, depth)) {
if (depth + 1 == entry.terms().size()) { // == child.entries.begin()->terms().size()
// All term entries match: it's an entry.
- child->entries.emplace_back(entry);
+ if (!entry.terms()[depth].empty())
+ child->entries.emplace_back(entry);
return;
} else {
insertIntoNode(entry, child, depth + 1);
return index_root;
}
-void outputIndexPage(XMLStream & xs, const IndexNode* root_node, unsigned depth = 0)
+void outputIndexPage(XMLStream & xs, const IndexNode* root_node, unsigned depth = 0) // NOLINT(misc-no-recursion)
{
LASSERT(root_node->entries.size() + root_node->children.size() > 0, return);
xs << xml::StartTag("li", "class='" + generateCssClassAtDepth(depth) + "'");
xs << xml::CR();
- xs << XMLStream::ESCAPE_NONE << termAtLevel(root_node, depth);
+ xs << termAtLevel(root_node, depth);
// By tree assumption, all the entries at this node have the same set of terms.
if (!root_node->entries.empty()) {
- xs << XMLStream::ESCAPE_NONE << " — ";
+ xs << XMLStream::ESCAPE_NONE << " — "; // Em dash, i.e. long (---).
unsigned entry_number = 1;
- for (unsigned i = 0; i < root_node->entries.size(); ++i) {
- const IndexEntry &entry = root_node->entries[i];
-
+ auto writeLinkToEntry = [&xs](const IndexEntry &entry, unsigned entry_number) {
std::string const link_attr = "href='#" + entry.inset()->paragraphs()[0].magicLabel() + "'";
xs << xml::StartTag("a", link_attr);
xs << from_ascii(std::to_string(entry_number));
xs << xml::EndTag("a");
+ };
+
+ for (unsigned i = 0; i < root_node->entries.size(); ++i) {
+ const IndexEntry &entry = root_node->entries[i];
+
+ switch (entry.inset()->params().range) {
+ case InsetIndexParams::PageRange::None:
+ writeLinkToEntry(entry, entry_number);
+ break;
+ case InsetIndexParams::PageRange::Start: {
+ // Try to find the end of the range, if it is just after. Otherwise, the output will be slightly
+ // scrambled, but understandable. Doing better would mean implementing more of the indexing logic here
+ // and more complex indexing here (skipping the end is not just incrementing i). Worst case output:
+ // 1--, 2, --3
+ const bool nextEntryIsEnd = i + 1 < root_node->entries.size() &&
+ root_node->entries[i + 1].inset()->params().range ==
+ InsetIndexParams::PageRange::End;
+ // No need to check if both entries are for the same terms: they are in the same IndexNode.
+
+ writeLinkToEntry(entry, entry_number);
+ xs << XMLStream::ESCAPE_NONE << " – "; // En dash, i.e. semi-long (--).
+
+ if (nextEntryIsEnd) {
+ // Skip the next entry in the loop, write it right now, after the dash.
+ entry_number += 1;
+ i += 1;
+ writeLinkToEntry(root_node->entries[i], entry_number);
+ }
+ }
+ break;
+ case InsetIndexParams::PageRange::End:
+ // This range end was not caught by the range start, do it now to avoid losing content.
+ xs << XMLStream::ESCAPE_NONE << " – "; // En dash, i.e. semi-long (--).
+ writeLinkToEntry(root_node->entries[i], entry_number);
+ }
if (i < root_node->entries.size() - 1) {
xs << ", ";
}
entry_number += 1;
}
+ xs << xml::CR();
}
if (!root_node->entries.empty() && !root_node->children.empty()) {
xs << xml::CR();
}
-// Only useful for debugging.
+#ifdef LYX_INSET_INDEX_DEBUG
void printTree(const IndexNode* root_node, unsigned depth = 0)
{
static const std::string pattern = " ";
printTree(node, depth + 1);
}
}
+#endif // LYX_INSET_INDEX_DEBUG
}
return docstring();
const IndexNode* index_root = buildIndexTree(entries);
-#if 0
+#ifdef LYX_INSET_INDEX_DEBUG
printTree(index_root);
#endif