]> git.lyx.org Git - lyx.git/blob - src/insets/InsetRef.cpp
Hint to deleted included file in ct output (#11809)
[lyx.git] / src / insets / InsetRef.cpp
1 /**
2  * \file InsetRef.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author José Matos
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10 #include <config.h>
11
12 #include "InsetRef.h"
13
14 #include "Buffer.h"
15 #include "BufferParams.h"
16 #include "Cursor.h"
17 #include "DispatchResult.h"
18 #include "FuncStatus.h"
19 #include "InsetLabel.h"
20 #include "Language.h"
21 #include "LaTeXFeatures.h"
22 #include "LyX.h"
23 #include "output_xhtml.h"
24 #include "Paragraph.h"
25 #include "ParIterator.h"
26 #include "xml.h"
27 #include "texstream.h"
28 #include "TocBackend.h"
29
30 #include "support/debug.h"
31 #include "support/docstream.h"
32 #include "support/gettext.h"
33 #include "support/lstrings.h"
34 #include "support/textutils.h"
35
36 using namespace lyx::support;
37 using namespace std;
38
39 namespace lyx {
40
41
42 InsetRef::InsetRef(Buffer * buf, InsetCommandParams const & p)
43         : InsetCommand(buf, p), broken_(false), active_(true)
44 {}
45
46
47 InsetRef::InsetRef(InsetRef const & ir)
48         : InsetCommand(ir), broken_(false), active_(true)
49 {}
50
51
52 bool InsetRef::isCompatibleCommand(string const & s) {
53         //FIXME This is likely not the best way to handle this.
54         //But this stuff is hardcoded elsewhere already.
55         return s == "ref"
56                 || s == "pageref"
57                 || s == "vref"
58                 || s == "vpageref"
59                 || s == "formatted"
60                 || s == "prettyref" // for InsetMathRef FIXME
61                 || s == "eqref"
62                 || s == "nameref"
63                 || s == "labelonly";
64 }
65
66
67 ParamInfo const & InsetRef::findInfo(string const & /* cmdName */)
68 {
69         static ParamInfo param_info_;
70         if (param_info_.empty()) {
71                 param_info_.add("name", ParamInfo::LATEX_OPTIONAL);
72                 param_info_.add("reference", ParamInfo::LATEX_REQUIRED,
73                                 ParamInfo::HANDLING_ESCAPE);
74                 param_info_.add("plural", ParamInfo::LYX_INTERNAL);
75                 param_info_.add("caps", ParamInfo::LYX_INTERNAL);
76                 param_info_.add("noprefix", ParamInfo::LYX_INTERNAL);
77         }
78         return param_info_;
79 }
80
81
82 docstring InsetRef::layoutName() const
83 {
84         return from_ascii("Ref");
85 }
86
87
88 void InsetRef::changeTarget(docstring const & new_label)
89 {
90         // With change tracking, we insert a new ref
91         // and delete the old one
92         if (buffer().masterParams().track_changes) {
93                 InsetCommandParams icp(REF_CODE, "ref");
94                 icp["reference"] = new_label;
95                 string const data = InsetCommand::params2string(icp);
96                 lyx::dispatch(FuncRequest(LFUN_INSET_INSERT, data));
97                 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_FORWARD));
98         } else
99                 setParam("reference", new_label);
100 }
101
102
103
104 void InsetRef::doDispatch(Cursor & cur, FuncRequest & cmd)
105 {
106         string const inset = cmd.getArg(0);
107         string const arg   = cmd.getArg(1);
108         string pstring;
109         if (cmd.action() == LFUN_INSET_MODIFY && inset == "ref") {
110                 if (arg == "toggle-plural")
111                         pstring = "plural";
112                 else if (arg == "toggle-caps")
113                         pstring = "caps";
114                 else if (arg == "toggle-noprefix")
115                         pstring = "noprefix";
116                 else if (arg == "changetarget") {
117                         string const oldtarget = cmd.getArg(2);
118                         string const newtarget = cmd.getArg(3);
119                         if (!oldtarget.empty() && !newtarget.empty()
120                             && getParam("reference") == from_utf8(oldtarget))
121                                 changeTarget(from_utf8(newtarget));
122                         cur.forceBufferUpdate();
123                         return;
124                 }
125         }
126
127         // Ctrl + click: go to label
128         if (cmd.action() == LFUN_MOUSE_RELEASE && cmd.modifier() == ControlModifier) {
129                         lyx::dispatch(FuncRequest(LFUN_BOOKMARK_SAVE, "0"));
130                         lyx::dispatch(FuncRequest(LFUN_LABEL_GOTO, getParam("reference")));
131                         return;
132                 }
133
134         // otherwise not for us
135         if (pstring.empty())
136                 return InsetCommand::doDispatch(cur, cmd);
137
138         bool const isSet = (getParam(pstring) == "true");
139         setParam(pstring, from_ascii(isSet ? "false"  : "true"));
140 }
141
142
143 bool InsetRef::getStatus(Cursor & cur, FuncRequest const & cmd,
144         FuncStatus & status) const
145 {
146         if (cmd.action() != LFUN_INSET_MODIFY)
147                 return InsetCommand::getStatus(cur, cmd, status);
148         if (cmd.getArg(0) != "ref")
149                 return InsetCommand::getStatus(cur, cmd, status);
150
151         string const arg = cmd.getArg(1);
152         string pstring;
153         if (arg == "changetarget")
154                 return true;
155         if (arg == "toggle-plural")
156                 pstring = "plural";
157         else if (arg == "toggle-caps")
158                 pstring = "caps";
159         if (!pstring.empty()) {
160                 status.setEnabled(buffer().params().use_refstyle &&
161                         params().getCmdName() == "formatted");
162                 bool const isSet = (getParam(pstring) == "true");
163                 status.setOnOff(isSet);
164                 return true;
165         }
166         if (arg == "toggle-noprefix") {
167                 status.setEnabled(params().getCmdName() == "labelonly");
168                 bool const isSet = (getParam("noprefix") == "true");
169                 status.setOnOff(isSet);
170                 return true;
171         }
172         // otherwise not for us
173         return InsetCommand::getStatus(cur, cmd, status);
174 }
175
176
177 // the ref argument is the label name we are referencing.
178 // we expect ref to be in the form: pfx:suffix.
179 //
180 // if it isn't, then we can't produce a formatted reference,
181 // so we return "\ref" and put ref into label.
182 //
183 // for refstyle, we return "\pfxcmd", and put suffix into
184 // label and pfx into prefix. this is because refstyle expects
185 // the command: \pfxcmd{suffix}.
186 //
187 // for prettyref, we return "\prettyref" and put ref into label
188 // and pfx into prefix. this is because prettyref uses the whole
189 // label, thus: \prettyref{pfx:suffix}.
190 //
191 docstring InsetRef::getFormattedCmd(docstring const & ref,
192         docstring & label, docstring & prefix, bool use_refstyle,
193         bool use_caps)
194 {
195         static docstring const defcmd = from_ascii("\\ref");
196         static docstring const prtcmd = from_ascii("\\prettyref");
197
198         label = split(ref, prefix, ':');
199
200         // we have to have xxx:xxxxx...
201         if (label.empty()) {
202                 LYXERR0("Label `" << ref << "' contains no `:' separator.");
203                 label = ref;
204                 prefix = from_ascii("");
205                 return defcmd;
206         }
207
208         if (prefix.empty()) {
209                 // we have ":xxxx"
210                 LYXERR0("Label `" << ref << "' contains nothign before `:'.");
211                 label = ref;
212                 return defcmd;
213         }
214
215         if (!use_refstyle) {
216                 // \prettyref uses the whole label
217                 label = ref;
218                 return prtcmd;
219         }
220
221         // make sure the prefix is legal for a latex command
222         size_t const len = prefix.size();
223         for (size_t i = 0; i < len; i++) {
224                 char_type const c = prefix[i];
225                 if (!isAlphaASCII(c)) {
226                         LYXERR0("Prefix `" << prefix << "' is invalid for LaTeX.");
227                         // restore the label
228                         label = ref;
229                         return defcmd;
230                 }
231         }
232         if (use_caps) {
233                 prefix = support::capitalize(prefix);
234         }
235         return from_ascii("\\") + prefix + from_ascii("ref");
236 }
237
238
239 docstring InsetRef::getEscapedLabel(OutputParams const & rp) const
240 {
241         InsetCommandParams const & p = params();
242         ParamInfo const & pi = p.info();
243         ParamInfo::ParamData const & pd = pi["reference"];
244         return p.prepareCommand(rp, getParam("reference"), pd.handling());
245 }
246
247
248 void InsetRef::latex(otexstream & os, OutputParams const & rp) const
249 {
250         string const & cmd = getCmdName();
251         docstring const & data = getEscapedLabel(rp);
252
253         if (rp.inulemcmd > 0)
254                 os << "\\mbox{";
255
256         if (buffer().params().use_refstyle && cmd == "eqref") {
257                 // we advertise this as printing "(n)", so we'll do that, at least
258                 // for refstyle, since refstlye's own \eqref prints, by default,
259                 // "equation n". if one wants \eqref, one can get it by using a
260                 // formatted label in this case.
261                 os << '(' << from_ascii("\\ref{") << data << from_ascii("})");
262         }
263         else if (cmd == "formatted") {
264                 docstring label;
265                 docstring prefix;
266                 bool const use_caps   = getParam("caps") == "true";
267                 bool const use_plural = getParam("plural") == "true";
268                 docstring const fcmd =
269                         getFormattedCmd(data, label, prefix, use_caps);
270                 os << fcmd;
271                 if (buffer().params().use_refstyle && use_plural)
272                     os << "[s]";
273                 os << '{' << label << '}';
274         }
275         else if (cmd == "labelonly") {
276                 docstring const & ref = getParam("reference");
277                 if (getParam("noprefix") != "true")
278                         os << ref;
279                 else {
280                         docstring prefix;
281                         docstring suffix = split(ref, prefix, ':');
282                         if (suffix.empty()) {
283                     LYXERR0("Label `" << ref << "' contains no `:' separator.");
284                                 os << ref;
285                         } else {
286                                 os << suffix;
287                         }
288                 }
289         }
290         else {
291                 InsetCommandParams p(REF_CODE, cmd);
292                 docstring const ref = getParam("reference");
293                 p["reference"] = ref;
294                 os << p.getCommand(rp);
295         }
296
297         if (rp.inulemcmd > 0)
298                 os << "}";
299 }
300
301
302 int InsetRef::plaintext(odocstringstream & os,
303         OutputParams const &, size_t) const
304 {
305         docstring const str = getParam("reference");
306         os << '[' << str << ']';
307         return 2 + int(str.size());
308 }
309
310
311 void InsetRef::docbook(XMLStream & xs, OutputParams const &) const
312 {
313         docstring const & ref = getParam("reference");
314         InsetLabel const * il = buffer().insetLabel(ref, true);
315         string const & cmd = params().getCmdName();
316         docstring linkend = xml::cleanID(ref);
317
318         // A name is provided, LyX will provide it. This is supposed to be a very rare case.
319         // Link with linkend, as is it within the document (not outside, in which case xlink:href is better suited).
320         docstring const & name = getParam("name");
321         if (!name.empty()) {
322                 docstring attr = from_utf8("linkend=\"") + linkend + from_utf8("\"");
323
324                 xs << xml::StartTag("link", to_utf8(attr));
325                 xs << name;
326                 xs << xml::EndTag("link");
327                 return;
328         }
329
330         // The DocBook processor will generate the name when required.
331         docstring display_before;
332         docstring display_after;
333         docstring role;
334
335         if (il && !il->counterValue().empty()) {
336                 // Try to construct a label from the InsetLabel we reference.
337                 if (cmd == "vref" || cmd == "pageref" || cmd == "vpageref" || cmd == "nameref" || cmd == "formatted") {
338                         // "ref on page #", "on page #", etc. The DocBook processor deals with generating the right text,
339                         // including in the right language.
340                         role = from_ascii(cmd);
341
342                         if (cmd == "formatted") {
343                                 // A formatted reference may have many parameters. Generate all of them as roles, the only
344                                 // way arbitrary parameters can be passed into DocBook.
345                                 if (buffer().params().use_refstyle && getParam("caps") == "true")
346                                         role += " refstyle-caps";
347                                 if (buffer().params().use_refstyle && getParam("plural") == "true")
348                                         role += " refstyle-plural";
349                         }
350                 } else if (cmd == "eqref") {
351                         display_before = from_ascii("(");
352                         display_after = from_ascii(")");
353                 }
354                 // TODO: what about labelonly? I don't get how this is supposed to work...
355         }
356
357         // No name, ask DocBook to generate one.
358         docstring attr = from_utf8("linkend=\"") + xml::cleanID(ref) + from_utf8("\"");
359         if (!role.empty())
360                 attr += " role=\"" + role + "\"";
361         xs << display_before;
362         xs << xml::CompTag("xref", to_utf8(attr));
363         xs << display_after;
364 }
365
366
367 docstring InsetRef::xhtml(XMLStream & xs, OutputParams const & op) const
368 {
369         docstring const & ref = getParam("reference");
370         InsetLabel const * il = buffer().insetLabel(ref, true);
371         string const & cmd = params().getCmdName();
372         docstring display_string;
373
374         if (il && !il->counterValue().empty()) {
375                 // Try to construct a label from the InsetLabel we reference.
376                 docstring const & value = il->counterValue();
377                 if (cmd == "ref")
378                         display_string = value;
379                 else if (cmd == "vref")
380                         // normally, would be "ref on page #", but we have no pages
381                         display_string = value;
382                 else if (cmd == "pageref" || cmd == "vpageref")
383                         // normally would be "on page #", but we have no pages.
384                         display_string = translateIfPossible(from_ascii("elsewhere"),
385                                 op.local_font->language()->lang());
386                 else if (cmd == "eqref")
387                         display_string = '(' + value + ')';
388                 else if (cmd == "formatted") {
389                         display_string = il->prettyCounter();
390                         if (buffer().params().use_refstyle && getParam("caps") == "true")
391                                 capitalize(display_string);
392                         // it is hard to see what to do about plurals...
393                 }
394                 else if (cmd == "nameref")
395                         // FIXME We don't really have the ability to handle these
396                         // properly in XHTML output yet (bug #8599).
397                         // It might not be that hard to do. We have the InsetLabel,
398                         // and we can presumably find its paragraph using the TOC.
399                         // But the label might be referencing a section, yet not be
400                         // in that section. So this is not trivial.
401                         display_string = il->prettyCounter();
402         } else
403                         display_string = ref;
404
405         // FIXME What we'd really like to do is to be able to output some
406         // appropriate sort of text here. But to do that, we need to associate
407         // some sort of counter with the label, and we don't have that yet.
408         docstring const attr = "href=\"#" + xml::cleanAttr(ref) + '"';
409         xs << xml::StartTag("a", to_utf8(attr));
410         xs << display_string;
411         xs << xml::EndTag("a");
412         return docstring();
413 }
414
415
416 void InsetRef::toString(odocstream & os) const
417 {
418         odocstringstream ods;
419         plaintext(ods, OutputParams(nullptr));
420         os << ods.str();
421 }
422
423
424 void InsetRef::forOutliner(docstring & os, size_t const, bool const) const
425 {
426         // There's no need for details in the TOC, and a long label
427         // will just get in the way.
428         os += '#';
429 }
430
431
432 void InsetRef::updateBuffer(ParIterator const & it, UpdateType, bool const /*deleted*/)
433 {
434         docstring const & ref = getParam("reference");
435
436         // Check if this one is active (i.e., neither deleted with change-tracking
437         // nor in an inset that does not produce output, such as notes or inactive branches)
438         Paragraph const & para = it.paragraph();
439         active_ = !para.isDeleted(it.pos()) && para.inInset().producesOutput();
440         // If not, check whether we are in a deleted/non-outputting inset
441         if (active_) {
442                 for (size_type sl = 0 ; sl < it.depth() ; ++sl) {
443                         Paragraph const & outer_par = it[sl].paragraph();
444                         if (outer_par.isDeleted(it[sl].pos())
445                             || !outer_par.inInset().producesOutput()) {
446                                 active_ = false;
447                                 break;
448                         }
449                 }
450         }
451
452         // register this inset into the buffer reference cache.
453         buffer().addReference(ref, this, it);
454
455         docstring label;
456         string const & cmd = getCmdName();
457         for (int i = 0; !types[i].latex_name.empty(); ++i) {
458                 if (cmd == types[i].latex_name) {
459                         label = _(types[i].short_gui_name);
460                         break;
461                 }
462         }
463
464         if (cmd != "labelonly")
465                 label += ref;
466         else {
467                 if (getParam("noprefix") != "true")
468                         label += ref;
469                 else {
470                         docstring prefix;
471                         docstring suffix = split(ref, prefix, ':');
472                         if (suffix.empty()) {
473                                 label += ref;
474                         } else {
475                                 label += suffix;
476                         }
477                 }
478         }
479
480         if (!buffer().params().isLatex() && !getParam("name").empty()) {
481                 label += "||";
482                 label += getParam("name");
483         }
484
485         unsigned int const maxLabelChars = 24;
486         if (label.size() > maxLabelChars) {
487                 tooltip_ = label;
488                 support::truncateWithEllipsis(label, maxLabelChars);
489         } else
490                 tooltip_ = from_ascii("");
491
492         screen_label_ = label;
493         broken_ = false;
494         setBroken(broken_);
495 }
496
497
498 docstring InsetRef::screenLabel() const
499 {
500         return (broken_ ? _("BROKEN: ") : docstring()) + screen_label_;
501 }
502
503
504 void InsetRef::addToToc(DocIterator const & cpit, bool output_active,
505                         UpdateType, TocBackend & backend) const
506 {
507         active_ = output_active;
508         docstring const & label = getParam("reference");
509         if (buffer().insetLabel(label)) {
510                 broken_ = !buffer().activeLabel(label) && active_;
511                 setBroken(broken_);
512                 if (broken_ && output_active) {
513                         shared_ptr<Toc> toc2 = backend.toc("brokenrefs");
514                         toc2->push_back(TocItem(cpit, 0, screenLabel(), output_active));
515                 }
516                 // This InsetRef has already been taken care of in InsetLabel::addToToc().
517                 return;
518         }
519
520         // It seems that this reference does not point to any valid label.
521         broken_ = true;
522         setBroken(broken_);
523         shared_ptr<Toc> toc = backend.toc("label");
524         toc->push_back(TocItem(cpit, 0, screenLabel(), output_active));
525         shared_ptr<Toc> toc2 = backend.toc("brokenrefs");
526         toc2->push_back(TocItem(cpit, 0, screenLabel(), output_active));
527 }
528
529
530 void InsetRef::validate(LaTeXFeatures & features) const
531 {
532         string const & cmd = getCmdName();
533         if (cmd == "vref" || cmd == "vpageref")
534                 features.require("varioref");
535         else if (cmd == "formatted") {
536                 docstring const data = getEscapedLabel(features.runparams());
537                 docstring label;
538                 docstring prefix;
539                 bool const use_refstyle = buffer().params().use_refstyle;
540                 bool const use_caps   = getParam("caps") == "true";
541                 docstring const fcmd =
542                         getFormattedCmd(data, label, prefix, use_refstyle, use_caps);
543                 if (use_refstyle) {
544                         features.require("refstyle");
545                         if (prefix == "cha")
546                                 features.addPreambleSnippet(from_ascii("\\let\\charef=\\chapref"));
547                         else if (!prefix.empty()) {
548                                 docstring lcmd = "\\AtBeginDocument{\\providecommand" +
549                                                 fcmd + "[1]{\\ref{" + prefix + ":#1}}}";
550                                 features.addPreambleSnippet(lcmd);
551                         }
552                 } else {
553                         features.require("prettyref");
554                         // prettyref uses "cha" for chapters, so we provide a kind of
555                         // translation.
556                         if (prefix == "chap")
557                                 features.addPreambleSnippet(from_ascii("\\let\\pr@chap=\\pr@cha"));
558                 }
559         } else if (cmd == "eqref" && !buffer().params().use_refstyle)
560                 // with refstyle, we simply output "(\ref{label})"
561                 features.require("amsmath");
562         else if (cmd == "nameref")
563                 features.require("nameref");
564 }
565
566 bool InsetRef::forceLTR(OutputParams const & rp) const
567 {
568         // We force LTR for references. However,
569         // * Namerefs are output in the scripts direction
570         //   at least with fontspec/bidi and luabidi, though (see #11518).
571         // * Parentheses are automatically swapped with XeTeX/bidi 
572         //   [not with LuaTeX/luabidi] (see #11626).
573         // FIXME: Re-Audit all other RTL cases.
574         if (rp.useBidiPackage())
575                 return false;
576         return (getCmdName() != "nameref" || !buffer().masterParams().useNonTeXFonts);
577 }
578
579
580 InsetRef::type_info const InsetRef::types[] = {
581         { "ref",       N_("Standard"),              N_("Ref: ")},
582         { "eqref",     N_("Equation"),              N_("EqRef: ")},
583         { "pageref",   N_("Page Number"),           N_("Page: ")},
584         { "vpageref",  N_("Textual Page Number"),   N_("TextPage: ")},
585         { "vref",      N_("Standard+Textual Page"), N_("Ref+Text: ")},
586         { "nameref",   N_("Reference to Name"),     N_("NameRef: ")},
587         { "formatted", N_("Formatted"),             N_("Format: ")},
588         { "labelonly", N_("Label Only"),            N_("Label: ")},
589         { "", "", "" }
590 };
591
592
593 docstring InsetRef::getTOCString() const
594 {
595         docstring const & label = getParam("reference");
596         if (buffer().insetLabel(label))
597                 broken_ = !buffer().activeLabel(label) && active_;
598         else 
599                 broken_ = active_;
600         return tooltip_.empty() ? screenLabel() : tooltip_;
601 }
602
603 } // namespace lyx