X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FLaTeX.cpp;h=7d9810aef1a8b22e147c8d571191ec98145ae693;hb=4ed0312c51704780af1c452d3a82a84171b3725a;hp=3436645fec13db6e717d8772bd3b151498d13702;hpb=41740ea915ee7a95206d780b6256e660cef39c6e;p=lyx.git diff --git a/src/LaTeX.cpp b/src/LaTeX.cpp index 3436645fec..7d9810aef1 100644 --- a/src/LaTeX.cpp +++ b/src/LaTeX.cpp @@ -15,12 +15,19 @@ #include -#include "BufferList.h" #include "LaTeX.h" + +#include "Buffer.h" +#include "BufferList.h" +#include "BufferParams.h" #include "LyXRC.h" +#include "LyX.h" #include "DepTable.h" +#include "Encoding.h" +#include "Language.h" #include "support/debug.h" +#include "support/docstring.h" #include "support/convert.h" #include "support/FileName.h" #include "support/filetools.h" @@ -29,9 +36,8 @@ #include "support/Systemcall.h" #include "support/os.h" -#include "support/regex.h" - #include +#include #include @@ -57,7 +63,7 @@ docstring runMessage(unsigned int count) return bformat(_("Waiting for LaTeX run number %1$d"), count); } -} // anon namespace +} // namespace /* * CLASS TEXERRORS @@ -72,6 +78,15 @@ void TeXErrors::insertError(int line, docstring const & error_desc, } +void TeXErrors::insertRef(int line, docstring const & error_desc, + docstring const & error_text, + string const & child_name) +{ + Error newerr(line, error_desc, error_text, child_name); + undef_ref.push_back(newerr); +} + + bool operator==(AuxInfo const & a, AuxInfo const & o) { return a.aux_file == o.aux_file @@ -92,11 +107,19 @@ bool operator!=(AuxInfo const & a, AuxInfo const & o) */ LaTeX::LaTeX(string const & latex, OutputParams const & rp, - FileName const & f, string const & p) - : cmd(latex), file(f), path(p), runparams(rp), biber(false) + FileName const & f, string const & p, string const & lp, + bool allow_cancellation, bool const clean_start) + : cmd(latex), file(f), path(p), lpath(lp), runparams(rp), biber(false), + allow_cancel(allow_cancellation) { num_errors = 0; - if (prefixIs(cmd, "pdf")) { // Do we use pdflatex ? + // lualatex can still produce a DVI with --output-format=dvi. However, + // we do not use that internally (we use the "dvilualatex" command) so + // it would only happen from a custom converter. Thus, it is better to + // guess that lualatex produces a PDF than to guess a DVI. + // FIXME we should base the extension on the output format, which we should + // get in a robust way, e.g. from the converter. + if (prefixIs(cmd, "pdf") || prefixIs(cmd, "lualatex") || prefixIs(cmd, "xelatex")) { depfile = FileName(file.absFileName() + ".dep-pdf"); output_file = FileName(changeExtension(file.absFileName(), ".pdf")); @@ -105,12 +128,17 @@ LaTeX::LaTeX(string const & latex, OutputParams const & rp, output_file = FileName(changeExtension(file.absFileName(), ".dvi")); } + if (clean_start) + removeAuxiliaryFiles(); } -void LaTeX::deleteFilesOnError() const +void LaTeX::removeAuxiliaryFiles() const { - // currently just a dummy function. + LYXERR(Debug::OUTFILE, "Removing auxiliary files"); + // Note that we do not always call this function when there is an error. + // For example, if there is an error but an output file is produced we + // still would like to output (export/view) the file. // What files do we have to delete? @@ -139,9 +167,20 @@ void LaTeX::deleteFilesOnError() const FileName const gls(changeExtension(file.absFileName(), ".gls")); gls.removeFile(); + // endnotes file + FileName const ent(changeExtension(file.absFileName(), ".ent")); + ent.removeFile(); + // Also remove the aux file FileName const aux(changeExtension(file.absFileName(), ".aux")); aux.removeFile(); + + // Also remove the .out file (e.g. hyperref bookmarks) (#9963) + FileName const out(changeExtension(file.absFileName(), ".out")); + out.removeFile(); + + // Remove the output file, which is often generated even if error + output_file.removeFile(); } @@ -153,6 +192,8 @@ int LaTeX::run(TeXErrors & terr) // each time the .tex file changes. { int scanres = NO_ERRORS; + int bscanres = NO_ERRORS; + int iscanres = NO_ERRORS; unsigned int count = 0; // number of times run num_errors = 0; // just to make sure. unsigned int const MAX_RUN = 6; @@ -163,8 +204,6 @@ int LaTeX::run(TeXErrors & terr) theBufferList().updateIncludedTeXfiles(FileName::getcwd().absFileName(), runparams); - // Never write the depfile if an error was encountered. - // 0 // first check if the file dependencies exist: // ->If it does exist @@ -192,6 +231,25 @@ int LaTeX::run(TeXErrors & terr) } if (had_depfile) { + if (runparams.includeall) { + // On an "includeall" call (whose purpose is to set up/maintain counters and references + // for includeonly), we remove the master from the dependency list since + // (1) it will be checked anyway on the subsequent includeonly run + // (2) the master is always changed (due to the \includeonly line), and this alone would + // trigger a complete, expensive run each time + head.remove_file(file); + // Also remove all children which are included + Buffer const * buf = theBufferList().getBufferFromTmp(file.absFileName()); + if (buf && buf->params().maintain_unincluded_children == BufferParams::CM_Mostly) { + for (auto const & incfile : buf->params().getIncludedChildren()) { + string const incm = + DocFileName(changeExtension(makeAbsPath(incfile, path) + .absFileName(), ".tex")).mangledFileName(); + FileName inc = makeAbsPath(incm, file.onlyPath().realPath()); + head.remove_file(inc); + } + } + } // Update the checksums head.update(); // Can't just check if anything has changed because it might @@ -219,26 +277,27 @@ int LaTeX::run(TeXErrors & terr) /// in which case we will not need to run bibtex again. vector bibtex_info_old; if (!run_bibtex) - bibtex_info_old = scanAuxFiles(aux_file); + bibtex_info_old = scanAuxFiles(aux_file, runparams.only_childbibs); ++count; - LYXERR(Debug::LATEX, "Run #" << count); + LYXERR(Debug::OUTFILE, "Run #" << count); message(runMessage(count)); - startscript(); + int exit_code = startscript(); + if (exit_code == Systemcall::KILLED || exit_code == Systemcall::TIMEOUT) + return exit_code; + scanres = scanLogFile(terr); if (scanres & ERROR_RERUN) { - LYXERR(Debug::LATEX, "Rerunning LaTeX"); - startscript(); + LYXERR(Debug::OUTFILE, "Rerunning LaTeX"); + terr.clearErrors(); + exit_code = startscript(); + if (exit_code == Systemcall::KILLED || exit_code == Systemcall::TIMEOUT) + return exit_code; scanres = scanLogFile(terr); } - if (scanres & ERRORS) { - deleteFilesOnError(); - return scanres; // return on error - } - - vector const bibtex_info = scanAuxFiles(aux_file); + vector const bibtex_info = scanAuxFiles(aux_file, runparams.only_childbibs); if (!run_bibtex && bibtex_info_old != bibtex_info) run_bibtex = true; @@ -247,63 +306,34 @@ int LaTeX::run(TeXErrors & terr) head.update(); // 1 - // At this point we must run external programs if needed. - // makeindex will be run if a .idx file changed or was generated. - // And if there were undefined citations or changes in references - // the .aux file is checked for signs of bibtex. Bibtex is then run - // if needed. - - // memoir (at least) writes an empty *idx file in the first place. - // A second latex run is needed. - FileName const idxfile(changeExtension(file.absFileName(), ".idx")); - rerun = idxfile.exists() && idxfile.isFileEmpty(); - - // run makeindex - if (head.haschanged(idxfile)) { - // no checks for now - LYXERR(Debug::LATEX, "Running MakeIndex."); - message(_("Running Index Processor.")); - // onlyFileName() is needed for cygwin - rerun |= runMakeIndex(onlyFileName(idxfile.absFileName()), - runparams); - } - FileName const nlofile(changeExtension(file.absFileName(), ".nlo")); - // If all nomencl entries are removed, nomencl writes an empty nlo file. - // DepTable::hasChanged() returns false in this case, since it does not - // distinguish empty files from non-existing files. This is why we need - // the extra checks here (to trigger a rerun). Cf. discussions in #8905. - // FIXME: Sort out the real problem in DepTable. - if (head.haschanged(nlofile) || (nlofile.exists() && nlofile.isFileEmpty())) - rerun |= runMakeIndexNomencl(file, ".nlo", ".nls"); - FileName const glofile(changeExtension(file.absFileName(), ".glo")); - if (head.haschanged(glofile)) - rerun |= runMakeIndexNomencl(file, ".glo", ".gls"); - - // check if we're using biber instead of bibtex + // At this point we must run the bibliography processor if needed. + // First, check if we're using biber instead of bibtex -- // biber writes no info to the aux file, so we just check - // if a bcf file exists (and if it was updated) + // if a bcf file exists (and if it was updated). FileName const bcffile(changeExtension(file.absFileName(), ".bcf")); biber |= head.exist(bcffile); - // run bibtex - // if (scanres & UNDEF_CIT || scanres & RERUN || run_bibtex) - if (scanres & UNDEF_CIT || run_bibtex) { + // If (scanres & UNDEF_CIT || scanres & RERUN || run_bibtex) + // We do not run bibtex/biber on an "includeall" call (whose purpose is + // to set up/maintain counters and references for includeonly) since + // (1) bibliographic references will be updated on the subsequent includeonly run + // (2) this would trigger a complete run each time (as references in non-included + // children are removed on subsequent includeonly runs) + if (!runparams.includeall && (scanres & UNDEF_CIT || run_bibtex)) { // Here we must scan the .aux file and look for // "\bibdata" and/or "\bibstyle". If one of those // tags is found -> run bibtex and set rerun = true; // no checks for now - LYXERR(Debug::LATEX, "Running BibTeX."); - message(_("Running BibTeX.")); + LYXERR(Debug::OUTFILE, "Running Bibliography Processor."); + message(_("Running Bibliography Processor.")); updateBibtexDependencies(head, bibtex_info); - rerun |= runBibTeX(bibtex_info, runparams); + int exit_code; + rerun |= runBibTeX(bibtex_info, runparams, exit_code); + if (exit_code == Systemcall::KILLED || exit_code == Systemcall::TIMEOUT) + return exit_code; FileName const blgfile(changeExtension(file.absFileName(), ".blg")); - if (blgfile.exists()) { - int bscanres = scanBlgFile(head, terr); - if (bscanres & ERRORS) { - deleteFilesOnError(); - return bscanres; // return on error - } - } + if (blgfile.exists()) + bscanres = scanBlgFile(head, terr); } else if (!had_depfile) { /// If we run pdflatex on the file after running latex on it, /// then we do not need to run bibtex, but we do need to @@ -312,7 +342,7 @@ int LaTeX::run(TeXErrors & terr) } // 2 - // we know on this point that latex has been run once (or we just + // We know on this point that latex has been run once (or we just // returned) and the question now is to decide if we need to run // it any more. This is done by asking if any of the files in the // dependency file has changed. (remember that the checksum for @@ -327,14 +357,12 @@ int LaTeX::run(TeXErrors & terr) rerun = false; ++count; LYXERR(Debug::DEPEND, "Dep. file has changed or rerun requested"); - LYXERR(Debug::LATEX, "Run #" << count); + LYXERR(Debug::OUTFILE, "Run #" << count); message(runMessage(count)); - startscript(); + int exit_code = startscript(); + if (exit_code == Systemcall::KILLED || exit_code == Systemcall::TIMEOUT) + return exit_code; scanres = scanLogFile(terr); - if (scanres & ERRORS) { - deleteFilesOnError(); - return scanres; // return on error - } // update the depedencies deplog(head); // reads the latex log @@ -342,56 +370,104 @@ int LaTeX::run(TeXErrors & terr) } else { LYXERR(Debug::DEPEND, "Dep. file has NOT changed"); } - + // 3 - // rerun bibtex? + // Rerun bibliography processor? // Complex bibliography packages such as Biblatex require // an additional bibtex cycle sometimes. - if (scanres & UNDEF_CIT) { + // We do not run bibtex/biber on an "includeall" call (whose purpose is + // to set up/maintain counters and references for includeonly) since + // (1) bibliographic references will be updated on the subsequent includeonly run + // (2) this would trigger a complete run each time (as references in non-included + // children are removed on subsequent includeonly runs) + if (!runparams.includeall && scanres & UNDEF_CIT) { // Here we must scan the .aux file and look for // "\bibdata" and/or "\bibstyle". If one of those // tags is found -> run bibtex and set rerun = true; // no checks for now - LYXERR(Debug::LATEX, "Running BibTeX."); - message(_("Running BibTeX.")); + LYXERR(Debug::OUTFILE, "Re-Running Bibliography Processor."); + message(_("Re-Running Bibliography Processor.")); updateBibtexDependencies(head, bibtex_info); - rerun |= runBibTeX(bibtex_info, runparams); + int exit_code; + rerun |= runBibTeX(bibtex_info, runparams, exit_code); + if (exit_code == Systemcall::KILLED || exit_code == Systemcall::TIMEOUT) + return exit_code; FileName const blgfile(changeExtension(file.absFileName(), ".blg")); - if (blgfile.exists()) { - int bscanres = scanBlgFile(head, terr); - if (bscanres & ERRORS) { - deleteFilesOnError(); - return bscanres; // return on error - } - } + if (blgfile.exists()) + bscanres = scanBlgFile(head, terr); } // 4 - // The inclusion of files generated by external programs such as - // makeindex or bibtex might have done changes to pagenumbering, - // etc. And because of this we must run the external programs - // again to make sure everything is redone correctly. - // Also there should be no need to run the external programs any - // more after this. - - // run makeindex if the .idx has changed or was generated. - if (head.haschanged(idxfile)) { + // After the bibliography was processed, we need more passes of LaTeX + // in order to resolve the citations. We need to do this before the index + // is being generated (since we need the correct pagination, see #2696). + // With bibliography environment, another LaTeX run might be needed + // as well to resolve citations. + // Also, memoir (at least) writes an empty *idx file in the first place. + // A further latex run is needed in that case as well. + FileName const idxfile(changeExtension(file.absFileName(), ".idx")); + if (run_bibtex || (scanres & UNDEF_CIT) || (idxfile.exists() && idxfile.isFileEmpty())) { + while ((head.sumchange() || rerun || (scanres & RERUN) || (scanres & UNDEF_CIT)) + && count < MAX_RUN) { + // Yes rerun until message goes away, or until + // MAX_RUNS are reached. + rerun = false; + ++count; + LYXERR(Debug::OUTFILE, "Run #" << count); + message(runMessage(count)); + startscript(); + scanres = scanLogFile(terr); + + // keep this updated + head.update(); + } + } + + // 5 + // Now that we have final pagination, run the index and nomencl processors + if (idxfile.exists()) { // no checks for now - LYXERR(Debug::LATEX, "Running MakeIndex."); + LYXERR(Debug::OUTFILE, "Running Index Processor."); message(_("Running Index Processor.")); // onlyFileName() is needed for cygwin - rerun = runMakeIndex(onlyFileName(changeExtension( - file.absFileName(), ".idx")), runparams); + int const ret = + runMakeIndex(onlyFileName(idxfile.absFileName()), runparams); + if (ret == Systemcall::KILLED || ret == Systemcall::TIMEOUT) + return ret; + else if (ret != Systemcall::OK) { + iscanres |= INDEX_ERROR; + terr.insertError(0, + _("Index Processor Error"), + _("The index processor did not run successfully. " + "Please check the output of View > Messages Pane!")); + } + FileName const ilgfile(changeExtension(file.absFileName(), ".ilg")); + if (ilgfile.exists()) + iscanres = scanIlgFile(terr); + rerun = true; + } + FileName const nlofile(changeExtension(file.absFileName(), ".nlo")); + // If all nomencl entries are removed, nomencl writes an empty nlo file. + // DepTable::hasChanged() returns false in this case, since it does not + // distinguish empty files from non-existing files. This is why we need + // the extra checks here (to trigger a rerun). Cf. discussions in #8905. + // FIXME: Sort out the real problem in DepTable. + if (head.haschanged(nlofile) || (nlofile.exists() && nlofile.isFileEmpty())) { + int const ret = runMakeIndexNomencl(file, ".nlo", ".nls"); + if (ret == Systemcall::KILLED || ret == Systemcall::TIMEOUT) + return ret; + rerun = true; + } + FileName const glofile(changeExtension(file.absFileName(), ".glo")); + if (head.haschanged(glofile)) { + int const ret = runMakeIndexNomencl(file, ".glo", ".gls"); + if (ret) + return ret; + rerun = true; } - // I am not pretty sure if need this twice. - if (head.haschanged(nlofile)) - rerun |= runMakeIndexNomencl(file, ".nlo", ".nls"); - if (head.haschanged(glofile)) - rerun |= runMakeIndexNomencl(file, ".glo", ".gls"); - - // 5 - // we will only run latex more if the log file asks for it. + // 6 + // We will re-run latex if the log file asks for it, // or if the sumchange() is true. // -> rerun asked for: // run latex and @@ -406,14 +482,10 @@ int LaTeX::run(TeXErrors & terr) // MAX_RUNS are reached. rerun = false; ++count; - LYXERR(Debug::LATEX, "Run #" << count); + LYXERR(Debug::OUTFILE, "Run #" << count); message(runMessage(count)); startscript(); scanres = scanLogFile(terr); - if (scanres & ERRORS) { - deleteFilesOnError(); - return scanres; // return on error - } // keep this updated head.update(); @@ -421,7 +493,22 @@ int LaTeX::run(TeXErrors & terr) // Write the dependencies to file. head.write(depfile); - LYXERR(Debug::LATEX, "Done."); + + if (exit_code) { + // add flag here, just before return, instead of when exit_code + // is defined because scanres is sometimes overwritten above + // (e.g. rerun) + scanres |= NONZERO_ERROR; + } + + LYXERR(Debug::OUTFILE, "Done."); + + if (bscanres & ERRORS) + return bscanres; // return on error + + if (iscanres & ERRORS) + return iscanres; // return on error + return scanres; } @@ -433,62 +520,119 @@ int LaTeX::startscript() + quoteName(onlyFileName(file.toFilesystemEncoding())) + " > " + os::nulldev(); Systemcall one; - return one.startscript(Systemcall::Wait, tmp, path); + Systemcall::Starttype const starttype = + allow_cancel ? Systemcall::WaitLoop : Systemcall::Wait; + return one.startscript(starttype, tmp, path, lpath, true); } -bool LaTeX::runMakeIndex(string const & f, OutputParams const & runparams, +int LaTeX::runMakeIndex(string const & f, OutputParams const & rp, string const & params) { - string tmp = runparams.use_japanese ? + string tmp = rp.use_japanese ? lyxrc.jindex_command : lyxrc.index_command; + + if (!rp.index_command.empty()) + tmp = rp.index_command; + + Language const * doc_lang = languages.getLanguage(rp.document_language); - if (!runparams.index_command.empty()) - tmp = runparams.index_command; + if (contains(tmp, "$$x")) { + // This adds appropriate [te]xindy options + // such as language and codepage (for the + // main document language/encoding) as well + // as input markup (latex or xelatex) + string xdyopts = doc_lang ? doc_lang->xindy() : string(); + if (!xdyopts.empty()) + xdyopts = "-L " + xdyopts; + if (rp.isFullUnicode() && rp.encoding->package() == Encoding::none) { + if (!xdyopts.empty()) + xdyopts += " "; + // xelatex includes lualatex + xdyopts += "-I xelatex"; + } + else if (rp.encoding->iconvName() == "UTF-8") { + if (!xdyopts.empty()) + xdyopts += " "; + // -I not really needed for texindy, but for xindy + xdyopts += "-C utf8 -I latex"; + } + else { + if (!xdyopts.empty()) + xdyopts += " "; + // not really needed for texindy, but for xindy + xdyopts += "-I latex"; + } + tmp = subst(tmp, "$$x", xdyopts); + } - LYXERR(Debug::LATEX, + if (contains(tmp, "$$b")) { + // advise xindy to write a log file + tmp = subst(tmp, "$$b", removeExtension(f)); + } + + LYXERR(Debug::OUTFILE, "idx file has been made, running index processor (" << tmp << ") on file " << f); - tmp = subst(tmp, "$$lang", runparams.document_language); - if (runparams.use_indices) { + if (doc_lang) { + tmp = subst(tmp, "$$lang", doc_lang->babel()); + tmp = subst(tmp, "$$lcode", doc_lang->code()); + } + if (rp.use_indices) { tmp = lyxrc.splitindex_command + " -m " + quoteName(tmp); - LYXERR(Debug::LATEX, + LYXERR(Debug::OUTFILE, "Multiple indices. Using splitindex command: " << tmp); } tmp += ' '; tmp += quoteName(f); tmp += params; Systemcall one; - one.startscript(Systemcall::Wait, tmp, path); - return true; + Systemcall::Starttype const starttype = + allow_cancel ? Systemcall::WaitLoop : Systemcall::Wait; + return one.startscript(starttype, tmp, path, lpath, true); } -bool LaTeX::runMakeIndexNomencl(FileName const & file, +int LaTeX::runMakeIndexNomencl(FileName const & fname, string const & nlo, string const & nls) { - LYXERR(Debug::LATEX, "Running MakeIndex for nomencl."); - message(_("Running MakeIndex for nomencl.")); + LYXERR(Debug::OUTFILE, "Running Nomenclature Processor."); + message(_("Running Nomenclature Processor.")); string tmp = lyxrc.nomencl_command + ' '; // onlyFileName() is needed for cygwin - tmp += quoteName(onlyFileName(changeExtension(file.absFileName(), nlo))); + tmp += quoteName(onlyFileName(changeExtension(fname.absFileName(), nlo))); tmp += " -o " - + onlyFileName(changeExtension(file.toFilesystemEncoding(), nls)); + + onlyFileName(changeExtension(fname.toFilesystemEncoding(), nls)); Systemcall one; - one.startscript(Systemcall::Wait, tmp, path); - return true; + Systemcall::Starttype const starttype = + allow_cancel ? Systemcall::WaitLoop : Systemcall::Wait; + return one.startscript(starttype, tmp, path, lpath, true); } vector const -LaTeX::scanAuxFiles(FileName const & file) +LaTeX::scanAuxFiles(FileName const & fname, bool const only_childbibs) { vector result; - result.push_back(scanAuxFile(file)); + // With chapterbib, we have to bibtex all children's aux files + // but _not_ the master's! + if (only_childbibs) { + for (string const &s: children) { + FileName fn = + makeAbsPath(s, fname.onlyPath().realPath()); + fn.changeExtension("aux"); + if (fn.exists()) + result.push_back(scanAuxFile(fn)); + } + return result; + } + + result.push_back(scanAuxFile(fname)); - string const basename = removeExtension(file.absFileName()); + // This is for bibtopic + string const basename = removeExtension(fname.absFileName()); for (int i = 1; i < 1000; ++i) { FileName const file2(basename + '.' + convert(i) @@ -501,20 +645,20 @@ LaTeX::scanAuxFiles(FileName const & file) } -AuxInfo const LaTeX::scanAuxFile(FileName const & file) +AuxInfo const LaTeX::scanAuxFile(FileName const & fname) { AuxInfo result; - result.aux_file = file; - scanAuxFile(file, result); + result.aux_file = fname; + scanAuxFile(fname, result); return result; } -void LaTeX::scanAuxFile(FileName const & file, AuxInfo & aux_info) +void LaTeX::scanAuxFile(FileName const & fname, AuxInfo & aux_info) { - LYXERR(Debug::LATEX, "Scanning aux file: " << file); + LYXERR(Debug::OUTFILE, "Scanning aux file: " << fname); - ifstream ifs(file.toFilesystemEncoding().c_str()); + ifstream ifs(fname.toFilesystemEncoding().c_str()); string token; static regex const reg1("\\\\citation\\{([^}]+)\\}"); static regex const reg2("\\\\bibdata\\{([^}]+)\\}"); @@ -532,7 +676,7 @@ void LaTeX::scanAuxFile(FileName const & file, AuxInfo & aux_info) while (!data.empty()) { string citation; data = split(data, citation, ','); - LYXERR(Debug::LATEX, "Citation: " << citation); + LYXERR(Debug::OUTFILE, "Citation: " << citation); aux_info.citations.insert(citation); } } else if (regex_match(token, sub, reg2)) { @@ -543,7 +687,7 @@ void LaTeX::scanAuxFile(FileName const & file, AuxInfo & aux_info) string database; data = split(data, database, ','); database = changeExtension(database, "bib"); - LYXERR(Debug::LATEX, "BibTeX database: `" << database << '\''); + LYXERR(Debug::OUTFILE, "BibTeX database: `" << database << '\''); aux_info.databases.insert(database); } } else if (regex_match(token, sub, reg3)) { @@ -551,7 +695,7 @@ void LaTeX::scanAuxFile(FileName const & file, AuxInfo & aux_info) // token is now the style file // pass it to the helper style = changeExtension(style, "bst"); - LYXERR(Debug::LATEX, "BibTeX style: `" << style << '\''); + LYXERR(Debug::OUTFILE, "BibTeX style: `" << style << '\''); aux_info.styles.insert(style); } else if (regex_match(token, sub, reg4)) { string const file2 = sub.str(1); @@ -597,32 +741,42 @@ void LaTeX::updateBibtexDependencies(DepTable & dep, bool LaTeX::runBibTeX(vector const & bibtex_info, - OutputParams const & runparams) + OutputParams const & rp, int & exit_code) { bool result = false; + exit_code = 0; for (vector::const_iterator it = bibtex_info.begin(); it != bibtex_info.end(); ++it) { if (!biber && it->databases.empty()) continue; result = true; - string tmp = runparams.use_japanese ? - lyxrc.jbibtex_command : lyxrc.bibtex_command; - - if (!runparams.bibtex_command.empty()) - tmp = runparams.bibtex_command; + string tmp = rp.bibtex_command; tmp += " "; // onlyFileName() is needed for cygwin tmp += quoteName(onlyFileName(removeExtension( it->aux_file.absFileName()))); Systemcall one; - one.startscript(Systemcall::Wait, tmp, path); + Systemcall::Starttype const starttype = + allow_cancel ? Systemcall::WaitLoop : Systemcall::Wait; + exit_code = one.startscript(starttype, tmp, path, lpath, true); + if (exit_code) { + return result; + } } // Return whether bibtex was run return result; } +//helper func for scanLogFile; gets line number X from strings "... on input line X ..." +//returns 0 if none is found +int getLineNumber(const string &token){ + string l = support::token(token, ' ', tokenPos(token,' ',"line") + 1); + return l.empty() ? 0 : convert(l); +} + + int LaTeX::scanLogFile(TeXErrors & terr) { int last_line = -1; @@ -630,20 +784,28 @@ int LaTeX::scanLogFile(TeXErrors & terr) int retval = NO_ERRORS; string tmp = onlyFileName(changeExtension(file.absFileName(), ".log")); - LYXERR(Debug::LATEX, "Log file: " << tmp); - FileName const fn = FileName(makeAbsPath(tmp)); + LYXERR(Debug::OUTFILE, "Log file: " << tmp); + FileName const fn = makeAbsPath(tmp); + // FIXME we should use an ifdocstream here and a docstring for token + // below. The encoding of the log file depends on the _output_ (font) + // encoding of the TeX file (T1, TU etc.). See #10728. ifstream ifs(fn.toFilesystemEncoding().c_str()); bool fle_style = false; static regex const file_line_error(".+\\.\\D+:[0-9]+: (.+)"); - static regex const child_file(".*([0-9]+[A-Za-z]*_.+\\.tex).*"); + static regex const child_file("[^0-9]*([0-9]+[A-Za-z]*_.+\\.tex).*"); + static regex const undef_ref(".*Reference `(\\w+)\\' on page.*"); // Flag for 'File ended while scanning' message. // We need to wait for subsequent processing. string wait_for_error; string child_name; int pnest = 0; stack > child; + children.clear(); + + terr.clearRefs(); string token; + string ml_token; while (getline(ifs, token)) { // MikTeX sometimes inserts \0 in the log file. They can't be // removed directly with the existing string utility @@ -653,11 +815,14 @@ int LaTeX::scanLogFile(TeXErrors & terr) token = subst(token, "\r", ""); smatch sub; - LYXERR(Debug::LATEX, "Log line: " << token); + LYXERR(Debug::OUTFILE, "Log line: " << token); if (token.empty()) continue; + if (!ml_token.empty()) + ml_token += token; + // Track child documents for (size_t i = 0; i < token.length(); ++i) { if (token[i] == '(') { @@ -666,10 +831,15 @@ int LaTeX::scanLogFile(TeXErrors & terr) size_t len = j == string::npos ? token.substr(i + 1).length() : j - i - 1; - if (regex_match(token.substr(i + 1, len), - sub, child_file)) { + string const substr = token.substr(i + 1, len); + if (regex_match(substr, sub, child_file)) { string const name = sub.str(1); - child.push(make_pair(name, pnest)); + // Sometimes also masters have a name that matches + // (if their name starts with a number and _) + if (name != file.onlyFileName()) { + child.push(make_pair(name, pnest)); + children.push_back(name); + } i += len; } } else if (token[i] == ')') { @@ -684,47 +854,115 @@ int LaTeX::scanLogFile(TeXErrors & terr) if (contains(token, "file:line:error style messages enabled")) fle_style = true; - if (prefixIs(token, "LaTeX Warning:") || - prefixIs(token, "! pdfTeX warning")) { + //Handles both "LaTeX Warning:" & "Package natbib Warning:" + //Various handlers for missing citations below won't catch the problem if citation + //key is long (>~25chars), because pdflatex splits output at line length 80. + //TODO: TL 2020 engines will contain new commandline switch --cnf-line which we + //can use to set max_print_line variable for appropriate length and detect all + //errors correctly. + if (!runparams.includeall && (contains(token, "There were undefined citations.") || + prefixIs(token, "Package biblatex Warning: The following entry could not be found"))) + retval |= UNDEF_CIT; + + if (prefixIs(token, "LaTeX Warning:") + || prefixIs(token, "! pdfTeX warning") + || prefixIs(ml_token, "LaTeX Warning:") + || prefixIs(ml_token, "! pdfTeX warning")) { // Here shall we handle different // types of warnings retval |= LATEX_WARNING; - LYXERR(Debug::LATEX, "LaTeX Warning."); + LYXERR(Debug::OUTFILE, "LaTeX Warning."); if (contains(token, "Rerun to get cross-references")) { retval |= RERUN; - LYXERR(Debug::LATEX, "We should rerun."); + LYXERR(Debug::OUTFILE, "We should rerun."); // package clefval needs 2 latex runs before bibtex } else if (contains(token, "Value of") && contains(token, "on page") && contains(token, "undefined")) { retval |= ERROR_RERUN; - LYXERR(Debug::LATEX, "Force rerun."); + LYXERR(Debug::OUTFILE, "Force rerun."); // package etaremune } else if (contains(token, "Etaremune labels have changed")) { retval |= ERROR_RERUN; - LYXERR(Debug::LATEX, "Force rerun."); - } else if (contains(token, "Citation") - && contains(token, "on page") + LYXERR(Debug::OUTFILE, "Force rerun."); + // package enotez + } else if (contains(token, "Endnotes may have changed. Rerun")) { + retval |= RERUN; + LYXERR(Debug::OUTFILE, "We should rerun."); + //"Citation `cit' on page X undefined on input line X." + } else if (!runparams.includeall && contains(token, "Citation") + //&& contains(token, "on input line") //often split to newline && contains(token, "undefined")) { retval |= UNDEF_CIT; - } else if (contains(token, "Citation") + terr.insertRef(getLineNumber(token), from_ascii("Citation undefined"), + from_utf8(token), child_name); + //"Reference `X' on page Y undefined on input line Z." + // This warning might be broken accross multiple lines with long labels. + // Thus we check that + } else if (contains(token, "Reference `") && !contains(token, "on input line")) { + // Rest of warning in next line(s) + // Save to ml_token + ml_token = token; + } else if (!ml_token.empty() && contains(ml_token, "Reference `") + && !contains(ml_token, "on input line")) { + // not finished yet. Continue with next line. + continue; + } else if (!ml_token.empty() && contains(ml_token, "Reference `") + && contains(ml_token, "on input line")) { + // We have collected the whole warning now. + if (!contains(ml_token, "undefined")) { + // Not the warning we are looking for + ml_token.clear(); + continue; + } + if (regex_match(ml_token, sub, undef_ref)) { + string const ref = sub.str(1); + Buffer const * buf = theBufferList().getBufferFromTmp(file.absFileName()); + if (!buf || !buf->masterBuffer()->activeLabel(from_utf8(ref))) { + terr.insertRef(getLineNumber(ml_token), from_ascii("Reference undefined"), + from_utf8(ml_token), child_name); + retval |= UNDEF_UNKNOWN_REF; + } + } + ml_token.clear(); + retval |= UNDEF_REF; + } else if (contains(token, "Reference `") && contains(token, "on input line") && contains(token, "undefined")) { - retval |= UNDEF_CIT; + if (regex_match(token, sub, undef_ref)) { + string const ref = sub.str(1); + Buffer const * buf = theBufferList().getBufferFromTmp(file.absFileName()); + if (!buf || !buf->masterBuffer()->activeLabel(from_utf8(ref))) { + terr.insertRef(getLineNumber(token), from_ascii("Reference undefined"), + from_utf8(token), child_name); + retval |= UNDEF_UNKNOWN_REF; + } + } + retval |= UNDEF_REF; + // In case the above checks fail we catch at least this generic statement + // occuring for both CIT & REF. + } else if (!runparams.includeall && contains(token, "There were undefined references.")) { + if (!(retval & UNDEF_CIT)) //if not handled already + retval |= UNDEF_REF; } + } else if (prefixIs(token, "Package")) { // Package warnings retval |= PACKAGE_WARNING; if (contains(token, "natbib Warning:")) { // Natbib warnings - if (contains(token, "Citation") + if (!runparams.includeall + && contains(token, "Citation") && contains(token, "on page") && contains(token, "undefined")) { retval |= UNDEF_CIT; + //Unf only keys up to ~6 chars will make it due to line splits + terr.insertRef(getLineNumber(token), from_ascii("Citation undefined"), + from_utf8(token), child_name); } - } else if (contains(token, "run BibTeX")) { + } else if (!runparams.includeall && contains(token, "run BibTeX")) { retval |= UNDEF_CIT; - } else if (contains(token, "run Biber")) { + } else if (!runparams.includeall && contains(token, "run Biber")) { retval |= UNDEF_CIT; biber = true; } else if (contains(token, "Rerun LaTeX") || @@ -732,20 +970,20 @@ int LaTeX::scanLogFile(TeXErrors & terr) contains(token, "Rerun to get")) { // at least longtable.sty and bibtopic.sty // might use this. - LYXERR(Debug::LATEX, "We should rerun."); + LYXERR(Debug::OUTFILE, "We should rerun."); retval |= RERUN; } } else if (prefixIs(token, "LETTRE WARNING:")) { if (contains(token, "veuillez recompiler")) { // lettre.cls - LYXERR(Debug::LATEX, "We should rerun."); + LYXERR(Debug::OUTFILE, "We should rerun."); retval |= RERUN; } } else if (token[0] == '(') { if (contains(token, "Rerun LaTeX") || contains(token, "Rerun to get")) { // Used by natbib - LYXERR(Debug::LATEX, "We should rerun."); + LYXERR(Debug::OUTFILE, "We should rerun."); retval |= RERUN; } } else if (prefixIs(token, "! ") @@ -764,11 +1002,11 @@ int LaTeX::scanLogFile(TeXErrors & terr) if (contains(token, "LaTeX Error:")) retval |= LATEX_ERROR; - if (prefixIs(token, "! File ended while scanning")){ + if (prefixIs(token, "! File ended while scanning")) { if (prefixIs(token, "! File ended while scanning use of \\Hy@setref@link.")){ // bug 7344. We must rerun LaTeX if hyperref has been toggled. retval |= ERROR_RERUN; - LYXERR(Debug::LATEX, "Force rerun."); + LYXERR(Debug::OUTFILE, "Force rerun."); } else { // bug 6445. At this point its not clear we finish with error. wait_for_error = desc; @@ -776,10 +1014,16 @@ int LaTeX::scanLogFile(TeXErrors & terr) } } + if (prefixIs(token, "! Incomplete \\if")) { + // bug 10666. At this point its not clear we finish with error. + wait_for_error = desc; + continue; + } + if (prefixIs(token, "! Paragraph ended before \\Hy@setref@link was complete.")){ // bug 7344. We must rerun LaTeX if hyperref has been toggled. retval |= ERROR_RERUN; - LYXERR(Debug::LATEX, "Force rerun."); + LYXERR(Debug::OUTFILE, "Force rerun."); } if (!wait_for_error.empty() && prefixIs(token, "! Emergency stop.")){ @@ -787,6 +1031,7 @@ int LaTeX::scanLogFile(TeXErrors & terr) string errstr; int count = 0; errstr = wait_for_error; + wait_for_error.clear(); do { if (!getline(ifs, tmp)) break; @@ -797,19 +1042,26 @@ int LaTeX::scanLogFile(TeXErrors & terr) } while (!contains(tmp, "(job aborted")); terr.insertError(0, - from_local8bit("Emergency stop"), + from_ascii("Emergency stop"), from_local8bit(errstr), child_name); } // get the next line - string tmp; int count = 0; + // We also collect intermediate lines + // This is needed for errors in preamble + string intermediate; do { if (!getline(ifs, tmp)) break; tmp = rtrim(tmp, "\r"); - if (++count > 10) + if (!prefixIs(tmp, "l.")) + intermediate += tmp; + // 15 is somewhat arbitrarily chosen, based on practice. + // We used 10 for 14 years and increased it to 15 when we + // saw one case. + if (++count > 15) break; } while (!prefixIs(tmp, "l.")); if (prefixIs(tmp, "l.")) { @@ -827,6 +1079,15 @@ int LaTeX::scanLogFile(TeXErrors & terr) sscanf(tmp.c_str(), "l.%d", &line); // get the rest of the message: string errstr(tmp, tmp.find(' ')); + bool preamble_error = false; + if (suffixIs(errstr, "\\begin{document}")) { + // this is an error in preamble + // the real error is in the + // intermediate lines + errstr = intermediate; + tmp = intermediate; + preamble_error = true; + } errstr += '\n'; getline(ifs, tmp); tmp = rtrim(tmp, "\r"); @@ -839,8 +1100,11 @@ int LaTeX::scanLogFile(TeXErrors & terr) getline(ifs, tmp); tmp = rtrim(tmp, "\r"); } - LYXERR(Debug::LATEX, "line: " << line << '\n' - << "Desc: " << desc << '\n' << "Text: " << errstr); + if (preamble_error) + // Add a note that the error is to be found in preamble + errstr += "\n" + to_utf8(_("(NOTE: The erroneous command is in the preamble)")); + LYXERR(Debug::OUTFILE, "line: " << line << '\n' + << "Desc: " << desc << '\n' << "Text: " << errstr); if (line == last_line) ++line_count; else { @@ -870,27 +1134,57 @@ int LaTeX::scanLogFile(TeXErrors & terr) retval |= TEX_WARNING; } else if (prefixIs(token, "Underfull ")) { retval |= TEX_WARNING; - } else if (contains(token, "Rerun to get citations")) { + } else if (!runparams.includeall && contains(token, "Rerun to get citations")) { // Natbib seems to use this. retval |= UNDEF_CIT; - } else if (contains(token, "No pages of output")) { - // A dvi file was not created + } else if (contains(token, "No pages of output") + || contains(token, "no pages of output")) { + // No output file (e.g. the DVI or PDF) was created retval |= NO_OUTPUT; + } else if (contains(token, "Error 256 (driver return code)")) { + // This is a xdvipdfmx driver error reported by XeTeX. + // We have to check whether an output PDF file was created. + FileName pdffile = file; + pdffile.changeExtension("pdf"); + if (!pdffile.exists()) + // No output PDF file was created (see #10076) + retval |= NO_OUTPUT; } else if (contains(token, "That makes 100 errors")) { - // More than 100 errors were reprted + // More than 100 errors were reported retval |= TOO_MANY_ERRORS; - } else if (prefixIs(token, "!pdfTeX error:")){ + } else if (prefixIs(token, "!pdfTeX error:")) { // otherwise we dont catch e.g.: // !pdfTeX error: pdflatex (file feyn10): Font feyn10 at 600 not found retval |= ERRORS; + terr.insertError(0, + from_ascii("pdfTeX Error"), + from_local8bit(token), + child_name); + } else if (!ignore_missing_glyphs + && prefixIs(token, "Missing character: There is no ") + && !contains(token, "nullfont")) { + // Warning about missing glyph in selected font + // may be dataloss (bug 9610) + // but can be ignored for 'nullfont' (bug 10394). + // as well as for ZERO WIDTH NON-JOINER (0x200C) which is + // missing in many fonts and output for ligature break (bug 10727). + // Since this error only occurs with utf8 output, we can safely assume + // that the log file is utf8-encoded + docstring const utoken = from_utf8(token); + if (!contains(utoken, 0x200C)) { + retval |= LATEX_ERROR; terr.insertError(0, - from_local8bit("pdfTeX Error"), - from_local8bit(token), + from_ascii("Missing glyphs!"), + utoken, child_name); + } + } else if (!wait_for_error.empty()) { + // We collect information until we know we have an error. + wait_for_error += token + '\n'; } } } - LYXERR(Debug::LATEX, "Log line: " << token); + LYXERR(Debug::OUTFILE, "Log line: " << token); return retval; } @@ -942,8 +1236,7 @@ bool handleFoundFile(string const & ff, DepTable & head) return true; // strip off part after last space and try again string tmp = strippedfile; - string const stripoff = - rsplit(tmp, strippedfile, ' '); + rsplit(tmp, strippedfile, ' '); absname.set(strippedfile); if (insertIfExists(absname, head)) return true; @@ -968,8 +1261,7 @@ bool handleFoundFile(string const & ff, DepTable & head) break; // strip off part after last space and try again string strippedfile; - string const stripoff = - rsplit(foundfile, strippedfile, ' '); + rsplit(foundfile, strippedfile, ' '); foundfile = strippedfile; onlyfile = onlyFileName(strippedfile); absname = makeAbsPath(onlyfile); @@ -1015,8 +1307,8 @@ bool completeFilename(string const & ff, DepTable & head) } -int iterateLine(string const & token, regex const & reg, string const & closing, - int fragment_pos, DepTable & head) +int iterateLine(string const & token, regex const & reg, string const & opening, + string const & closing, int fragment_pos, DepTable & head) { smatch what; string::const_iterator first = token.begin(); @@ -1033,6 +1325,15 @@ int iterateLine(string const & token, regex const & reg, string const & closing, // since we had a closing bracket, // do not investigate further fragment = false; + } else if (what.str(2) == opening) { + // if we have another opening bracket, + // we might have a nested file chain + // as is (file.ext (subfile.ext)) + fragment = !handleFoundFile(rtrim(what.str(1)), head); + // decrease first position by one in order to + // consider the opening delimiter on next iteration + if (first > token.begin()) + --first; } else // if we have no closing bracket, // try to handle as file nevertheless @@ -1077,7 +1378,7 @@ int iterateLine(string const & token, regex const & reg, string const & closing, return result; } -} // anon namespace +} // namespace void LaTeX::deplog(DepTable & head) @@ -1091,14 +1392,16 @@ void LaTeX::deplog(DepTable & head) static regex const reg1("File: (.+).*"); static regex const reg2("No file (.+)(.).*"); - static regex const reg3("\\\\openout[0-9]+.*=.*`(.+)(..).*"); + static regex const reg3a("\\\\openout[0-9]+.*=.*`(.+)(..).*"); + // LuaTeX has a slightly different output + static regex const reg3b("\\\\openout[0-9]+.*=\\s*(.+)"); // If an index should be created, MikTex does not write a line like // \openout# = 'sample.idx'. // but instead only a line like this into the log: // Writing index file sample.idx static regex const reg4("Writing index file (.+).*"); static regex const regoldnomencl("Writing glossary file (.+).*"); - static regex const regnomencl("Writing nomenclature file (.+).*"); + static regex const regnomencl(".*Writing nomenclature file (.+).*"); // If a toc should be created, MikTex does not write a line like // \openout# = `sample.toc'. // but only a line like this into the log: @@ -1184,14 +1487,22 @@ void LaTeX::deplog(DepTable & head) else // we suspect a line break fragment = true; - // (3) "\openout = `file.ext'." - } else if (regex_match(token, sub, reg3)) { + // (3)(a) "\openout = `file.ext'." + } else if (regex_match(token, sub, reg3a)) { // search for closing '. at the end of the line if (sub.str(2) == "\'.") fragment = !handleFoundFile(sub.str(1), head); else // potential fragment fragment = true; + // (3)(b) "\openout = file.ext" (LuaTeX) + } else if (regex_match(token, sub, reg3b)) { + // file names must contains a dot + if (contains(sub.str(1), '.')) + fragment = !handleFoundFile(sub.str(1), head); + else + // potential fragment + fragment = true; // (4) "Writing index file file.ext" } else if (regex_match(token, sub, reg4)) // fragmential file name? @@ -1216,7 +1527,7 @@ void LaTeX::deplog(DepTable & head) if (regex_match(token, sub, reg5)) { // search for strings in <...> static regex const reg5_1("<([^>]+)(.)"); - fragment_pos = iterateLine(token, reg5_1, ">", + fragment_pos = iterateLine(token, reg5_1, "<", ">", fragment_pos, head); fragment = (fragment_pos != -1); } @@ -1229,7 +1540,7 @@ void LaTeX::deplog(DepTable & head) if (regex_match(token, sub, reg6)) { // search for strings in (...) static regex const reg6_1("\\(([^()]+)(.)"); - fragment_pos = iterateLine(token, reg6_1, ")", + fragment_pos = iterateLine(token, reg6_1, "(", ")", fragment_pos, head); fragment = (fragment_pos != -1); } @@ -1251,7 +1562,7 @@ void LaTeX::deplog(DepTable & head) int LaTeX::scanBlgFile(DepTable & dep, TeXErrors & terr) { FileName const blg_file(changeExtension(file.absFileName(), "blg")); - LYXERR(Debug::LATEX, "Scanning blg file: " << blg_file); + LYXERR(Debug::OUTFILE, "Scanning blg file: " << blg_file); ifstream ifs(blg_file.toFilesystemEncoding().c_str()); string token; @@ -1274,7 +1585,7 @@ int LaTeX::scanBlgFile(DepTable & dep, TeXErrors & terr) if (regex_match(token, sub, reg1)) { string data = sub.str(3); if (!data.empty()) { - LYXERR(Debug::LATEX, "Found bib file: " << data); + LYXERR(Debug::OUTFILE, "Found bib file: " << data); handleFoundFile(data, dep); } } @@ -1284,32 +1595,31 @@ int LaTeX::scanBlgFile(DepTable & dep, TeXErrors & terr) || regex_match(token, sub, bibtexError5)) { retval |= BIBTEX_ERROR; string errstr = N_("BibTeX error: ") + token; - string message; + string msg; if ((prefixIs(token, "while executing---line") || prefixIs(token, "---line ") || prefixIs(token, "*Please notify the BibTeX")) && !prevtoken.empty()) { errstr = N_("BibTeX error: ") + prevtoken; - message = prevtoken + '\n'; + msg = prevtoken + '\n'; } - message += token; + msg += token; terr.insertError(0, from_local8bit(errstr), - from_local8bit(message)); + from_local8bit(msg)); } else if (regex_match(prevtoken, sub, bibtexError3)) { retval |= BIBTEX_ERROR; string errstr = N_("BibTeX error: ") + prevtoken; - string message = prevtoken + '\n' + token; + string msg = prevtoken + '\n' + token; terr.insertError(0, from_local8bit(errstr), - from_local8bit(message)); + from_local8bit(msg)); } else if (regex_match(token, sub, biberError)) { retval |= BIBTEX_ERROR; string errstr = N_("Biber error: ") + sub.str(2); - string message = token; terr.insertError(0, from_local8bit(errstr), - from_local8bit(message)); + from_local8bit(token)); } prevtoken = token; } @@ -1317,4 +1627,40 @@ int LaTeX::scanBlgFile(DepTable & dep, TeXErrors & terr) } +int LaTeX::scanIlgFile(TeXErrors & terr) +{ + FileName const ilg_file(changeExtension(file.absFileName(), "ilg")); + LYXERR(Debug::OUTFILE, "Scanning ilg file: " << ilg_file); + + ifstream ifs(ilg_file.toFilesystemEncoding().c_str()); + string token; + int retval = NO_ERRORS; + + string prevtoken; + while (getline(ifs, token)) { + token = rtrim(token, "\r"); + if (prefixIs(token, "!! ")) + prevtoken = token; + else if (!prevtoken.empty()) { + retval |= INDEX_ERROR; + string errstr = N_("Makeindex error: ") + prevtoken; + string msg = prevtoken + '\n'; + msg += token; + terr.insertError(0, + from_local8bit(errstr), + from_local8bit(msg)); + prevtoken.clear(); + } else if (prefixIs(token, "ERROR: ")) { + retval |= BIBTEX_ERROR; + string errstr = N_("Xindy error: ") + token.substr(6); + terr.insertError(0, + from_local8bit(errstr), + from_local8bit(token)); + } + } + return retval; +} + + + } // namespace lyx