"Fossies" - the Fresh Open Source Software Archive

Member "texstudio-3.1.1/src/latexdocument.cpp" (21 Feb 2021, 111763 Bytes) of package /linux/misc/texstudio-3.1.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "latexdocument.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.1.0_vs_3.1.1.

    1 #include "latexdocument.h"
    2 #include "latexeditorview.h"
    3 #include "qdocument.h"
    4 #include "qformatscheme.h"
    5 #include "qlanguagedefinition.h"
    6 #include "qdocumentline.h"
    7 #include "qdocumentline_p.h"
    8 #include "qdocumentcursor.h"
    9 #include "qeditor.h"
   10 #include "latexcompleter.h"
   11 #include "latexcompleter_config.h"
   12 #include "configmanagerinterface.h"
   13 #include "smallUsefulFunctions.h"
   14 #include "latexparser/latexparsing.h"
   15 #include <QtConcurrent>
   16 
   17 
   18 //FileNamePair::FileNamePair(const QString& rel):relative(rel){};
   19 FileNamePair::FileNamePair(const QString &rel, const QString &abs): relative(rel), absolute(abs) {}
   20 UserCommandPair::UserCommandPair(const QString &name, const CodeSnippet &snippet): name(name), snippet(snippet) {}
   21 
   22 // languages for LaTeX syntax checking (exact name from qnfa file)
   23 const QSet<QString> LatexDocument::LATEX_LIKE_LANGUAGES = QSet<QString>() << "(La)TeX" << "Pweave" << "Sweave" << "TeX dtx file";
   24 /*! \brief constructor
   25  * sets up structure for structure view
   26  * starts the syntax checker in a separate thread
   27  */
   28 LatexDocument::LatexDocument(QObject *parent): QDocument(parent), remeberAutoReload(false), mayHaveDiffMarkers(false), edView(nullptr), mAppendixLine(nullptr), mBeyondEnd(nullptr)
   29 {
   30     baseStructure = new StructureEntry(this, StructureEntry::SE_DOCUMENT_ROOT);
   31     magicCommentList = new StructureEntry(this, StructureEntry::SE_OVERVIEW);
   32     labelList = new StructureEntry(this, StructureEntry::SE_OVERVIEW);
   33     todoList = new StructureEntry(this, StructureEntry::SE_OVERVIEW);
   34     bibTeXList = new StructureEntry(this, StructureEntry::SE_OVERVIEW);
   35     blockList = new StructureEntry(this, StructureEntry::SE_OVERVIEW);
   36 
   37     magicCommentList->title = tr("MAGIC_COMMENTS");
   38     labelList->title = tr("LABELS");
   39     todoList->title = tr("TODO");
   40     bibTeXList->title = tr("BIBLIOGRAPHY");
   41     blockList->title = tr("BLOCKS");
   42     mLabelItem.clear();
   43     mBibItem.clear();
   44     mUserCommandList.clear();
   45     mMentionedBibTeXFiles.clear();
   46     masterDocument = nullptr;
   47     this->parent = nullptr;
   48 
   49     unclosedEnv.id = -1;
   50     syntaxChecking = true;
   51 
   52     lp = LatexParser::getInstance();
   53 
   54     SynChecker.setLtxCommands(LatexParser::getInstance());
   55     updateSettings();
   56     SynChecker.start();
   57 
   58     connect(&SynChecker, SIGNAL(checkNextLine(QDocumentLineHandle *, bool, int, int)), SLOT(checkNextLine(QDocumentLineHandle *, bool, int, int)), Qt::QueuedConnection);
   59 }
   60 
   61 LatexDocument::~LatexDocument()
   62 {
   63     SynChecker.stop();
   64     SynChecker.wait();
   65     if (!magicCommentList->parent) delete magicCommentList;
   66     if (!labelList->parent) delete labelList;
   67     if (!todoList->parent) delete todoList;
   68     if (!bibTeXList->parent) delete bibTeXList;
   69     if (!blockList->parent) delete blockList;
   70 
   71     foreach (QDocumentLineHandle *dlh, mLineSnapshot) {
   72         dlh->deref();
   73     }
   74     mLineSnapshot.clear();
   75 
   76     delete baseStructure;
   77 }
   78 
   79 void LatexDocument::setFileName(const QString &fileName)
   80 {
   81     //clear all references to old editor
   82     if (this->edView) {
   83         StructureEntryIterator iter(baseStructure);
   84         while (iter.hasNext()) iter.next()->setLine(nullptr);
   85     }
   86 
   87     this->setFileNameInternal(fileName);
   88     this->edView = nullptr;
   89 }
   90 
   91 void LatexDocument::setEditorView(LatexEditorView *edView)
   92 {
   93     this->setFileNameInternal(edView->editor->fileName(), edView->editor->fileInfo());
   94     this->edView = edView;
   95     if (baseStructure) {
   96         baseStructure->title = fileName;
   97         emit updateElement(baseStructure);
   98     }
   99 }
  100 
  101 /// Set the values of this->fileName and this->this->fileInfo
  102 /// Note: QFileInfo is cached, so the performance cost to recreate
  103 /// QFileInfo (instead of copying it from edView) should be very small.
  104 void LatexDocument::setFileNameInternal(const QString &fileName)
  105 {
  106     setFileNameInternal(fileName, QFileInfo(fileName));
  107 }
  108 /// Set the values of this->fileName and this->this->fileInfo
  109 void LatexDocument::setFileNameInternal(const QString &fileName, const QFileInfo& pairedFileInfo)
  110 {
  111     Q_ASSERT(fileName.isEmpty() || pairedFileInfo.isAbsolute());
  112     this->fileName = fileName;
  113     QFileInfo info = getNonSymbolicFileInfo(pairedFileInfo);
  114     this->fileInfo = info;
  115 }
  116 
  117 LatexEditorView *LatexDocument::getEditorView() const
  118 {
  119     return this->edView;
  120 }
  121 
  122 QString LatexDocument::getFileName() const
  123 {
  124     return fileName;
  125 }
  126 
  127 bool LatexDocument::isHidden()
  128 {
  129     return parent->hiddenDocuments.contains(this);
  130 }
  131 
  132 QFileInfo LatexDocument::getFileInfo() const
  133 {
  134     return fileInfo;
  135 }
  136 
  137 QMultiHash<QDocumentLineHandle *, FileNamePair> &LatexDocument::mentionedBibTeXFiles()
  138 {
  139     return mMentionedBibTeXFiles;
  140 }
  141 
  142 const QMultiHash<QDocumentLineHandle *, FileNamePair> &LatexDocument::mentionedBibTeXFiles() const
  143 {
  144     return mMentionedBibTeXFiles;
  145 }
  146 
  147 QStringList LatexDocument::listOfMentionedBibTeXFiles() const
  148 {
  149     QStringList result;
  150     foreach (const FileNamePair &fnp, mMentionedBibTeXFiles.values())
  151         result << fnp.absolute;
  152     return result;
  153 }
  154 /*! select a complete section with the text
  155  * this method is called from structureview via contex menu
  156  *
  157  */
  158 QDocumentSelection LatexDocument::sectionSelection(StructureEntry *section)
  159 {
  160     QDocumentSelection result = { -1, -1, -1, -1};
  161 
  162     if (section->type != StructureEntry::SE_SECTION) return result;
  163     int startLine = section->getRealLineNumber();
  164 
  165     // find next section or higher
  166     StructureEntry *parent;
  167     int index;
  168     do {
  169         parent = section->parent;
  170         if (parent) {
  171             index = section->getRealParentRow();
  172             section = parent;
  173         } else index = -1;
  174     } while ((index >= 0) && (index >= parent->children.count() - 1) && (parent->type == StructureEntry::SE_SECTION));
  175 
  176     int endingLine = -1;
  177     if (index >= 0 && index < parent->children.count() - 1) {
  178         endingLine = parent->children.at(index + 1)->getRealLineNumber();
  179     } else {
  180         // no ending section but end of document
  181         endingLine = findLineContaining("\\end{document}", startLine, Qt::CaseInsensitive);
  182         if (endingLine < 0) endingLine = lines();
  183     }
  184 
  185     result.startLine = startLine;
  186     result.endLine = endingLine;
  187     result.end = 0;
  188     result.start = 0;
  189     return result;
  190 }
  191 
  192 class LatexStructureMerger{
  193 public:
  194     LatexStructureMerger (LatexDocument* document, int maxDepth):
  195         document(document), parent_level(maxDepth)
  196     {
  197     }
  198 
  199 protected:
  200     LatexDocument* document;
  201     QVector<StructureEntry *> parent_level;
  202     void updateParentVector(StructureEntry *se);
  203     void moveToAppropiatePositionWithSignal(StructureEntry *se);
  204 };
  205 
  206 
  207 class LatexStructureMergerMerge: public LatexStructureMerger{
  208 public:
  209         LatexStructureMergerMerge (LatexDocument* doc, int maxDepth, int linenr, int count):
  210             LatexStructureMerger(doc, maxDepth), linenr(linenr), count(count), flatStructure(nullptr)
  211     {
  212     }
  213     void operator ()(QList<StructureEntry *> &flatStructure){
  214         this->flatStructure = &flatStructure;
  215         parent_level.fill(document->baseStructure);
  216         mergeStructure(document->baseStructure);
  217     }
  218 private:
  219     const int linenr;
  220     const int count;
  221     QList<StructureEntry *> *flatStructure;
  222     void mergeStructure(StructureEntry *se);
  223     void mergeChildren(StructureEntry *se, int start = 0);
  224 };
  225 
  226 
  227 
  228 /*! clear all internal data
  229  * preparation for rebuilding structure or for first parsing
  230  */
  231 void LatexDocument::initClearStructure()
  232 {
  233     mUserCommandList.clear();
  234     mLabelItem.clear();
  235     mBibItem.clear();
  236     mRefItem.clear();
  237     mMentionedBibTeXFiles.clear();
  238 
  239     mAppendixLine = nullptr;
  240     mBeyondEnd = nullptr;
  241 
  242 
  243     emit structureUpdated(this, nullptr);
  244 
  245     const int CATCOUNT = 5;
  246     StructureEntry *categories[CATCOUNT] = {magicCommentList, labelList, todoList, bibTeXList, blockList};
  247     for (int i = 0; i < CATCOUNT; i++)
  248         if (categories[i]->parent == baseStructure) {
  249             removeElementWithSignal(categories[i]);
  250             foreach (StructureEntry *se, categories[i]->children)
  251                 delete se;
  252             categories[i]->children.clear();
  253         }
  254 
  255     for (int i = 0; i < baseStructure->children.length(); i++) {
  256         StructureEntry *temp = baseStructure->children[i];
  257         removeElementWithSignal(temp);
  258         delete temp;
  259     }
  260 
  261     baseStructure->title = fileName;
  262 }
  263 /*! rebuild structure view completely
  264  *  /note very expensive call
  265  */
  266 void LatexDocument::updateStructure()
  267 {
  268     initClearStructure();
  269 
  270     patchStructure(0, -1);
  271 
  272     emit structureLost(this);
  273 }
  274 
  275 /*! Removes a deleted line from the structure view
  276 */
  277 void LatexDocument::patchStructureRemoval(QDocumentLineHandle *dlh, int hint)
  278 {
  279     if (!baseStructure) return;
  280     bool completerNeedsUpdate = false;
  281     bool bibTeXFilesNeedsUpdate = false;
  282     bool updateSyntaxCheck = false;
  283     if (mLabelItem.contains(dlh)) {
  284         QList<ReferencePair> labels = mLabelItem.values(dlh);
  285         completerNeedsUpdate = true;
  286         mLabelItem.remove(dlh);
  287         foreach (const ReferencePair &rp, labels)
  288             updateRefsLabels(rp.name);
  289     }
  290     mRefItem.remove(dlh);
  291     if (mMentionedBibTeXFiles.remove(dlh))
  292         bibTeXFilesNeedsUpdate = true;
  293     if (mBibItem.contains(dlh)) {
  294         mBibItem.remove(dlh);
  295         bibTeXFilesNeedsUpdate = true;
  296     }
  297 
  298     QList<UserCommandPair> commands = mUserCommandList.values(dlh);
  299     foreach (UserCommandPair elem, commands) {
  300         QString word = elem.snippet.word;
  301         if(word.length()==1){
  302             for (auto i:ltxCommands.possibleCommands["%columntypes"]) {
  303             if(i.left(1)==word){
  304                 ltxCommands.possibleCommands["%columntypes"].remove(i);
  305                 break;
  306             }
  307             }
  308         }else{
  309             int i = word.indexOf("{");
  310             if (i >= 0) word = word.left(i);
  311             ltxCommands.possibleCommands["user"].remove(word);
  312         }
  313         updateSyntaxCheck = true;
  314     }
  315     mUserCommandList.remove(dlh);
  316 
  317     QStringList removeIncludes = mIncludedFilesList.values(dlh);
  318     if (mIncludedFilesList.remove(dlh) > 0) {
  319         parent->removeDocs(removeIncludes);
  320         parent->updateMasterSlaveRelations(this);
  321     }
  322 
  323     QStringList removedUsepackages;
  324     removedUsepackages << mUsepackageList.values(dlh);
  325     mUsepackageList.remove(dlh);
  326 
  327     if (dlh == mAppendixLine) {
  328         updateContext(mAppendixLine, nullptr, StructureEntry::InAppendix);
  329         mAppendixLine = nullptr;
  330     }
  331 
  332     int linenr = indexOf(dlh,hint);
  333     if (linenr == -1) linenr = lines();
  334 
  335     // check if line contains bookmark
  336     if (edView) {
  337         int id=edView->hasBookmark(dlh);
  338         if (id>-2) {
  339             emit bookmarkRemoved(dlh);
  340             edView->removeBookmark(dlh, id);
  341         }
  342     }
  343 
  344     QList<StructureEntry *> categories = QList<StructureEntry *>() << magicCommentList << labelList << todoList << blockList << bibTeXList;
  345     foreach (StructureEntry *sec, categories) {
  346         int l = 0;
  347         QMutableListIterator<StructureEntry *> iter(sec->children);
  348         while (iter.hasNext()) {
  349             StructureEntry *se = iter.next();
  350             if (dlh == se->getLineHandle()) {
  351                 emit removeElement(se, l);
  352                 iter.remove();
  353                 emit removeElementFinished();
  354                 delete se;
  355             } else l++;
  356         }
  357     }
  358 
  359     QList<StructureEntry *> tmp;
  360     if(!baseStructure->children.isEmpty()){ // merge is not the fastest, even when there is nothing to merge
  361         LatexStructureMergerMerge(this, LatexParser::getInstance().structureDepth(), linenr, 1)(tmp);
  362     }
  363 
  364     // rehighlight current cursor position
  365     StructureEntry *newSection = nullptr;
  366     if (edView) {
  367         int i = edView->editor->cursor().lineNumber();
  368         if (i >= 0) {
  369             newSection = findSectionForLine(i);
  370         }
  371     }
  372 
  373     emit structureUpdated(this, newSection);
  374     //emit setHighlightedEntry(newSection);
  375 
  376     if (bibTeXFilesNeedsUpdate)
  377         emit updateBibTeXFiles();
  378 
  379     if (completerNeedsUpdate || bibTeXFilesNeedsUpdate)
  380         emit updateCompleter();
  381 
  382     if (!removedUsepackages.isEmpty() || updateSyntaxCheck) {
  383         updateCompletionFiles(updateSyntaxCheck);
  384     }
  385 
  386 }
  387 
  388 // workaround to prevent false command recognition in definitions:
  389 // Example: In \newcommand{\seeref}[1]{\ref{(see #1)}} the argument of \ref is not actually a label.
  390 // Using this function we detect this case.
  391 // TODO: a more general solution should make this dependent on if the command is inside a definition.
  392 // However this requires a restructuring of the patchStructure. It would also allow categorizing
  393 // the redefined command, e.g. as "%ref"
  394 inline bool isDefinitionArgument(const QString &arg)
  395 {
  396     // equivalent to checking the regexp #[0-9], but faster:
  397     int pos = arg.indexOf("#");
  398     return (pos >= 0 && pos < arg.length() - 1 && arg[pos + 1].isDigit());
  399 }
  400 
  401 /*!
  402  * \brief parse lines to update syntactical and structure information
  403  *
  404  * updates structure informationen from the changed lines only
  405  * parses the lines to gather syntactical information on the latex content
  406  * e.g. find labels/references, new command definitions etc.
  407  * the syntax parsing has been largely changed to the token system which is tranlated here for faster information extraction \see Tokens
  408  * \param linenr first line to check
  409  * \param count number of lines to check (-1: all)
  410  * \param recheck method has been called a second time to handle profound syntax changes from first call (like newly loaded packages). This allows to avoid some costly operations on the second call.
  411  * \return true means a second run is suggested as packages are loadeed which change the outcome
  412  *         e.g. definition of specialDef command, but packages are load at the end of this method.
  413  */
  414 bool LatexDocument::patchStructure(int linenr, int count, bool recheck)
  415 {
  416     /* true means a second run is suggested as packages are loadeed which change the outcome
  417      * e.g. definition of specialDef command, but packages are load at the end of this method.
  418      */
  419     //qDebug()<<"begin Patch"<<QTime::currentTime().toString("HH:mm:ss:zzz");
  420 
  421     if (!parent->patchEnabled())
  422         return false;
  423 
  424     if (!baseStructure) return false;
  425 
  426     static QRegExp rxMagicTexComment("^%\\ ?!T[eE]X");
  427     static QRegExp rxMagicBibComment("^%\\ ?!BIB");
  428 
  429     bool reRunSuggested = false;
  430     bool recheckLabels = true;
  431     if (count < 0) {
  432         count = lineCount();
  433         recheckLabels = false;
  434     }
  435 
  436     emit toBeChanged();
  437 
  438     bool completerNeedsUpdate = false;
  439     bool bibTeXFilesNeedsUpdate = false;
  440     bool bibItemsChanged = false;
  441 
  442     QDocumentLineHandle *oldLine = mAppendixLine; // to detect a change in appendix position
  443     QDocumentLineHandle *oldLineBeyond = mBeyondEnd; // to detect a change in end document position
  444     // get remainder
  445     TokenStack remainder;
  446     int lineNrStart = linenr;
  447     int newCount = count;
  448     if (linenr > 0) {
  449         QDocumentLineHandle *previous = line(linenr - 1).handle();
  450         remainder = previous->getCookieLocked(QDocumentLine::LEXER_REMAINDER_COOKIE).value<TokenStack >();
  451         if (!remainder.isEmpty() && remainder.top().subtype != Token::none) {
  452             QDocumentLineHandle *lh = remainder.top().dlh;
  453             lineNrStart = lh->document()->indexOf(lh);
  454             if (linenr - lineNrStart > 10) // limit search depth
  455                 lineNrStart = linenr;
  456         }
  457     }
  458     bool updateSyntaxCheck = false;
  459     QList<StructureEntry *> flatStructure;
  460 
  461     // usepackage list
  462     QStringList removedUsepackages;
  463     QStringList addedUsepackages;
  464     QStringList removedUserCommands, addedUserCommands;
  465     QStringList lstFilesToLoad;
  466     //first pass: lex
  467     TokenStack oldRemainder;
  468     CommandStack oldCommandStack;
  469     if (!recheck) {
  470         QList<QDocumentLineHandle *> l_dlh;
  471 //#pragma omp parallel for shared(l_dlh)
  472         for (int i = linenr; i < linenr + count; i++) {
  473             l_dlh << line(i).handle();
  474             //Parsing::simpleLexLatexLine(line(i).handle());
  475         }
  476         QtConcurrent::blockingMap(l_dlh,Parsing::simpleLexLatexLine);
  477     }
  478     QDocumentLineHandle *lastHandle = line(linenr - 1).handle();
  479     if (lastHandle) {
  480         oldRemainder = lastHandle->getCookieLocked(QDocumentLine::LEXER_REMAINDER_COOKIE).value<TokenStack >();
  481         oldCommandStack = lastHandle->getCookieLocked(QDocumentLine::LEXER_COMMANDSTACK_COOKIE).value<CommandStack >();
  482     }
  483     int stoppedAtLine=-1;
  484     for (int i = linenr; i < lineCount() && i < linenr + count; i++) {
  485         if (line(i).text() == "\\begin{document}"){
  486             if(linenr==0 && count==lineCount() && !recheck) {
  487                 stoppedAtLine=i;
  488                 break; // do recheck quickly as usepackages probably need to be loaded
  489             }
  490         }
  491         bool remainderChanged = Parsing::latexDetermineContexts2(line(i).handle(), oldRemainder, oldCommandStack, lp);
  492         if (remainderChanged && i + 1 == linenr + count && i + 1 < lineCount()) { // remainder changed in last line which is to be checked
  493             count++; // check also next line ...
  494         }
  495     }
  496     if (linenr >= lineNrStart) {
  497         newCount = linenr + count - lineNrStart;
  498     }
  499     // Note: We cannot re-use the structure elements in the updated area because if there are multiple same-type elements on the same line
  500     // and the user has changed their order, re-using these elements would not update their order and this would break updates of any
  501     // QPersistentModelIndex'es that point to these elements in the structure tree view. That is why we remove all the structure elements
  502     // within the updated area and then just add anew any structure elements that we find in the updated area.
  503 
  504     QList<StructureEntry *> removedMagicComments;
  505     int posMagicComment = findStructureParentPos(magicCommentList->children, removedMagicComments, lineNrStart, newCount);
  506 
  507     QList<StructureEntry *> removedLabels;
  508     int posLabel = findStructureParentPos(labelList->children, removedLabels, lineNrStart, newCount);
  509 
  510     QList<StructureEntry *> removedTodo;
  511     int posTodo = findStructureParentPos(todoList->children, removedTodo, lineNrStart, newCount);
  512 
  513     QList<StructureEntry *> removedBlock;
  514     int posBlock = findStructureParentPos(blockList->children, removedBlock, lineNrStart, newCount);
  515 
  516     QList<StructureEntry *> removedBibTeX;
  517     int posBibTeX = findStructureParentPos(bibTeXList->children, removedBibTeX, lineNrStart, newCount);
  518 
  519     bool isLatexLike = languageIsLatexLike();
  520     //updateSubsequentRemaindersLatex(this,linenr,count,lp);
  521     // force command from all line of which the actual line maybe subsequent lines (multiline commands)
  522     for (int i = lineNrStart; i < linenr + count; i++) {
  523         //update bookmarks
  524         if (edView && edView->hasBookmark(i, -1)) {
  525             emit bookmarkLineUpdated(i);
  526         }
  527 
  528         if (!isLatexLike) continue;
  529 
  530         QString curLine = line(i).text();
  531         QDocumentLineHandle *dlh = line(i).handle();
  532         if (!dlh)
  533             continue; //non-existing line ...
  534 
  535         // remove command,bibtex,labels at from this line
  536         QList<UserCommandPair> commands = mUserCommandList.values(dlh);
  537         foreach (UserCommandPair cmd, commands) {
  538             QString elem = cmd.snippet.word;
  539             if(elem.length()==1){
  540                 for (auto i:ltxCommands.possibleCommands["%columntypes"]) {
  541                 if(i.left(1)==elem){
  542                     ltxCommands.possibleCommands["%columntypes"].remove(i);
  543                     break;
  544                 }
  545                 }
  546             }else{
  547                 int i = elem.indexOf("{");
  548                 if (i >= 0) elem = elem.left(i);
  549                 ltxCommands.possibleCommands["user"].remove(elem);
  550             }
  551             if(cmd.snippet.type==CodeSnippet::userConstruct)
  552                 continue;
  553             removedUserCommands << elem;
  554             //updateSyntaxCheck=true;
  555         }
  556         if (mLabelItem.contains(dlh)) {
  557             QList<ReferencePair> labels = mLabelItem.values(dlh);
  558             completerNeedsUpdate = true;
  559             mLabelItem.remove(dlh);
  560             foreach (const ReferencePair &rp, labels)
  561                 updateRefsLabels(rp.name);
  562         }
  563         mRefItem.remove(dlh);
  564         QStringList removedIncludes = mIncludedFilesList.values(dlh);
  565         mIncludedFilesList.remove(dlh);
  566 
  567         if (mUserCommandList.remove(dlh) > 0) completerNeedsUpdate = true;
  568         if (mBibItem.remove(dlh))
  569             bibTeXFilesNeedsUpdate = true;
  570 
  571         removedUsepackages << mUsepackageList.values(dlh);
  572         if (mUsepackageList.remove(dlh) > 0) completerNeedsUpdate = true;
  573 
  574         //remove old bibs files from hash, but keeps a temporary copy
  575         QStringList oldBibs;
  576         while (mMentionedBibTeXFiles.contains(dlh)) {
  577             QMultiHash<QDocumentLineHandle *, FileNamePair>::iterator it = mMentionedBibTeXFiles.find(dlh);
  578             Q_ASSERT(it.key() == dlh);
  579             Q_ASSERT(it != mMentionedBibTeXFiles.end());
  580             if (it == mMentionedBibTeXFiles.end()) break;
  581             oldBibs.append(it.value().relative);
  582             mMentionedBibTeXFiles.erase(it);
  583         }
  584 
  585         // handle special comments (TODO, MAGIC comments)
  586         // comment detection moved to lexer as formats are not yet generated here (e.g. on first load)
  587         QPair<int,int> commentStart = dlh->getCookieLocked(QDocumentLine::LEXER_COMMENTSTART_COOKIE).value<QPair<int,int> >();
  588         int col = commentStart.first;
  589         if (col >= 0) {
  590             // all
  591             //// TODO marker
  592             QString text = curLine.mid(col);
  593             QString regularExpression=ConfigManagerInterface::getInstance()->getOption("Editor/todo comment regExp").toString();
  594             QRegExp rx(regularExpression);
  595             if (rx.indexIn(text)==0) {  // other todos like \todo are handled by the tokenizer below.
  596                 StructureEntry *newTodo = new StructureEntry(this, StructureEntry::SE_TODO);
  597                 newTodo->title = text.mid(1).trimmed();
  598                 newTodo->setLine(line(i).handle(), i);
  599                 insertElementWithSignal(todoList, posTodo++, newTodo);
  600                 // save comment type into cookie
  601                 commentStart.second=Token::todoComment;
  602                 dlh->setCookie(QDocumentLine::LEXER_COMMENTSTART_COOKIE, QVariant::fromValue<QPair<int,int> >(commentStart));
  603             }
  604             //// parameter comment
  605             if (curLine.startsWith("%&")) {
  606                 int start = curLine.indexOf("-job-name=");
  607                 if (start >= 0) {
  608                     int end = start + 10; // += "-job-name=".length;
  609                     if (end < curLine.length() && curLine[end] == '"') {
  610                         // quoted filename
  611                         end = curLine.indexOf('"', end + 1);
  612                         if (end >= 0) {
  613                             end += 1;  // include closing quotation mark
  614                             addMagicComment(curLine.mid(start, end - start), i, posMagicComment++);
  615                         }
  616                     } else {
  617                         end = curLine.indexOf(' ', end + 1);
  618                         if (end >= 0) {
  619                             addMagicComment(curLine.mid(start, end - start), i, posMagicComment++);
  620                         } else {
  621                             addMagicComment(curLine.mid(start), i, posMagicComment++);
  622                         }
  623                     }
  624                 }
  625                 commentStart.second=Token::magicComment;
  626                 dlh->setCookie(QDocumentLine::LEXER_COMMENTSTART_COOKIE, QVariant::fromValue<QPair<int,int> >(commentStart));
  627             }
  628             //// magic comment
  629             if (rxMagicTexComment.indexIn(text) == 0) {
  630                 addMagicComment(text.mid(rxMagicTexComment.matchedLength()).trimmed(), i, posMagicComment++);
  631                 commentStart.second=Token::magicComment;
  632                 dlh->setCookie(QDocumentLine::LEXER_COMMENTSTART_COOKIE, QVariant::fromValue<QPair<int,int> >(commentStart));
  633             } else if (rxMagicBibComment.indexIn(text) == 0) {
  634                 // workaround to also support "% !BIB program = biber" syntax used by TeXShop and TeXWorks
  635                 text = text.mid(rxMagicBibComment.matchedLength()).trimmed();
  636                 QString name;
  637                 QString val;
  638                 splitMagicComment(text, name, val);
  639                 if ((name == "TS-program" || name == "program") && (val == "biber" || val == "bibtex" || val == "bibtex8")) {
  640                     addMagicComment(QString("TXS-program:bibliography = txs:///%1").arg(val), i, posMagicComment++);
  641                     commentStart.second=Token::magicComment;
  642                     dlh->setCookie(QDocumentLine::LEXER_COMMENTSTART_COOKIE, QVariant::fromValue<QPair<int,int> >(commentStart));
  643                 }
  644             }
  645         }
  646 
  647         // check also in command argument, als references might be put there as well...
  648         //// Appendix keyword
  649         if (curLine == "\\appendix") {
  650             oldLine = mAppendixLine;
  651             mAppendixLine = line(i).handle();
  652 
  653         }
  654         if (line(i).handle() == mAppendixLine && curLine != "\\appendix") {
  655             oldLine = mAppendixLine;
  656             mAppendixLine = nullptr;
  657         }
  658         /// \begin{document}
  659         /// break patchStructure at begin{document} since added usepackages need to be loaded and then the text needs to be checked
  660         /// only useful when loading a complete new text.
  661         if (curLine == "\\begin{document}"){
  662             if(linenr==0 && count==lineCount() && !recheck) {
  663                 if(!addedUsepackages.isEmpty()){
  664                     break; // do recheck quickly as usepackages probably need to be loaded
  665                 }else{
  666                     // oops, complete tokenlist needed !
  667                     // redo on time
  668                     for (int i = stoppedAtLine; i < lineCount(); i++) {
  669                         Parsing::latexDetermineContexts2(line(i).handle(), oldRemainder, oldCommandStack, lp);
  670                     }
  671                 }
  672             }
  673         }
  674         /// \end{document} keyword
  675         /// don't add section in structure view after passing \end{document} , this command must not contains spaces nor any additions in the same line
  676         if (curLine == "\\end{document}") {
  677             oldLineBeyond = mBeyondEnd;
  678             mBeyondEnd = line(i).handle();
  679         }
  680         if (line(i).handle() == mBeyondEnd && curLine != "\\end{document}") {
  681             oldLineBeyond = mBeyondEnd;
  682             mBeyondEnd = nullptr;
  683         }
  684 
  685         TokenList tl = dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList >();
  686 
  687         for (int j = 0; j < tl.length(); j++) {
  688             Token tk = tl.at(j);
  689             // break at comment start
  690             if (tk.type == Token::comment)
  691                 break;
  692             // work special args
  693             ////Ref
  694             //for reference counting (can be placed in command options as well ...
  695             if (tk.type == Token::labelRef || tk.type == Token::labelRefList) {
  696                 ReferencePair elem;
  697                 elem.name = tk.getText();
  698                 elem.start = tk.start;
  699                 mRefItem.insert(line(i).handle(), elem);
  700             }
  701 
  702             //// label ////
  703             if (tk.type == Token::label && tk.length > 0) {
  704                 ReferencePair elem;
  705                 elem.name = tk.getText();
  706                 elem.start = tk.start;
  707                 mLabelItem.insert(line(i).handle(), elem);
  708                 completerNeedsUpdate = true;
  709                 StructureEntry *newLabel = new StructureEntry(this, StructureEntry::SE_LABEL);
  710                 newLabel->title = elem.name;
  711                 newLabel->setLine(line(i).handle(), i);
  712                 insertElementWithSignal(labelList, posLabel++, newLabel);
  713             }
  714             //// newtheorem ////
  715             if (tk.type == Token::newTheorem && tk.length > 0) {
  716                 completerNeedsUpdate = true;
  717                 QStringList lst;
  718                 QString firstArg = tk.getText();
  719                 lst << "\\begin{" + firstArg + "}" << "\\end{" + firstArg + "}";
  720                 foreach (const QString &elem, lst) {
  721                     mUserCommandList.insert(line(i).handle(), UserCommandPair(firstArg, elem));
  722                     ltxCommands.possibleCommands["user"].insert(elem);
  723                     if (!removedUserCommands.removeAll(elem)) {
  724                         addedUserCommands << elem;
  725                     }
  726                 }
  727                 continue;
  728             }
  729             /// bibitem ///
  730             if (tk.type == Token::newBibItem && tk.length > 0) {
  731                 ReferencePair elem;
  732                 elem.name = tk.getText();
  733                 elem.start = tk.start;
  734                 mBibItem.insert(line(i).handle(), elem);
  735                 bibItemsChanged = true;
  736                 continue;
  737             }
  738             /// todo ///
  739             if (tk.subtype == Token::todo && (tk.type == Token::braces || tk.type == Token::openBrace)) {
  740                 StructureEntry *newTodo = new StructureEntry(this, StructureEntry::SE_TODO);
  741                 newTodo->title = tk.getInnerText();
  742                 newTodo->setLine(line(i).handle(), i);
  743                 insertElementWithSignal(todoList, posTodo++, newTodo);
  744             }
  745 
  746             // work on general commands
  747             if (tk.type != Token::command && tk.type != Token::commandUnknown)
  748                 continue; // not a command
  749             Token tkCmd;
  750             TokenList args;
  751             QString cmd;
  752             int cmdStart = Parsing::findCommandWithArgsFromTL(tl, tkCmd, args, j, parent->showCommentedElementsInStructure);
  753             if (cmdStart < 0) break;
  754             cmdStart=tkCmd.start; // from here, cmdStart is line column position of command
  755             cmd = curLine.mid(tkCmd.start, tkCmd.length);
  756 
  757             QString firstArg = Parsing::getArg(args, dlh, 0, ArgumentList::Mandatory,true,i);
  758 
  759             //// newcommand ////
  760             if (lp.possibleCommands["%definition"].contains(cmd) || ltxCommands.possibleCommands["%definition"].contains(cmd)) {
  761                 completerNeedsUpdate = true;
  762                 //Tokens cmdName;
  763                 QString cmdName = Parsing::getArg(args, Token::def);
  764                 cmdName.replace("@","@@"); // special treatment for commandnames containing @
  765                 bool isDefWidth = true;
  766                 if (cmdName.isEmpty())
  767                     cmdName = Parsing::getArg(args, Token::defWidth);
  768                 else
  769                     isDefWidth = false;
  770                 //int optionCount = Parsing::getArg(args, dlh, 0, ArgumentList::Optional).toInt(); // results in 0 if there is no optional argument or conversion fails
  771                 int optionCount = Parsing::getArg(args, Token::defArgNumber).toInt(); // results in 0 if there is no optional argument or conversion fails
  772                 if (optionCount > 9 || optionCount < 0) optionCount = 0; // limit number of options
  773                 bool def = !Parsing::getArg(args, Token::optionalArgDefinition).isEmpty();
  774 
  775                 ltxCommands.possibleCommands["user"].insert(cmdName);
  776 
  777                 if (!removedUserCommands.removeAll(cmdName)) {
  778                     addedUserCommands << cmdName;
  779                 }
  780                 QString cmdNameWithoutArgs = cmdName;
  781                 QString cmdNameWithoutOptional = cmdName;
  782                 for (int j = 0; j < optionCount; j++) {
  783                     if (j == 0) {
  784                         if (!def){
  785                             cmdName.append("{%<arg1%|%>}");
  786                             cmdNameWithoutOptional.append("{%<arg1%|%>}");
  787                         } else
  788                             cmdName.append("[%<opt. arg1%|%>]");
  789                         } else {
  790                             cmdName.append(QString("{%<arg%1%>}").arg(j + 1));
  791                             cmdNameWithoutOptional.append(QString("{%<arg%1%>}").arg(j + 1));
  792                         }
  793                 }
  794                 CodeSnippet cs(cmdName);
  795                 cs.index = qHash(cmdName);
  796                 cs.snippetLength = cmdName.length();
  797                 if (isDefWidth)
  798                     cs.type = CodeSnippet::length;
  799                 mUserCommandList.insert(line(i).handle(), UserCommandPair(cmdNameWithoutArgs, cs));
  800                 if(def){ // optional argument, add version without that argument as well
  801                     CodeSnippet cs(cmdNameWithoutOptional);
  802                     cs.index = qHash(cmdNameWithoutOptional);
  803                     cs.snippetLength = cmdNameWithoutOptional.length();
  804                     if (isDefWidth)
  805                         cs.type = CodeSnippet::length;
  806                     mUserCommandList.insert(line(i).handle(), UserCommandPair(cmdNameWithoutArgs, cs));
  807                 }
  808                 // remove obsolete Overlays (maybe this can be refined
  809                 //updateSyntaxCheck=true;
  810                 continue;
  811             }
  812             // special treatment \def
  813             if (cmd == "\\def" || cmd == "\\gdef" || cmd == "\\edef" || cmd == "\\xdef") {
  814                 QString remainder = curLine.mid(cmdStart + cmd.length());
  815                 completerNeedsUpdate = true;
  816                 QRegExp rx("(\\\\\\w+)\\s*([^{%]*)");
  817                 if (rx.indexIn(remainder) > -1) {
  818                     QString name = rx.cap(1);
  819                     QString nameWithoutArgs = name;
  820                     QString optionStr = rx.cap(2);
  821                     //qDebug()<< name << ":"<< optionStr;
  822                     ltxCommands.possibleCommands["user"].insert(name);
  823                     if (!removedUserCommands.removeAll(name)) addedUserCommands << name;
  824                     optionStr = optionStr.trimmed();
  825                     if (optionStr.length()) {
  826                         int lastArg = optionStr[optionStr.length() - 1].toLatin1() - '0';
  827                         if (optionStr.length() == lastArg * 2) { //#1#2#3...
  828                             for (int j = 1; j <= lastArg; j++)
  829                                 if (j == 1) name.append("{%<arg1%|%>}");
  830                                 else name.append(QString("{%<arg%1%>}").arg(j));
  831                         } else {
  832                             QStringList args = optionStr.split('#'); //#1;#2#3:#4 => ["",1;,2,3:,4]
  833                             bool hadSeparator = true;
  834                             for (int i = 1; i < args.length(); i++) {
  835                                 if (args[i].length() == 0) continue; //invalid
  836                                 bool hasSeparator = (args[i].length() != 1); //only single digit variables allowed. last arg also needs a sep
  837                                 if (!hadSeparator || !hasSeparator)
  838                                     args[i] = "{%<arg" + args[i][0] + "%>}" + args[i].mid(1);
  839                                 else
  840                                     args[i] = "%<arg" + args[i][0] + "%>" + args[i].mid(1); //no need to use {} for arguments that are separated anyways
  841                                 hadSeparator  = hasSeparator;
  842                             }
  843                             name.append(args.join(""));
  844                         }
  845                     }
  846                     mUserCommandList.insert(line(i).handle(), UserCommandPair(nameWithoutArgs, name));
  847                     // remove obsolete Overlays (maybe this can be refined
  848                     //updateSyntaxCheck=true;
  849                 }
  850                 continue;
  851             }
  852             if (cmd == "\\newcolumntype") {
  853                 if(firstArg.length()==1){ // only single letter definitions are allowed/handled
  854                     QString secondArg = Parsing::getArg(args, dlh, 1, ArgumentList::Mandatory);
  855                     ltxCommands.possibleCommands["%columntypes"].insert(firstArg+secondArg);
  856                     if (!removedUserCommands.removeAll(firstArg)) {
  857                         addedUserCommands << firstArg;
  858                     }
  859                     mUserCommandList.insert(line(i).handle(), UserCommandPair(QString(), firstArg));
  860                     continue;
  861                 }
  862             }
  863 
  864             //// newenvironment ////
  865             static const QStringList envTokens = QStringList() << "\\newenvironment" << "\\renewenvironment";
  866             if (envTokens.contains(cmd)) {
  867                 completerNeedsUpdate = true;
  868                 TokenList argsButFirst = args;
  869                 if(argsButFirst.isEmpty())
  870                     continue; // no arguments present
  871                 argsButFirst.removeFirst();
  872                 int optionCount = Parsing::getArg(argsButFirst, dlh, 0, ArgumentList::Optional).toInt(); // results in 0 if there is no optional argument or conversion fails
  873                 if (optionCount > 9 || optionCount < 0) optionCount = 0; // limit number of options
  874                 mUserCommandList.insert(line(i).handle(), UserCommandPair(firstArg, "\\end{" + firstArg + "}"));
  875                 QStringList lst;
  876                 lst << "\\begin{" + firstArg + "}" << "\\end{" + firstArg + "}";
  877                 foreach (const QString &elem, lst) {
  878                     ltxCommands.possibleCommands["user"].insert(elem);
  879                     if (!removedUserCommands.removeAll(elem)) {
  880                         addedUserCommands << elem;
  881                     }
  882                 }
  883                 bool hasDefaultArg = !Parsing::getArg(argsButFirst, dlh, 1, ArgumentList::Optional).isNull();
  884                 int mandatoryOptionCount = hasDefaultArg ? optionCount - 1 : optionCount;
  885                 QString mandatoryArgString;
  886                 for (int j = 0; j < mandatoryOptionCount; j++) {
  887                     if (j == 0) mandatoryArgString.append("{%<1%>}");
  888                     else mandatoryArgString.append(QString("{%<%1%>}").arg(j + 1));
  889                 }
  890                 mUserCommandList.insert(line(i).handle(), UserCommandPair(firstArg, "\\begin{" + firstArg + "}" + mandatoryArgString));
  891                 if (hasDefaultArg) {
  892                     mUserCommandList.insert(line(i).handle(), UserCommandPair(firstArg, "\\begin{" + firstArg + "}" + "[%<opt%>]" + mandatoryArgString));
  893                 }
  894                 continue;
  895             }
  896             //// newcounter ////
  897             if (cmd == "\\newcounter") {
  898                 completerNeedsUpdate = true;
  899                 QStringList lst;
  900                 lst << "\\the" + firstArg ;
  901                 foreach (const QString &elem, lst) {
  902                     mUserCommandList.insert(line(i).handle(), UserCommandPair(elem, elem));
  903                     ltxCommands.possibleCommands["user"].insert(elem);
  904                     if (!removedUserCommands.removeAll(elem)) {
  905                         addedUserCommands << elem;
  906                     }
  907                 }
  908                 continue;
  909             }
  910             //// newif ////
  911             if (cmd == "\\newif") {
  912                 // \newif\ifmycondition also defines \myconditiontrue and \myconditionfalse
  913                 completerNeedsUpdate = true;
  914                 QStringList lst;
  915                 lst << firstArg
  916                     << "\\" + firstArg.mid(3) + "false"
  917                     << "\\" + firstArg.mid(3) + "true";
  918                 foreach (const QString &elem, lst) {
  919                     mUserCommandList.insert(line(i).handle(), UserCommandPair(elem, elem));
  920                     ltxCommands.possibleCommands["user"].insert(elem);
  921                     if (!removedUserCommands.removeAll(elem)) {
  922                         addedUserCommands << elem;
  923                     }
  924                 }
  925                 continue;
  926             }
  927             /// specialDefinition ///
  928             /// e.g. definecolor
  929             if (ltxCommands.specialDefCommands.contains(cmd)) {
  930                 if (!args.isEmpty() ) {
  931                     completerNeedsUpdate = true;
  932                     QString definition = ltxCommands.specialDefCommands.value(cmd);
  933                     Token::TokenType type = Token::braces;
  934                     if (definition.startsWith('(')) {
  935                         definition.chop(1);
  936                         definition = definition.mid(1);
  937                         type = Token::bracket;
  938                     }
  939                     if (definition.startsWith('[')) {
  940                         definition.chop(1);
  941                         definition = definition.mid(1);
  942                         type = Token::squareBracket;
  943                     }
  944 
  945                     foreach (Token mTk, args) {
  946                         if (mTk.type != type)
  947                             continue;
  948                         QString elem = mTk.getText();
  949                         elem = elem.mid(1, elem.length() - 2); // strip braces
  950                         mUserCommandList.insert(line(i).handle(), UserCommandPair(QString(), definition + "%" + elem));
  951                         if (!removedUserCommands.removeAll(elem)) {
  952                             addedUserCommands << elem;
  953                         }
  954                         break;
  955                     }
  956                 }
  957             }
  958 
  959             ///usepackage
  960             if (lp.possibleCommands["%usepackage"].contains(cmd)) {
  961                 completerNeedsUpdate = true;
  962                 QStringList packagesHelper = firstArg.split(",");
  963 
  964                 if (cmd.endsWith("theme")) { // special treatment for  \usetheme
  965                     QString preambel = cmd;
  966                     preambel.remove(0, 4);
  967                     preambel.prepend("beamer");
  968                     packagesHelper.replaceInStrings(QRegExp("^"), preambel);
  969                 }
  970 
  971                 QString firstOptArg = Parsing::getArg(args, dlh, 0, ArgumentList::Optional);
  972                 if (cmd == "\\documentclass") {
  973                     //special treatment for documentclass, especially for the class options
  974                     // at the moment a change here soes not automatically lead to an update of corresponding definitions, here babel
  975                     mClassOptions = firstOptArg;
  976                 }
  977 
  978                 if (firstArg == "babel") {
  979                     //special treatment for babel
  980                     if (firstOptArg.isEmpty()) {
  981                         firstOptArg = mClassOptions;
  982                     }
  983                     if (!firstOptArg.isEmpty()) {
  984                         packagesHelper << firstOptArg.split(",");
  985                     }
  986                 }
  987 
  988                 QStringList packages;
  989                 foreach (QString elem, packagesHelper) {
  990                     elem = elem.simplified();
  991                     if (lp.packageAliases.contains(elem))
  992                         packages << lp.packageAliases.values(elem);
  993                     else
  994                         packages << elem;
  995                 }
  996 
  997                 foreach (const QString &elem, packages) {
  998                     if (!removedUsepackages.removeAll(firstOptArg + "#" + elem))
  999                         addedUsepackages << firstOptArg + "#" + elem;
 1000                     mUsepackageList.insert(dlh, firstOptArg + "#" + elem); // hand on option of usepackages for conditional cwl load ..., force load if option is changed
 1001                 }
 1002                 continue;
 1003             }
 1004             //// bibliography ////
 1005             if (lp.possibleCommands["%bibliography"].contains(cmd)) {
 1006                 QStringList additionalBibPaths = ConfigManagerInterface::getInstance()->getOption("Files/Bib Paths").toString().split(getPathListSeparator());
 1007                 QStringList bibs = firstArg.split(',', QString::SkipEmptyParts);
 1008                 //add new bibs and set bibTeXFilesNeedsUpdate if there was any change
 1009                 foreach (const QString &elem, bibs) { //latex doesn't seem to allow any spaces in file names
 1010                     mMentionedBibTeXFiles.insert(line(i).handle(), FileNamePair(elem, getAbsoluteFilePath(elem, "bib", additionalBibPaths)));
 1011                     if (oldBibs.removeAll(elem) == 0)
 1012                         bibTeXFilesNeedsUpdate = true;
 1013                 }
 1014                 //write bib tex in tree
 1015                 foreach (const QString &bibFile, bibs) {
 1016                     StructureEntry *newFile = new StructureEntry(this, StructureEntry::SE_BIBTEX);
 1017                     newFile->title = bibFile;
 1018                     newFile->setLine(line(i).handle(), i);
 1019                     insertElementWithSignal(bibTeXList, posBibTeX++, newFile);
 1020                 }
 1021                 continue;
 1022             }
 1023 
 1024             //// beamer blocks ////
 1025 
 1026             if (cmd == "\\begin" && firstArg == "block") {
 1027                 StructureEntry *newBlock = new StructureEntry(this, StructureEntry::SE_BLOCK);
 1028                 newBlock->title = Parsing::getArg(args, dlh, 1, ArgumentList::Mandatory,true,i);
 1029                 newBlock->setLine(line(i).handle(), i);
 1030                 insertElementWithSignal(blockList, posBlock++, newBlock);
 1031                 continue;
 1032             }
 1033 
 1034             //// include,input,import ////
 1035             if (lp.possibleCommands["%include"].contains(cmd) && !isDefinitionArgument(firstArg)) {
 1036                 StructureEntry *newInclude = new StructureEntry(this, StructureEntry::SE_INCLUDE);
 1037                 newInclude->level = parent && !parent->indentIncludesInStructure ? 0 : lp.structureDepth() - 1;
 1038                 firstArg = removeQuote(firstArg);
 1039                 newInclude->title = firstArg;
 1040                 QString name=firstArg;
 1041                 name.replace("\\string~",QDir::homePath());
 1042                 QString fname = findFileName(name);
 1043                 removedIncludes.removeAll(fname);
 1044                 mIncludedFilesList.insert(line(i).handle(), fname);
 1045                 LatexDocument *dc = parent->findDocumentFromName(fname);
 1046                 if (dc) {
 1047                     childDocs.insert(dc);
 1048                     dc->setMasterDocument(this, recheckLabels);
 1049                 } else {
 1050                     lstFilesToLoad << fname;
 1051                     //parent->addDocToLoad(fname);
 1052                 }
 1053 
 1054                 newInclude->valid = !fname.isEmpty();
 1055                 newInclude->setLine(line(i).handle(), i);
 1056                 newInclude->columnNumber = cmdStart;
 1057                 flatStructure << newInclude;
 1058                 updateSyntaxCheck = true;
 1059                 continue;
 1060             }
 1061 
 1062             if (lp.possibleCommands["%import"].contains(cmd) && !isDefinitionArgument(firstArg)) {
 1063                 StructureEntry *newInclude = new StructureEntry(this, StructureEntry::SE_INCLUDE);
 1064                 newInclude->level = parent && !parent->indentIncludesInStructure ? 0 : lp.structureDepth() - 1;
 1065                 QDir dir(firstArg);
 1066                 QFileInfo fi(dir, Parsing::getArg(args, dlh, 1, ArgumentList::Mandatory,true,i));
 1067                 QString file = fi.filePath();
 1068                 newInclude->title = file;
 1069                 QString fname = findFileName(file);
 1070                 removedIncludes.removeAll(fname);
 1071                 mIncludedFilesList.insert(line(i).handle(), fname);
 1072                 LatexDocument *dc = parent->findDocumentFromName(fname);
 1073                 if (dc) {
 1074                     childDocs.insert(dc);
 1075                     dc->setMasterDocument(this, recheckLabels);
 1076                 } else {
 1077                     lstFilesToLoad << fname;
 1078                     //parent->addDocToLoad(fname);
 1079                 }
 1080 
 1081                 newInclude->valid = !fname.isEmpty();
 1082                 newInclude->setLine(line(i).handle(), i);
 1083                 newInclude->columnNumber = cmdStart;
 1084                 flatStructure << newInclude;
 1085                 updateSyntaxCheck = true;
 1086                 continue;
 1087             }
 1088 
 1089             //// all sections ////
 1090             if (cmd.endsWith("*"))
 1091                 cmd = cmd.left(cmd.length() - 1);
 1092             int level = lp.structureCommandLevel(cmd);
 1093             if(level<0 && cmd=="\\begin"){
 1094                 // special treatment for \begin{frame}{title}
 1095                 level=lp.structureCommandLevel(cmd+"{"+firstArg+"}");
 1096             }
 1097             if (level > -1 && !firstArg.isEmpty() && tkCmd.subtype == Token::none) {
 1098                 StructureEntry *newSection = new StructureEntry(this, StructureEntry::SE_SECTION);
 1099                 if (mAppendixLine && indexOf(mAppendixLine) < i) newSection->setContext(StructureEntry::InAppendix);
 1100                 if (mBeyondEnd && indexOf(mBeyondEnd) < i) newSection->setContext(StructureEntry::BeyondEnd);
 1101                 //QString firstOptArg = Parsing::getArg(args, dlh, 0, ArgumentList::Optional);
 1102                 QString firstOptArg = Parsing::getArg(args, Token::shorttitle);
 1103                 if (!firstOptArg.isEmpty() && firstOptArg != "[]") // workaround, actually getArg should return "" for "[]"
 1104                     firstArg = firstOptArg;
 1105                 if(cmd=="\\begin"){
 1106                     // special treatment for \begin{frame}{title}
 1107                     firstArg = Parsing::getArg(args, dlh, 1, ArgumentList::MandatoryWithBraces,false,i);
 1108                     if(firstArg.isEmpty()){
 1109                         // empty frame title, maybe \frametitle is used ?
 1110                         delete newSection;
 1111                         continue;
 1112                     }
 1113                 }
 1114                 newSection->title = latexToText(firstArg).trimmed();
 1115                 newSection->level = level;
 1116                 newSection->setLine(line(i).handle(), i);
 1117                 newSection->columnNumber = cmdStart;
 1118                 flatStructure << newSection;
 1119                 continue;
 1120             }
 1121             /// auto user command for \symbol_...
 1122             if(j+2<tl.length()){
 1123                 Token tk2=tl.at(j+1);
 1124                 if(tk2.getText()=="_"){
 1125                     QString txt=cmd+"_";
 1126                     tk2=tl.at(j+2);
 1127                     txt.append(tk2.getText());
 1128                     if(tk2.type==Token::command && j+3<tl.length()){
 1129                         Token tk3=tl.at(j+3);
 1130                         if(tk3.level==tk2.level && tk.subtype!=Token::none)
 1131                             txt.append(tk3.getText());
 1132                     }
 1133                     CodeSnippet cs(txt);
 1134                     cs.type=CodeSnippet::userConstruct;
 1135                     mUserCommandList.insert(line(i).handle(), UserCommandPair(QString(), cs));
 1136                 }
 1137             }
 1138             /// auto user commands of \mathcmd{one arg} e.g. \mathsf{abc} or \overbrace{abc}
 1139             if(j+2<tl.length() && !firstArg.isEmpty() && lp.possibleCommands["math"].contains(cmd) ){
 1140                 if (lp.commandDefs.contains(cmd)) {
 1141                     CommandDescription cd = lp.commandDefs.value(cmd);
 1142                     if(cd.args==1 && cd.bracketArgs==0 && cd.optionalArgs==0){
 1143                         QString txt=cmd+"{"+firstArg+"}";
 1144                         CodeSnippet cs(txt);
 1145                         cs.type=CodeSnippet::userConstruct;
 1146                         mUserCommandList.insert(line(i).handle(), UserCommandPair(QString(), cs));
 1147                     }
 1148                 }
 1149             }
 1150 
 1151         } // while(findCommandWithArgs())
 1152 
 1153         if (!oldBibs.isEmpty())
 1154             bibTeXFilesNeedsUpdate = true; //file name removed
 1155 
 1156         if (!removedIncludes.isEmpty()) {
 1157             parent->removeDocs(removedIncludes);
 1158             parent->updateMasterSlaveRelations(this);
 1159         }
 1160     }//for each line handle
 1161     StructureEntry *se;
 1162     foreach (se, removedTodo) {
 1163         removeElementWithSignal(se);
 1164         delete se;
 1165     }
 1166     foreach (se, removedBibTeX) {
 1167         removeElementWithSignal(se);
 1168         delete se;
 1169     }
 1170     foreach (se, removedBlock) {
 1171         removeElementWithSignal(se);
 1172         delete se;
 1173     }
 1174     foreach (se, removedLabels) {
 1175         removeElementWithSignal(se);
 1176         delete se;
 1177     }
 1178     foreach (se, removedMagicComments) {
 1179         removeElementWithSignal(se);
 1180         delete se;
 1181     }
 1182     StructureEntry *newSection = nullptr;
 1183 
 1184     // always generate complete structure, also for hidden, as needed for globalTOC
 1185     LatexStructureMergerMerge(this, lp.structureDepth(), lineNrStart, newCount)(flatStructure);
 1186 
 1187     const QList<StructureEntry *> categories =
 1188             QList<StructureEntry *>() << magicCommentList << blockList << labelList << todoList << bibTeXList;
 1189 
 1190     for (int i = categories.size() - 1; i >= 0; i--) {
 1191         StructureEntry *cat = categories[i];
 1192         if (cat->children.isEmpty() == (cat->parent == nullptr)) continue;
 1193         if (cat->children.isEmpty()) removeElementWithSignal(cat);
 1194         else insertElementWithSignal(baseStructure, 0, cat);
 1195     }
 1196 
 1197     //update appendix change
 1198     if (oldLine != mAppendixLine) {
 1199         updateContext(oldLine, mAppendixLine, StructureEntry::InAppendix);
 1200     }
 1201     //update end document change
 1202     if (oldLineBeyond != mBeyondEnd) {
 1203         updateContext(oldLineBeyond, mBeyondEnd, StructureEntry::BeyondEnd);
 1204     }
 1205 
 1206     // rehighlight current cursor position
 1207     if (edView) {
 1208         int i = edView->editor->cursor().lineNumber();
 1209         if (i >= 0) {
 1210             newSection = findSectionForLine(i);
 1211         }
 1212     }
 1213 
 1214     emit structureUpdated(this, newSection);
 1215     bool updateLtxCommands = false;
 1216     if (!addedUsepackages.isEmpty() || !removedUsepackages.isEmpty() || !addedUserCommands.isEmpty() || !removedUserCommands.isEmpty()) {
 1217         bool forceUpdate = !addedUserCommands.isEmpty() || !removedUserCommands.isEmpty();
 1218         reRunSuggested = (count > 1) && (!addedUsepackages.isEmpty() || !removedUsepackages.isEmpty());
 1219         // don't patch single lines if the whole text needs to be rechecked anyways
 1220         updateLtxCommands = updateCompletionFiles(forceUpdate, false, true, reRunSuggested);
 1221     }
 1222     if (bibTeXFilesNeedsUpdate)
 1223         emit updateBibTeXFiles();
 1224     // force update on citation overlays
 1225     if (bibItemsChanged || bibTeXFilesNeedsUpdate) {
 1226         parent->updateBibFiles(bibTeXFilesNeedsUpdate);
 1227         // needs probably done asynchronously as bibteFiles needs to be loaded first ...
 1228         foreach (LatexDocument *elem, getListOfDocs()) {
 1229             if (elem->edView)
 1230                 elem->edView->updateCitationFormats();
 1231         }
 1232     }
 1233     if (completerNeedsUpdate || bibTeXFilesNeedsUpdate)
 1234         emit updateCompleter();
 1235     if ((!recheck && updateSyntaxCheck) || updateLtxCommands) {
 1236         this->updateLtxCommands(true);
 1237     }
 1238     //update view (unless patchStructure is run again anyway)
 1239     if (edView && !reRunSuggested)
 1240         edView->documentContentChanged(linenr, count);
 1241 #ifndef QT_NO_DEBUG
 1242     if (!isHidden())
 1243         checkForLeak();
 1244 #endif
 1245     foreach (QString fname, lstFilesToLoad) {
 1246         parent->addDocToLoad(fname);
 1247     }
 1248     //qDebug()<<"leave"<< QTime::currentTime().toString("HH:mm:ss:zzz");
 1249     if (reRunSuggested && !recheck){
 1250         patchStructure(0, -1, true); // expensive solution for handling changed packages (and hence command definitions)
 1251     }
 1252     if(!recheck){
 1253         reCheckSyntax(lineNrStart, newCount);
 1254     }
 1255 
 1256     return reRunSuggested;
 1257 }
 1258 
 1259 #ifndef QT_NO_DEBUG
 1260 void LatexDocument::checkForLeak()
 1261 {
 1262     StructureEntryIterator iter(baseStructure);
 1263     QSet<StructureEntry *>zw = StructureContent;
 1264     while (iter.hasNext()) {
 1265         zw.remove(iter.next());
 1266     }
 1267 
 1268     // filter top level elements
 1269     QMutableSetIterator<StructureEntry *> i(zw);
 1270     while (i.hasNext())
 1271         if (i.next()->type == StructureEntry::SE_OVERVIEW) i.remove();
 1272 
 1273     if (zw.count() > 0) {
 1274         qDebug("Memory leak in structure");
 1275     }
 1276 }
 1277 #endif
 1278 
 1279 StructureEntry *LatexDocument::findSectionForLine(int currentLine)
 1280 {
 1281     StructureEntryIterator iter(baseStructure);
 1282     StructureEntry *newSection = nullptr;
 1283 
 1284     while (/*iter.hasNext()*/true) {
 1285         StructureEntry *curSection = nullptr;
 1286         while (iter.hasNext()) {
 1287             curSection = iter.next();
 1288             if (curSection->type == StructureEntry::SE_SECTION)
 1289                 break;
 1290         }
 1291         if (curSection == nullptr || curSection->type != StructureEntry::SE_SECTION)
 1292             break;
 1293 
 1294         if (curSection->getRealLineNumber() > currentLine) break; //curSection is after newSection where the cursor is
 1295         else newSection = curSection;
 1296     }
 1297     if (newSection && newSection->getRealLineNumber() > currentLine) newSection = nullptr;
 1298 
 1299     return newSection;
 1300 }
 1301 
 1302 void LatexDocument::setTemporaryFileName(const QString &fileName)
 1303 {
 1304     temporaryFileName = fileName;
 1305 }
 1306 
 1307 QString LatexDocument::getTemporaryFileName() const
 1308 {
 1309     return temporaryFileName;
 1310 }
 1311 
 1312 QString LatexDocument::getFileNameOrTemporaryFileName() const
 1313 {
 1314     if (!fileName.isEmpty()) return fileName;
 1315     return temporaryFileName;
 1316 }
 1317 
 1318 QFileInfo LatexDocument::getTemporaryFileInfo() const
 1319 {
 1320     return QFileInfo(temporaryFileName);
 1321 }
 1322 
 1323 int LatexDocument::countLabels(const QString &name)
 1324 {
 1325     int result = 0;
 1326     foreach (const LatexDocument *elem, getListOfDocs()) {
 1327         QStringList items = elem->labelItems();
 1328         result += items.count(name);
 1329     }
 1330     return result;
 1331 }
 1332 
 1333 int LatexDocument::countRefs(const QString &name)
 1334 {
 1335     int result = 0;
 1336     foreach (const LatexDocument *elem, getListOfDocs()) {
 1337         QStringList items = elem->refItems();
 1338         result += items.count(name);
 1339     }
 1340     return result;
 1341 }
 1342 
 1343 bool LatexDocument::bibIdValid(const QString &name)
 1344 {
 1345     bool result = !findFileFromBibId(name).isEmpty();
 1346     if (!result) {
 1347         foreach (const LatexDocument *doc, getListOfDocs()) {
 1348             //if(doc->getEditorView()->containsBibTeXId(name)){
 1349             if (doc->bibItems().contains(name)) {
 1350                 result = true;
 1351                 break;
 1352             }
 1353         }
 1354     }
 1355     return result;
 1356 }
 1357 
 1358 bool LatexDocument::isBibItem(const QString &name)
 1359 {
 1360     bool result = false;
 1361     foreach (const LatexDocument *doc, getListOfDocs()) {
 1362         //if(doc->getEditorView()->containsBibTeXId(name)){
 1363         if (doc->bibItems().contains(name)) {
 1364             result = true;
 1365             break;
 1366         }
 1367     }
 1368     return result;
 1369 }
 1370 
 1371 QString LatexDocument::findFileFromBibId(const QString &bibId)
 1372 {
 1373     QStringList collected_mentionedBibTeXFiles;
 1374     foreach (const LatexDocument *doc, getListOfDocs())
 1375         collected_mentionedBibTeXFiles << doc->listOfMentionedBibTeXFiles();
 1376     const QMap<QString, BibTeXFileInfo> &bibtexfiles = parent->bibTeXFiles;
 1377     foreach (const QString &file, collected_mentionedBibTeXFiles)
 1378         if (bibtexfiles.value(file).ids.contains(bibId))
 1379             return file;
 1380     return QString();
 1381 }
 1382 
 1383 QMultiHash<QDocumentLineHandle *, int> LatexDocument::getBibItems(const QString &name)
 1384 {
 1385     QMultiHash<QDocumentLineHandle *, int> result;
 1386     foreach (const LatexDocument *elem, getListOfDocs()) {
 1387         QMultiHash<QDocumentLineHandle *, ReferencePair>::const_iterator it;
 1388         for (it = elem->mBibItem.constBegin(); it != elem->mBibItem.constEnd(); ++it) {
 1389             ReferencePair rp = it.value();
 1390             if (rp.name == name && elem->indexOf(it.key()) >= 0) {
 1391                 result.insert(it.key(), rp.start);
 1392             }
 1393         }
 1394     }
 1395     return result;
 1396 }
 1397 
 1398 QMultiHash<QDocumentLineHandle *, int> LatexDocument::getLabels(const QString &name)
 1399 {
 1400     QMultiHash<QDocumentLineHandle *, int> result;
 1401     foreach (const LatexDocument *elem, getListOfDocs()) {
 1402         QMultiHash<QDocumentLineHandle *, ReferencePair>::const_iterator it;
 1403         for (it = elem->mLabelItem.constBegin(); it != elem->mLabelItem.constEnd(); ++it) {
 1404             ReferencePair rp = it.value();
 1405             if (rp.name == name && elem->indexOf(it.key()) >= 0) {
 1406                 result.insert(it.key(), rp.start);
 1407             }
 1408         }
 1409     }
 1410     return result;
 1411 }
 1412 
 1413 QDocumentLineHandle *LatexDocument::findCommandDefinition(const QString &name)
 1414 {
 1415     foreach (const LatexDocument *elem, getListOfDocs()) {
 1416         QMultiHash<QDocumentLineHandle *, UserCommandPair>::const_iterator it;
 1417         for (it = elem->mUserCommandList.constBegin(); it != elem->mUserCommandList.constEnd(); ++it) {
 1418             if (it.value().name == name && elem->indexOf(it.key()) >= 0) {
 1419                 return it.key();
 1420             }
 1421         }
 1422     }
 1423     return nullptr;
 1424 }
 1425 
 1426 QDocumentLineHandle *LatexDocument::findUsePackage(const QString &name)
 1427 {
 1428     foreach (const LatexDocument *elem, getListOfDocs()) {
 1429         QMultiHash<QDocumentLineHandle *, QString>::const_iterator it;
 1430         for (it = elem->mUsepackageList.constBegin(); it != elem->mUsepackageList.constEnd(); ++it) {
 1431             if (LatexPackage::keyToPackageName(it.value()) == name && elem->indexOf(it.key()) >= 0) {
 1432                 return it.key();
 1433             }
 1434         }
 1435     }
 1436     return nullptr;
 1437 }
 1438 
 1439 QMultiHash<QDocumentLineHandle *, int> LatexDocument::getRefs(const QString &name)
 1440 {
 1441     QMultiHash<QDocumentLineHandle *, int> result;
 1442     foreach (const LatexDocument *elem, getListOfDocs()) {
 1443         QMultiHash<QDocumentLineHandle *, ReferencePair>::const_iterator it;
 1444         for (it = elem->mRefItem.constBegin(); it != elem->mRefItem.constEnd(); ++it) {
 1445             ReferencePair rp = it.value();
 1446             if (rp.name == name && elem->indexOf(it.key()) >= 0) {
 1447                 result.insert(it.key(), rp.start);
 1448             }
 1449         }
 1450     }
 1451     return result;
 1452 }
 1453 
 1454 /*!
 1455  * replace all given items by newName
 1456  * an optional QDocumentCursor may be passed in, if the operation should be
 1457  * part of a larger editBlock of that cursor.
 1458  */
 1459 void LatexDocument::replaceItems(QMultiHash<QDocumentLineHandle *, ReferencePair> items, const QString &newName, QDocumentCursor *cursor)
 1460 {
 1461     QDocumentCursor *cur = cursor;
 1462     if (!cursor) {
 1463         cur = new QDocumentCursor(this);
 1464         cur->beginEditBlock();
 1465     }
 1466     QMultiHash<QDocumentLineHandle *, ReferencePair>::const_iterator it;
 1467     int oldLineNr=-1;
 1468     int offset=0;
 1469     for (it = items.constBegin(); it != items.constEnd(); ++it) {
 1470         QDocumentLineHandle *dlh = it.key();
 1471         ReferencePair rp = it.value();
 1472         int lineNo = indexOf(dlh);
 1473         if(oldLineNr!=lineNo){
 1474             offset=0;
 1475         }
 1476         if (lineNo >= 0) {
 1477             cur->setLineNumber(lineNo);
 1478             cur->setColumnNumber(rp.start+offset);
 1479             cur->movePosition(rp.name.length(), QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
 1480             cur->replaceSelectedText(newName);
 1481             offset+=newName.length()-rp.name.length();
 1482             oldLineNr=lineNo;
 1483         }
 1484     }
 1485     if (!cursor) {
 1486         cur->endEditBlock();
 1487         delete cur;
 1488     }
 1489 }
 1490 
 1491 /*!
 1492  * replace all labels name by newName
 1493  * an optional QDocumentCursor may be passed in, if the operation should be
 1494  * part of a larger editBlock of that cursor.
 1495  */
 1496 void LatexDocument::replaceLabel(const QString &name, const QString &newName, QDocumentCursor *cursor)
 1497 {
 1498     QMultiHash<QDocumentLineHandle *, ReferencePair> labelItemsMatchingName;
 1499     QMultiHash<QDocumentLineHandle *, ReferencePair>::const_iterator it;
 1500     for (it = mLabelItem.constBegin(); it != mLabelItem.constEnd(); ++it) {
 1501         if (it.value().name == name) {
 1502             labelItemsMatchingName.insert(it.key(), it.value());
 1503         }
 1504     }
 1505     replaceItems(labelItemsMatchingName, newName, cursor);
 1506 }
 1507 
 1508 /*!
 1509  * replace all references name by newName
 1510  * an optional QDocumentCursor may be passed in, if the operation should be
 1511  * part of a larger editBlock of that cursor.
 1512  */
 1513 void LatexDocument::replaceRefs(const QString &name, const QString &newName, QDocumentCursor *cursor)
 1514 {
 1515     QMultiHash<QDocumentLineHandle *, ReferencePair> refItemsMatchingName;
 1516     QMultiHash<QDocumentLineHandle *, ReferencePair>::const_iterator it;
 1517     for (it = mRefItem.constBegin(); it != mRefItem.constEnd(); ++it) {
 1518         if (it.value().name == name) {
 1519             refItemsMatchingName.insert(it.key(), it.value());
 1520         }
 1521     }
 1522     replaceItems(refItemsMatchingName, newName, cursor);
 1523 }
 1524 
 1525 void LatexDocument::replaceLabelsAndRefs(const QString &name, const QString &newName)
 1526 {
 1527     QDocumentCursor cursor(this);
 1528     cursor.beginEditBlock();
 1529     replaceLabel(name, newName, &cursor);
 1530     replaceRefs(name, newName, &cursor);
 1531     cursor.endEditBlock();
 1532 }
 1533 
 1534 void LatexDocument::setMasterDocument(LatexDocument *doc, bool recheck)
 1535 {
 1536     masterDocument = doc;
 1537     if (recheck) {
 1538         QList<LatexDocument *>listOfDocs = getListOfDocs();
 1539         foreach (LatexDocument *elem, listOfDocs) {
 1540             elem->recheckRefsLabels();
 1541         }
 1542     }
 1543 }
 1544 
 1545 void LatexDocument::addChild(LatexDocument *doc)
 1546 {
 1547     childDocs.insert(doc);
 1548 }
 1549 
 1550 void LatexDocument::removeChild(LatexDocument *doc)
 1551 {
 1552     childDocs.remove(doc);
 1553 }
 1554 
 1555 bool LatexDocument::containsChild(LatexDocument *doc) const
 1556 {
 1557     return childDocs.contains(doc);
 1558 }
 1559 
 1560 QList<LatexDocument *>LatexDocument::getListOfDocs(QSet<LatexDocument *> *visitedDocs)
 1561 {
 1562     QList<LatexDocument *>listOfDocs;
 1563     bool deleteVisitedDocs = false;
 1564     if (parent->masterDocument) {
 1565         listOfDocs = parent->getDocuments();
 1566     } else {
 1567         LatexDocument *master = this;
 1568         if (!visitedDocs) {
 1569             visitedDocs = new QSet<LatexDocument *>();
 1570             deleteVisitedDocs = true;
 1571         }
 1572         foreach (LatexDocument *elem, parent->getDocuments()) { // check children
 1573             if (elem != master && !master->childDocs.contains(elem)) continue;
 1574 
 1575             if (visitedDocs && !visitedDocs->contains(elem)) {
 1576                 listOfDocs << elem;
 1577                 visitedDocs->insert(elem);
 1578                 listOfDocs << elem->getListOfDocs(visitedDocs);
 1579             }
 1580         }
 1581         if (masterDocument) { //check masters
 1582             master = masterDocument;
 1583             if (!visitedDocs->contains(master))
 1584                 listOfDocs << master->getListOfDocs(visitedDocs);
 1585         }
 1586     }
 1587     if (deleteVisitedDocs)
 1588         delete visitedDocs;
 1589     return listOfDocs;
 1590 }
 1591 void LatexDocument::updateRefHighlight(ReferencePairEx p){
 1592     p.dlh->clearOverlays(p.formatList);
 1593     for(int i=0;i<p.starts.size();++i) {
 1594         p.dlh->addOverlay(QFormatRange(p.starts[i], p.lengths[i], p.formats[i]));
 1595     }
 1596 }
 1597 
 1598 void LatexDocument::recheckRefsLabels()
 1599 {
 1600     // get occurences (refs)
 1601     int referenceMultipleFormat = getFormatId("referenceMultiple");
 1602     int referencePresentFormat = getFormatId("referencePresent");
 1603     int referenceMissingFormat = getFormatId("referenceMissing");
 1604     const QList<int> formatList{referenceMissingFormat,referencePresentFormat,referenceMultipleFormat};
 1605     QList<ReferencePairEx> results;
 1606 
 1607     QStringList items;
 1608     foreach (const LatexDocument *elem, getListOfDocs()) {
 1609         items << elem->labelItems();
 1610     }
 1611 
 1612     QMultiHash<QDocumentLineHandle *, ReferencePair>::const_iterator it;
 1613     QSet<QDocumentLineHandle*> dlhs;
 1614     for (it = mLabelItem.constBegin(); it != mLabelItem.constEnd(); ++it) {
 1615         dlhs.insert(it.key());
 1616     }
 1617     for (it = mRefItem.constBegin(); it != mRefItem.constEnd(); ++it) {
 1618         dlhs.insert(it.key());
 1619     }
 1620 
 1621     for(QDocumentLineHandle *dlh : dlhs){
 1622         ReferencePairEx p;
 1623         p.formatList=formatList;
 1624         p.dlh=dlh;
 1625         for(const ReferencePair &rp : mLabelItem.values(dlh)) {
 1626             int cnt = items.count(rp.name);
 1627             int format= referenceMissingFormat;
 1628             if (cnt > 1) {
 1629                 format=referenceMultipleFormat;
 1630             } else if (cnt == 1) format=referencePresentFormat;
 1631             p.starts<<rp.start;
 1632             p.lengths<<rp.name.length();
 1633             p.formats<<format;
 1634         }
 1635         for(const ReferencePair &rp :  mRefItem.values(dlh)) {
 1636             int cnt = items.count(rp.name);
 1637             int format= referenceMissingFormat;
 1638             if (cnt > 1) {
 1639                 format=referenceMultipleFormat;
 1640             } else if (cnt == 1) format=referencePresentFormat;
 1641             p.starts<<rp.start;
 1642             p.lengths<<rp.name.length();
 1643             p.formats<<format;
 1644         }
 1645         results<<p;
 1646     }
 1647 
 1648     QtConcurrent::blockingMap(results,LatexDocument::updateRefHighlight);
 1649 }
 1650 
 1651 QStringList LatexDocument::someItems(const QMultiHash<QDocumentLineHandle *, ReferencePair> &list)
 1652 {
 1653     QList<ReferencePair> lst = list.values();
 1654     QStringList result;
 1655     foreach (const ReferencePair &elem, lst) {
 1656         result << elem.name;
 1657     }
 1658 
 1659     return result;
 1660 }
 1661 
 1662 
 1663 QStringList LatexDocument::labelItems() const
 1664 {
 1665     return someItems(mLabelItem);
 1666 }
 1667 
 1668 QStringList LatexDocument::refItems() const
 1669 {
 1670     return someItems(mRefItem);
 1671 }
 1672 
 1673 QStringList LatexDocument::bibItems() const
 1674 {
 1675     return someItems(mBibItem);
 1676 }
 1677 
 1678 QList<CodeSnippet> LatexDocument::userCommandList() const
 1679 {
 1680     QList<CodeSnippet> csl;
 1681     foreach (UserCommandPair cmd, mUserCommandList.values()) {
 1682         csl.append(cmd.snippet);
 1683     }
 1684     std::sort(csl.begin(),csl.end());
 1685     return csl;
 1686 }
 1687 
 1688 
 1689 void LatexDocument::updateRefsLabels(const QString &ref)
 1690 {
 1691     // get occurences (refs)
 1692     int referenceMultipleFormat = getFormatId("referenceMultiple");
 1693     int referencePresentFormat = getFormatId("referencePresent");
 1694     int referenceMissingFormat = getFormatId("referenceMissing");
 1695     const QList<int> formatList{referenceMissingFormat,referencePresentFormat,referenceMultipleFormat};
 1696 
 1697     int cnt = countLabels(ref);
 1698     QMultiHash<QDocumentLineHandle *, int> occurences = getLabels(ref);
 1699     occurences += getRefs(ref);
 1700     QMultiHash<QDocumentLineHandle *, int>::const_iterator it;
 1701     for (it = occurences.constBegin(); it != occurences.constEnd(); ++it) {
 1702         QDocumentLineHandle *dlh = it.key();
 1703         dlh->clearOverlays(formatList);
 1704         for(const int pos : occurences.values(dlh)) {
 1705             if (cnt > 1) {
 1706                 dlh->addOverlay(QFormatRange(pos, ref.length(), referenceMultipleFormat));
 1707             } else if (cnt == 1) dlh->addOverlay(QFormatRange(pos, ref.length(), referencePresentFormat));
 1708             else dlh->addOverlay(QFormatRange(pos, ref.length(), referenceMissingFormat));
 1709         }
 1710     }
 1711 }
 1712 
 1713 
 1714 
 1715 LatexDocuments::LatexDocuments(): model(new LatexDocumentsModel(*this)), masterDocument(nullptr), currentDocument(nullptr), bibTeXFilesModified(false)
 1716 {
 1717     showLineNumbersInStructure = false;
 1718     indentationInStructure = -1;
 1719     showCommentedElementsInStructure = false;
 1720     markStructureElementsBeyondEnd = true;
 1721     markStructureElementsInAppendix = true;
 1722     indentIncludesInStructure = false;
 1723     m_patchEnabled = true;
 1724 }
 1725 
 1726 LatexDocuments::~LatexDocuments()
 1727 {
 1728     delete model;
 1729 }
 1730 
 1731 void LatexDocuments::addDocument(LatexDocument *document, bool hidden)
 1732 {
 1733     if (hidden) {
 1734         hiddenDocuments.append(document);
 1735         LatexEditorView *edView = document->getEditorView();
 1736         if (edView) {
 1737             QEditor *ed = edView->getEditor();
 1738             if (ed) {
 1739                 document->remeberAutoReload = ed->silentReloadOnExternalChanges();
 1740                 ed->setSilentReloadOnExternalChanges(true);
 1741                 ed->setHidden(true);
 1742             }
 1743         }
 1744     } else {
 1745         documents.append(document);
 1746     }
 1747     connect(document, SIGNAL(updateBibTeXFiles()), SLOT(bibTeXFilesNeedUpdate()));
 1748     connect(document, SIGNAL(structureLost(LatexDocument *)), model, SLOT(structureLost(LatexDocument *)));
 1749     connect(document, SIGNAL(structureUpdated(LatexDocument *, StructureEntry *)), model, SLOT(structureUpdated(LatexDocument *, StructureEntry *)));
 1750     //connect(document, SIGNAL(setHighlightedEntry(StructureEntry *)), model, SLOT(setHighlightedEntry(StructureEntry *)));
 1751     connect(document, SIGNAL(toBeChanged()), model, SIGNAL(layoutAboutToBeChanged()));
 1752     connect(document, SIGNAL(removeElement(StructureEntry *, int)), model, SLOT(removeElement(StructureEntry *, int)));
 1753     connect(document, SIGNAL(removeElementFinished()), model, SLOT(removeElementFinished()));
 1754     connect(document, SIGNAL(addElement(StructureEntry *, int)), model, SLOT(addElement(StructureEntry *, int)));
 1755     connect(document, SIGNAL(addElementFinished()), model, SLOT(addElementFinished()));
 1756     connect(document, SIGNAL(updateElement(StructureEntry *)), model, SLOT(updateElement(StructureEntry *)));
 1757     document->parent = this;
 1758     if (masterDocument) {
 1759         // repaint all docs
 1760         foreach (const LatexDocument *doc, documents) {
 1761             LatexEditorView *edView = doc->getEditorView();
 1762             if (edView) edView->documentContentChanged(0, edView->editor->document()->lines());
 1763         }
 1764     }
 1765     if (!hidden)
 1766         model->structureUpdated(document, nullptr);
 1767 }
 1768 
 1769 void LatexDocuments::deleteDocument(LatexDocument *document, bool hidden, bool purge)
 1770 {
 1771     if (!hidden)
 1772         emit aboutToDeleteDocument(document);
 1773     LatexEditorView *view = document->getEditorView();
 1774     if (view)
 1775         view->closeCompleter();
 1776         if ((document != masterDocument)||(documents.count()==1) ) {
 1777         // get list of all affected documents
 1778         QList<LatexDocument *> lstOfDocs = document->getListOfDocs();
 1779         // special treatment to remove document in purge mode (hidden doc was deleted on disc)
 1780         if (purge) {
 1781             Q_ASSERT(hidden); //purging non-hidden doc crashes.
 1782             LatexDocument *rootDoc = document->getRootDocument();
 1783             hiddenDocuments.removeAll(document);
 1784             foreach (LatexDocument *elem, getDocuments()) {
 1785                 if (elem->containsChild(document)) {
 1786                     elem->removeChild(document);
 1787                 }
 1788             }
 1789             //update children (connection to parents is severed)
 1790             foreach (LatexDocument *elem, lstOfDocs) {
 1791                 if (elem->getMasterDocument() == document) {
 1792                     if (elem->isHidden())
 1793                         deleteDocument(elem, true, true);
 1794                     else
 1795                         elem->setMasterDocument(nullptr);
 1796                 }
 1797             }
 1798             delete document;
 1799             if (rootDoc != document) {
 1800                 // update parents
 1801                 lstOfDocs = rootDoc->getListOfDocs();
 1802                 int n = 0;
 1803                 foreach (LatexDocument *elem, lstOfDocs) {
 1804                     if (!elem->isHidden()) {
 1805                         n++;
 1806                         break;
 1807                     }
 1808                 }
 1809                 if (n == 0)
 1810                     deleteDocument(rootDoc, true, true);
 1811                 else
 1812                     updateMasterSlaveRelations(rootDoc, true, true);
 1813             }
 1814             return;
 1815         }
 1816         // count open related (child/parent) documents
 1817         int n = 0;
 1818         foreach (LatexDocument *elem, lstOfDocs) {
 1819             if (!elem->isHidden())
 1820                 n++;
 1821         }
 1822         if (hidden) {
 1823             hiddenDocuments.removeAll(document);
 1824             return;
 1825         }
 1826         if (n > 1) { // at least one related document will be open after removal
 1827             hiddenDocuments.append(document);
 1828             LatexEditorView *edView = document->getEditorView();
 1829             if (edView) {
 1830                 QEditor *ed = edView->getEditor();
 1831                 if (ed) {
 1832                     document->remeberAutoReload = ed->silentReloadOnExternalChanges();
 1833                     ed->setSilentReloadOnExternalChanges(true);
 1834                     ed->setHidden(true);
 1835                 }
 1836             }
 1837         } else {
 1838             // no open document remains, remove all others as well
 1839             foreach (LatexDocument *elem, getDocuments()) {
 1840                 if (elem->containsChild(document)) {
 1841                     elem->removeChild(document);
 1842                 }
 1843             }
 1844             foreach (LatexDocument *elem, lstOfDocs) {
 1845                 if (elem->isHidden()) {
 1846                     hiddenDocuments.removeAll(elem);
 1847                     delete elem->getEditorView();
 1848                     delete elem;
 1849                 }
 1850             }
 1851         }
 1852 
 1853         int row = documents.indexOf(document);
 1854         //qDebug()<<document->getFileName()<<row;
 1855         if (!document->baseStructure) row = -1; //may happen directly after reload (but won't)
 1856         if (model->getSingleDocMode()) {
 1857             row = 0;
 1858         }
 1859         if (row >= 0 ) { //&& !model->getSingleDocMode()){
 1860             model->resetHighlight();
 1861             model->removeElement(document->baseStructure, row); //remove from root
 1862         }
 1863         documents.removeAll(document);
 1864         if (document == currentDocument) {
 1865                     currentDocument = nullptr;
 1866         }
 1867         if (row >= 0 ) { //&& !model->getSingleDocMode()){
 1868             model->removeElementFinished();
 1869         }
 1870         //model->resetAll();
 1871         if (n > 1) { // don't remove document, stays hidden instead
 1872             hideDocInEditor(document->getEditorView());
 1873                         if(masterDocument && documents.count()==1){
 1874                             // special check if masterDocument, but document is not visible
 1875                             LatexDocument *doc=documents.first();
 1876                             if(!doc->getEditorView()){
 1877                                 // no view left -> purge
 1878                                 deleteDocument(masterDocument);
 1879                             }
 1880                         }
 1881             return;
 1882         }
 1883         delete view;
 1884         delete document;
 1885     } else {
 1886         if (hidden) {
 1887             hiddenDocuments.removeAll(document);
 1888             return;
 1889         }
 1890         document->setFileName(document->getFileName());
 1891         model->resetAll();
 1892         document->clearAppendix();
 1893         delete view;
 1894         if (document == currentDocument)
 1895             currentDocument = nullptr;
 1896     }
 1897         // purge masterdocument if none is left
 1898         if(documents.isEmpty()){
 1899             if(masterDocument){
 1900                 masterDocument=nullptr;
 1901             }
 1902             hiddenDocuments.clear();
 1903         }
 1904 }
 1905 
 1906 void LatexDocuments::requestedClose()
 1907 {
 1908     QEditor *editor = qobject_cast<QEditor *>(sender());
 1909     LatexDocument *doc = qobject_cast<LatexDocument *>(editor->document());
 1910     deleteDocument(doc, true, true);
 1911 }
 1912 /*!
 1913  * \brief set \param document as new master document
 1914  * Garcefully close old master document if set and set document as new master
 1915  * \param document
 1916  */
 1917 void LatexDocuments::setMasterDocument(LatexDocument *document)
 1918 {
 1919     if (document == masterDocument) return;
 1920     if (masterDocument != nullptr && masterDocument->getEditorView() == nullptr) {
 1921         QString fn = masterDocument->getFileName();
 1922         addDocToLoad(fn);
 1923         LatexDocument *doc = masterDocument;
 1924         masterDocument = nullptr;
 1925         deleteDocument(doc);
 1926         //documents.removeAll(masterDocument);
 1927         //delete masterDocument;
 1928     }
 1929     masterDocument = document;
 1930     if (masterDocument != nullptr) {
 1931         documents.removeAll(masterDocument);
 1932         documents.prepend(masterDocument);
 1933         // repaint doc
 1934         foreach (LatexDocument *doc, documents) {
 1935             LatexEditorView *edView = doc->getEditorView();
 1936                         if (edView) edView->documentContentChanged(0, doc->lines());
 1937         }
 1938     }
 1939     model->resetAll();
 1940     emit masterDocumentChanged(masterDocument);
 1941 }
 1942 /*!
 1943  * \brief return current document
 1944  * \return current document
 1945  */
 1946 LatexDocument *LatexDocuments::getCurrentDocument() const
 1947 {
 1948     return currentDocument;
 1949 }
 1950 /*!
 1951  * \brief return master document if one is set
 1952  * \return masterDocument
 1953  */
 1954 LatexDocument *LatexDocuments::getMasterDocument() const
 1955 {
 1956     return masterDocument;
 1957 }
 1958 
 1959 /*!
 1960  * \brief return list of *all* open documents
 1961  * This includes visibles and hidden documents in memory
 1962  * \return list of documents
 1963  */
 1964 QList<LatexDocument *> LatexDocuments::getDocuments() const
 1965 {
 1966     QList<LatexDocument *> docs = documents + hiddenDocuments;
 1967     return docs;
 1968 }
 1969 
 1970 void LatexDocuments::move(int from, int to)
 1971 {
 1972     model->layoutAboutToBeChanged();
 1973     model->moveDocs(from, to);
 1974     documents.move(from, to);
 1975     model->layoutChanged();
 1976 }
 1977 /*!
 1978  * \brief get file name of current document
 1979  * \return file name
 1980  */
 1981 QString LatexDocuments::getCurrentFileName() const
 1982 {
 1983     if (!currentDocument) return "";
 1984     return currentDocument->getFileName();
 1985 }
 1986 
 1987 QString LatexDocuments::getCompileFileName() const
 1988 {
 1989     if (masterDocument)
 1990         return masterDocument->getFileName();
 1991     if (!currentDocument)
 1992         return "";
 1993     // check for magic comment
 1994     QString curDocFile = currentDocument->getMagicComment("root");
 1995     if (curDocFile.isEmpty())
 1996         curDocFile = currentDocument->getMagicComment("texroot");
 1997     if (!curDocFile.isEmpty()) {
 1998         return currentDocument->findFileName(curDocFile);
 1999     }
 2000     //
 2001     const LatexDocument *rootDoc = currentDocument->getRootDocument();
 2002     curDocFile = currentDocument->getFileName();
 2003     if (rootDoc)
 2004         curDocFile = rootDoc->getFileName();
 2005     return curDocFile;
 2006 }
 2007 
 2008 QString LatexDocuments::getTemporaryCompileFileName() const
 2009 {
 2010     QString temp = getCompileFileName();
 2011     if (!temp.isEmpty()) return temp;
 2012     if (masterDocument) return masterDocument->getTemporaryFileName();
 2013     else if (currentDocument) return currentDocument->getTemporaryFileName();
 2014     return "";
 2015 }
 2016 
 2017 QString LatexDocuments::getLogFileName() const
 2018 {
 2019     if (!currentDocument) return QString();
 2020     LatexDocument *rootDoc = currentDocument->getRootDocument();
 2021     QString jobName = rootDoc->getMagicComment("-job-name");
 2022     if (!jobName.isEmpty()) {
 2023         return ensureTrailingDirSeparator(rootDoc->getFileInfo().absolutePath()) + jobName + ".log";
 2024     } else {
 2025         return replaceFileExtension(getTemporaryCompileFileName(), ".log");
 2026     }
 2027 }
 2028 
 2029 QString LatexDocuments::getAbsoluteFilePath(const QString &relName, const QString &extension, const QStringList &additionalSearchPaths) const
 2030 {
 2031     if (!currentDocument) return relName;
 2032     return currentDocument->getAbsoluteFilePath(relName, extension, additionalSearchPaths);
 2033 }
 2034 
 2035 LatexDocument *LatexDocuments::findDocumentFromName(const QString &fileName) const
 2036 {
 2037     QList<LatexDocument *> docs = getDocuments();
 2038     foreach (LatexDocument *doc, docs) {
 2039         if (doc->getFileName() == fileName) return doc;
 2040     }
 2041     return nullptr;
 2042 }
 2043 
 2044 /*!
 2045  * Adjust the internal order of documents to the given order.
 2046  * \param order should contain exactly the same documents as this.
 2047  */
 2048 void LatexDocuments::reorder(const QList<LatexDocument *> &order)
 2049 {
 2050     model->layoutAboutToBeChanged();
 2051     if (order.size() != documents.size()) qDebug() << "Warning: Size of list of documents for reordering differs from current documents";
 2052     foreach (LatexDocument *doc, order) {
 2053         int n = documents.removeAll(doc);
 2054         if (n > 1) qDebug() << "Warning: document listed multiple times in LatexDocuments";
 2055         if (n < 1) qDebug() << "Warning: encountered a document that is not listed in LatexDocuments";
 2056         documents.append(doc);
 2057     }
 2058     model->layoutChanged();
 2059 }
 2060 
 2061 LatexDocument *LatexDocuments::findDocument(const QDocument *qDoc) const
 2062 {
 2063     QList<LatexDocument *> docs = getDocuments();
 2064     foreach (LatexDocument *doc, docs) {
 2065         LatexEditorView *edView = doc->getEditorView();
 2066         if (edView && edView->editor->document() == qDoc) return doc;
 2067     }
 2068     return nullptr;
 2069 }
 2070 
 2071 LatexDocument *LatexDocuments::findDocument(const QString &fileName, bool checkTemporaryNames) const
 2072 {
 2073     if (fileName == "") return nullptr;
 2074     if (checkTemporaryNames) {
 2075         LatexDocument *temp = findDocument(fileName, false);
 2076         if (temp) return temp;
 2077     }
 2078 
 2079     QFileInfo fi(fileName);
 2080     fi = getNonSymbolicFileInfo(fi);
 2081     if (fi.exists()) {
 2082         foreach (LatexDocument *document, documents) {
 2083             if (document->getFileInfo() == fi) {
 2084                 return document;
 2085             }
 2086         }
 2087         if (checkTemporaryNames) {
 2088             foreach (LatexDocument *document, documents) {
 2089                 if (document->getFileName().isEmpty() && document->getTemporaryFileInfo() == fi) {
 2090                     return document;
 2091                 }
 2092             }
 2093         }
 2094     }
 2095 
 2096     //check for relative file names
 2097     fi.setFile(getAbsoluteFilePath(fileName));
 2098     if (!fi.exists()) {
 2099         fi.setFile(getAbsoluteFilePath(fileName), ".tex");
 2100     }
 2101     if (!fi.exists()) {
 2102         fi.setFile(getAbsoluteFilePath(fileName), ".bib");
 2103     }
 2104     if (fi.exists()) {
 2105         foreach (LatexDocument *document, documents) {
 2106             if (document->getFileInfo().exists() && document->getFileInfo() == fi) {
 2107                 return document;
 2108             }
 2109         }
 2110     }
 2111 
 2112     return nullptr;
 2113 }
 2114 
 2115 void LatexDocuments::settingsRead()
 2116 {
 2117     return; // currently unused
 2118 }
 2119 
 2120 bool LatexDocuments::singleMode() const
 2121 {
 2122     return !masterDocument;
 2123 }
 2124 
 2125 void LatexDocuments::updateBibFiles(bool updateFiles)
 2126 {
 2127     mentionedBibTeXFiles.clear();
 2128     QStringList additionalBibPaths = ConfigManagerInterface::getInstance()->getOption("Files/Bib Paths").toString().split(getPathListSeparator());
 2129     foreach (LatexDocument *doc, getDocuments() ) {
 2130         if (updateFiles) {
 2131             QMultiHash<QDocumentLineHandle *, FileNamePair>::iterator it = doc->mentionedBibTeXFiles().begin();
 2132             QMultiHash<QDocumentLineHandle *, FileNamePair>::iterator itend = doc->mentionedBibTeXFiles().end();
 2133             for (; it != itend; ++it) {
 2134                 it.value().absolute = getAbsoluteFilePath(it.value().relative, ".bib", additionalBibPaths).replace(QDir::separator(), "/"); // update absolute path
 2135                 mentionedBibTeXFiles << it.value().absolute;
 2136             }
 2137         }
 2138     }
 2139 
 2140     //bool changed=false;
 2141     if (updateFiles) {
 2142         QString bibFileEncoding = ConfigManagerInterface::getInstance()->getOption("Bibliography/BibFileEncoding").toString();
 2143         QTextCodec *defaultCodec = QTextCodec::codecForName(bibFileEncoding.toLatin1());
 2144         for (int i = 0; i < mentionedBibTeXFiles.count(); i++) {
 2145             QString &fileName = mentionedBibTeXFiles[i];
 2146             QFileInfo fi(fileName);
 2147             if (!fi.isReadable()) continue; //ups...
 2148             if (!bibTeXFiles.contains(fileName))
 2149                 bibTeXFiles.insert(fileName, BibTeXFileInfo());
 2150             BibTeXFileInfo &bibTex = bibTeXFiles[mentionedBibTeXFiles[i]];
 2151             // TODO: allow to use the encoding of the tex file which mentions the bib file (need to port this information from above)
 2152             bibTex.codec = defaultCodec;
 2153             bibTex.loadIfModified(fileName);
 2154 
 2155             /*if (bibTex.loadIfModified(fileName))
 2156                 changed = true;*/
 2157             if (bibTex.ids.empty() && !bibTex.linksTo.isEmpty())
 2158                 //handle obscure bib tex feature, a just line containing "link fileName"
 2159                 mentionedBibTeXFiles.append(bibTex.linksTo);
 2160         }
 2161     }
 2162     /*
 2163     if (changed || (newBibItems!=bibItems)) {
 2164         allBibTeXIds.clear();
 2165         bibItems=newBibItems;
 2166         for (QMap<QString, BibTeXFileInfo>::const_iterator it=bibTeXFiles.constBegin(); it!=bibTeXFiles.constEnd();++it)
 2167             foreach (const QString& s, it.value().ids)
 2168         allBibTeXIds << s;
 2169         allBibTeXIds.unite(bibItems);
 2170         for (int i=0;i<documents.size();i++)
 2171             if (documents[i]->getEditorView())
 2172                 documents[i]->getEditorView()->setBibTeXIds(&allBibTeXIds);
 2173         bibTeXFilesModified=true;
 2174     }*/
 2175 }
 2176 
 2177 void LatexDocuments::removeDocs(QStringList removeIncludes)
 2178 {
 2179     foreach (QString fname, removeIncludes) {
 2180         LatexDocument *dc = findDocumentFromName(fname);
 2181         if (dc) {
 2182             foreach (LatexDocument *elem, getDocuments()) {
 2183                 if (elem->containsChild(dc)) {
 2184                     elem->removeChild(dc);
 2185                 }
 2186             }
 2187         }
 2188         if (dc && dc->isHidden()) {
 2189             QStringList toremove = dc->includedFiles();
 2190             dc->setMasterDocument(nullptr);
 2191             hiddenDocuments.removeAll(dc);
 2192             //qDebug()<<fname;
 2193             delete dc->getEditorView();
 2194             delete dc;
 2195             if (!toremove.isEmpty())
 2196                 removeDocs(toremove);
 2197         }
 2198     }
 2199 }
 2200 
 2201 void LatexDocuments::addDocToLoad(QString filename)
 2202 {
 2203     emit docToLoad(filename);
 2204 }
 2205 
 2206 void LatexDocuments::hideDocInEditor(LatexEditorView *edView)
 2207 {
 2208     emit docToHide(edView);
 2209 }
 2210 
 2211 int LatexDocument::findStructureParentPos(const QList<StructureEntry *> &children, QList<StructureEntry *> &removedElements, int linenr, int count)
 2212 {
 2213     QListIterator<StructureEntry *> iter(children);
 2214     int parentPos = 0;
 2215     while (iter.hasNext()) {
 2216         StructureEntry *se = iter.next();
 2217         int realline = se->getRealLineNumber();
 2218         Q_ASSERT(realline >= 0);
 2219         if (realline >= linenr + count) {
 2220             break;
 2221         }
 2222         if (realline >= linenr) {
 2223             removedElements.append(se);
 2224         }
 2225         ++parentPos;
 2226     }
 2227     return parentPos;
 2228 }
 2229 
 2230 void LatexStructureMergerMerge::mergeStructure(StructureEntry *se)
 2231 {
 2232     if (!se) return;
 2233     if (se->type != StructureEntry::SE_DOCUMENT_ROOT && se->type != StructureEntry::SE_SECTION && se->type != StructureEntry::SE_INCLUDE) return;
 2234     int se_line = se->getRealLineNumber();
 2235     if (se_line < linenr || se->type == StructureEntry::SE_DOCUMENT_ROOT) {
 2236         //se is before updated region, but children might still be in it
 2237         updateParentVector(se);
 2238 
 2239         //if (!se->children.isEmpty() && se->children.last()->getRealLineNumber() >= linenr) {
 2240         int start = -1;
 2241         for (int i = 0; i < se->children.size(); i++) {
 2242             StructureEntry *c = se->children[i];
 2243             if (c->type != StructureEntry::SE_SECTION && c->type != StructureEntry::SE_INCLUDE) continue;
 2244             if (c->getRealLineNumber() < linenr)
 2245                 updateParentVector(c);
 2246             start = i;
 2247             break;
 2248         }
 2249         if (start >= 0) {
 2250             if (start > 0) start--;
 2251             mergeChildren(se, start);
 2252         }
 2253     } else {
 2254         //se is within or after the region
 2255         // => insert flatStructure.first() before se or replace se with it (don't insert after since there might be another "se" to replace)
 2256         while (!flatStructure->isEmpty() && se->getRealLineNumber() >= flatStructure->first()->getRealLineNumber() ) {
 2257             StructureEntry * next = flatStructure->takeFirst();
 2258             if (se->getRealLineNumber() == next->getRealLineNumber()) {
 2259                 //copy next to se except for parent/children
 2260                 next->parent = se->parent;
 2261                 next->children = se->children;
 2262                 *se = *next;
 2263                 next->children.clear();
 2264                 delete next;
 2265                 document->updateElementWithSignal(se);
 2266                 moveToAppropiatePositionWithSignal(se);
 2267                 //  qDebug()<<"a"<<se->children.size() << ":"<<se->title<<" von "<<linenr<<count;
 2268                 mergeChildren(se);
 2269                 return;
 2270             }
 2271             moveToAppropiatePositionWithSignal(next);
 2272         }
 2273 
 2274         if (se_line < linenr + count) {
 2275             //se is within the region (delete if necessary and then merge children)
 2276             if (flatStructure->isEmpty() || se->getRealLineNumber() < flatStructure->first()->getRealLineNumber()) {
 2277                 QList<StructureEntry *> oldChildren = se->children;
 2278                 int oldrow = se->getRealParentRow();
 2279                 for (int i = se->children.size() - 1; i >= 0; i--)
 2280                     document->moveElementWithSignal(se->children[i], se->parent, oldrow);
 2281                 document->removeElementWithSignal(se);
 2282                 delete se;
 2283                 for (int i = 1; i < parent_level.size(); i++)
 2284                     if (parent_level[i] == se)
 2285                         parent_level[i] = parent_level[i - 1];
 2286                 foreach (StructureEntry *next, oldChildren)
 2287                     mergeStructure(next);
 2288                 return;
 2289             }
 2290         }
 2291 
 2292         //se not replaced or deleted => se is after everything the region => keep children
 2293         moveToAppropiatePositionWithSignal(se);
 2294         QList<StructureEntry *> oldChildren = se->children;
 2295         foreach (StructureEntry *c, oldChildren)
 2296             moveToAppropiatePositionWithSignal(c);
 2297 
 2298     }
 2299 
 2300     //insert unprocessed elements of flatStructure at the end of the structure
 2301     if (se->type == StructureEntry::SE_DOCUMENT_ROOT && !flatStructure->isEmpty()) {
 2302         foreach (StructureEntry *s, *flatStructure) {
 2303             document->addElementWithSignal(parent_level[s->level], s);
 2304             updateParentVector(s);
 2305         }
 2306         flatStructure->clear();
 2307     }
 2308 }
 2309 
 2310 void LatexStructureMergerMerge::mergeChildren(StructureEntry *se, int start){
 2311     QList<StructureEntry *> oldChildren = se->children; //need to cow-protected list, in case se will be changed by mergeStructure
 2312     for (int i = start; i < oldChildren.size(); i++)
 2313         mergeStructure(oldChildren[i]);
 2314 }
 2315 
 2316 bool LatexDocument::IsInTree (StructureEntry *se)
 2317 {
 2318     Q_ASSERT(se);
 2319     while (se) {
 2320         if (se->type == StructureEntry::SE_DOCUMENT_ROOT) {
 2321             return true;
 2322         }
 2323         se = se->parent;
 2324     }
 2325     return false;
 2326 }
 2327 
 2328 void LatexDocument::removeElementWithSignal(StructureEntry *se)
 2329 {
 2330     int sendSignal = IsInTree(se);
 2331     int parentRow = se->getRealParentRow();
 2332     REQUIRE(parentRow >= 0);
 2333     if (sendSignal) {
 2334         emit removeElement(se, parentRow);
 2335     }
 2336     se->parent->children.removeAt(parentRow);
 2337     se->parent = nullptr;
 2338     if (sendSignal) {
 2339         emit removeElementFinished();
 2340     }
 2341 }
 2342 
 2343 void LatexDocument::addElementWithSignal(StructureEntry *parent, StructureEntry *se)
 2344 {
 2345     int sendSignal = IsInTree(parent);
 2346     if (sendSignal) {
 2347         emit addElement(parent, parent->children.size());
 2348     }
 2349     parent->children.append(se);
 2350     se->parent = parent;
 2351     if (sendSignal) {
 2352         emit addElementFinished();
 2353     }
 2354 }
 2355 
 2356 void LatexDocument::insertElementWithSignal(StructureEntry *parent, int pos, StructureEntry *se)
 2357 {
 2358     int sendSignal = IsInTree(parent);
 2359     if (sendSignal) {
 2360         emit addElement(parent, pos);
 2361     }
 2362     parent->children.insert(pos, se);
 2363     se->parent = parent;
 2364     if (sendSignal) {
 2365         emit addElementFinished();
 2366     }
 2367 }
 2368 
 2369 void LatexDocument::moveElementWithSignal(StructureEntry *se, StructureEntry *parent, int pos)
 2370 {
 2371     removeElementWithSignal(se);
 2372     insertElementWithSignal(parent, pos, se);
 2373 }
 2374 
 2375 void LatexStructureMerger::updateParentVector(StructureEntry *se)
 2376 {
 2377     REQUIRE(se);
 2378     if (se->type == StructureEntry::SE_DOCUMENT_ROOT
 2379                     || (se->type == StructureEntry::SE_INCLUDE && document->parent && !document->parent->indentIncludesInStructure))
 2380         parent_level.fill(document->baseStructure);
 2381     else if (se->type == StructureEntry::SE_SECTION)
 2382         for (int j = se->level + 1; j < parent_level.size(); j++)
 2383             parent_level[j] = se;
 2384 }
 2385 
 2386 class LessThanRealLineNumber
 2387 {
 2388 public:
 2389     inline bool operator()(const StructureEntry *const se1, const StructureEntry *const se2) const
 2390     {
 2391         int l1 = se1->getRealLineNumber();
 2392         int l2 = se2->getRealLineNumber();
 2393         if (l1 < l2) return true;
 2394         if (l1 == l2 && (se1->columnNumber < se2->columnNumber)) return true;
 2395         return false;
 2396     }
 2397 };
 2398 
 2399 void LatexStructureMerger::moveToAppropiatePositionWithSignal(StructureEntry *se)
 2400 {
 2401     REQUIRE(se);
 2402     StructureEntry *newParent = parent_level.value(se->level, nullptr);
 2403     if (!newParent) {
 2404         qDebug("structure update failed!");
 2405         return;
 2406     }
 2407     int oldPos, newPos;
 2408     LessThanRealLineNumber compare;
 2409     if (se->parent == newParent) {
 2410         //the construction somehow ensures that in this case
 2411         //se is already at the correct position regarding line numbers.
 2412         //but not necessarily regarding the column position
 2413         oldPos = se->getRealParentRow();
 2414         if ((oldPos == 0 || compare(newParent->children[oldPos - 1], se )) &&
 2415                 (oldPos == newParent->children.size() - 1 || compare(se, newParent->children[oldPos + 1] ))
 2416             )
 2417             newPos = oldPos;
 2418         else {
 2419             newPos = std::upper_bound(newParent->children.begin(), newParent->children.end(), se, compare) - newParent->children.begin();
 2420             while (newPos > 0
 2421                  && newParent->children[newPos-1]->getRealLineNumber() == se->getRealLineNumber()
 2422                  && newParent->children[newPos-1]->columnNumber == se->columnNumber
 2423             )
 2424                 newPos--; //upperbound always returns the position after se if it is in newParent->children
 2425         }
 2426     } else {
 2427         oldPos = -1;
 2428         if (newParent->children.size() > 0 &&
 2429                 newParent->children.last()->getRealLineNumber() >= se->getRealLineNumber())
 2430             newPos = std::upper_bound(newParent->children.begin(), newParent->children.end(), se, compare) - newParent->children.begin();
 2431         else
 2432             newPos = newParent->children.size();
 2433     }
 2434 
 2435 
 2436     //qDebug() << "auto insert " << se->title <<" at " << newPos;
 2437     if (se->parent) {
 2438         if (newPos != oldPos)
 2439             document->moveElementWithSignal(se, newParent, newPos);
 2440     } else document->insertElementWithSignal(newParent, newPos, se);
 2441 
 2442     updateParentVector(se);
 2443     return;
 2444 }
 2445 
 2446 /*!
 2447   Splits a [name] = [val] string into \a name and \a val removing extra spaces.
 2448 
 2449   \return true if splitting successful, false otherwise (in that case name and val are empty)
 2450  */
 2451 bool LatexDocument::splitMagicComment(const QString &comment, QString &name, QString &val)
 2452 {
 2453     int sep = comment.indexOf("=");
 2454     if (sep < 0) return false;
 2455     name = comment.left(sep).trimmed();
 2456     val = comment.mid(sep + 1).trimmed();
 2457     return true;
 2458 }
 2459 
 2460 /*!
 2461   Used by the parser to add a magic comment
 2462 
 2463 \a text is the comment without the leading "! TeX" declaration. e.g. "spellcheck = DE-de"
 2464 \a lineNr - line number of the magic comment
 2465 \a posMagicComment - Zero-based position of magic comment in the structure list tree view.
 2466   */
 2467 void LatexDocument::addMagicComment(const QString &text, int lineNr, int posMagicComment)
 2468 {
 2469     StructureEntry *newMagicComment = new StructureEntry(this, StructureEntry::SE_MAGICCOMMENT);
 2470     QDocumentLineHandle *dlh = line(lineNr).handle();
 2471     QString name;
 2472     QString val;
 2473     splitMagicComment(text, name, val);
 2474 
 2475     parseMagicComment(name, val, newMagicComment);
 2476     newMagicComment->title = text;
 2477     newMagicComment->setLine(dlh, lineNr);
 2478     insertElementWithSignal(magicCommentList, posMagicComment, newMagicComment);
 2479 }
 2480 
 2481 /*!
 2482   Formats the StructureEntry and modifies the document according to the MagicComment contents
 2483   */
 2484 void LatexDocument::parseMagicComment(const QString &name, const QString &val, StructureEntry *se)
 2485 {
 2486     se->valid = false;
 2487     se->tooltip = QString();
 2488 
 2489     QString lowerName = name.toLower();
 2490     if (lowerName == "spellcheck") {
 2491         mSpellingDictName = val;
 2492         emit spellingDictChanged(mSpellingDictName);
 2493         se->valid = true;
 2494     } else if ((lowerName == "texroot") || (lowerName == "root")) {
 2495         QString fname = findFileName(val);
 2496         LatexDocument *dc = parent->findDocumentFromName(fname);
 2497         if (dc) {
 2498             dc->childDocs.insert(this);
 2499             setMasterDocument(dc);
 2500         } else {
 2501             parent->addDocToLoad(fname);
 2502         }
 2503         se->valid = true;
 2504     } else if (lowerName == "encoding") {
 2505         QTextCodec *codec = QTextCodec::codecForName(val.toLatin1());
 2506         if (!codec) {
 2507             se->tooltip = tr("Invalid codec");
 2508             return;
 2509         }
 2510         setCodecDirect(codec);
 2511         emit encodingChanged();
 2512         se->valid = true;
 2513     } else if (lowerName == "txs-script") {
 2514         se->valid = true;
 2515     } else if (lowerName == "program" || lowerName == "ts-program" || lowerName.startsWith("txs-program:")) {
 2516         se->valid = true;
 2517     } else if (lowerName == "-job-name") {
 2518         if (!val.isEmpty()) {
 2519             se->valid = true;
 2520         } else {
 2521             se->tooltip = tr("Missing value for -job-name");
 2522         }
 2523     } else {
 2524         se->tooltip = tr("Unknown magic comment");
 2525     }
 2526 }
 2527 
 2528 void LatexDocument::updateContext(QDocumentLineHandle *oldLine, QDocumentLineHandle *newLine, StructureEntry::Context context)
 2529 {
 2530     int endLine = newLine ? indexOf(newLine) : -1 ;
 2531     int startLine = -1;
 2532     if (oldLine) {
 2533         startLine = indexOf(oldLine);
 2534         if (endLine < 0 || endLine > startLine) {
 2535             // remove appendic marker
 2536             StructureEntry *se = baseStructure;
 2537             setContextForLines(se, startLine, endLine, context, false);
 2538         }
 2539     }
 2540 
 2541     if (endLine > -1 && (endLine < startLine || startLine < 0)) {
 2542         StructureEntry *se = baseStructure;
 2543         setContextForLines(se, endLine, startLine, context, true);
 2544     }
 2545 }
 2546 
 2547 void LatexDocument::setContextForLines(StructureEntry *se, int startLine, int endLine, StructureEntry::Context context, bool state)
 2548 {
 2549     bool first = false;
 2550     for (int i = 0; i < se->children.size(); i++) {
 2551         StructureEntry *elem = se->children[i];
 2552         if (endLine >= 0 && elem->getLineHandle() && elem->getRealLineNumber() > endLine) break;
 2553         if (elem->type == StructureEntry::SE_SECTION && elem->getRealLineNumber() > startLine) {
 2554             if (!first && i > 0) setContextForLines(se->children[i - 1], startLine, endLine, context, state);
 2555             elem->setContext(context, state);
 2556             emit updateElement(elem);
 2557             setContextForLines(se->children[i], startLine, endLine, context, state);
 2558             first = true;
 2559         }
 2560     }
 2561     if (!first && !se->children.isEmpty()) {
 2562         StructureEntry *elem = se->children.last();
 2563         if (elem->type == StructureEntry::SE_SECTION) setContextForLines(elem, startLine, endLine, context, state);
 2564     }
 2565 }
 2566 
 2567 bool LatexDocument::fileExits(QString fname)
 2568 {
 2569     QString curPath = ensureTrailingDirSeparator(getFileInfo().absolutePath());
 2570     bool exist = QFile(getAbsoluteFilePath(fname, ".tex")).exists();
 2571     if (!exist) exist = QFile(getAbsoluteFilePath(curPath + fname, ".tex")).exists();
 2572     if (!exist) exist = QFile(getAbsoluteFilePath(curPath + fname, "")).exists();
 2573     return exist;
 2574 }
 2575 
 2576 /*!
 2577  * A line snapshot is a list of DocumentLineHandles at a given time.
 2578  * For example, this is used to reconstruct the line number at latex compile time
 2579  * allowing syncing from PDF to the correct source line also after altering the source document
 2580  */
 2581 void LatexDocument::saveLineSnapshot()
 2582 {
 2583     foreach (QDocumentLineHandle *dlh, mLineSnapshot) {
 2584         dlh->deref();
 2585     }
 2586     mLineSnapshot.clear();
 2587     mLineSnapshot.reserve(lineCount());
 2588     QDocumentConstIterator it = begin(), e = end();
 2589     while (it != e) {
 2590         mLineSnapshot.append(*it);
 2591         (*it)->ref();
 2592         it++;
 2593     }
 2594 }
 2595 
 2596 // get the line with given lineNumber (0-based) from the snapshot
 2597 QDocumentLine LatexDocument::lineFromLineSnapshot(int lineNumber)
 2598 {
 2599     if (lineNumber < 0 || lineNumber >= mLineSnapshot.count()) return QDocumentLine();
 2600     return QDocumentLine(mLineSnapshot.at(lineNumber));
 2601 }
 2602 
 2603 // returns the 0-based number of the line in the snapshot, or -1 if line is not in the snapshot
 2604 int LatexDocument::lineToLineSnapshotLineNumber(const QDocumentLine &line)
 2605 {
 2606     return mLineSnapshot.indexOf(line.handle());
 2607 }
 2608 
 2609 QString LatexDocument::findFileName(QString fname)
 2610 {
 2611     QString curPath = ensureTrailingDirSeparator(getFileInfo().absolutePath());
 2612     QString result;
 2613     if (QFile(getAbsoluteFilePath(fname, ".tex")).exists())
 2614         result = QFileInfo(getAbsoluteFilePath(fname, ".tex")).absoluteFilePath();
 2615     if (result.isEmpty() && QFile(getAbsoluteFilePath(curPath + fname, ".tex")).exists())
 2616         result = QFileInfo(getAbsoluteFilePath(curPath + fname, ".tex")).absoluteFilePath();
 2617     if (result.isEmpty() && QFile(getAbsoluteFilePath(curPath + fname, "")).exists())
 2618         result = QFileInfo(getAbsoluteFilePath(curPath + fname, "")).absoluteFilePath();
 2619     return result;
 2620 }
 2621 
 2622 void LatexDocuments::updateStructure()
 2623 {
 2624     foreach (const LatexDocument *doc, documents) {
 2625         model->updateElement(doc->baseStructure);
 2626     }
 2627     if (model->getSingleDocMode()) {
 2628         model->structureUpdated(currentDocument, nullptr);
 2629     }
 2630 }
 2631 
 2632 void LatexDocuments::bibTeXFilesNeedUpdate()
 2633 {
 2634     bibTeXFilesModified = true;
 2635 }
 2636 
 2637 void LatexDocuments::updateMasterSlaveRelations(LatexDocument *doc, bool recheckRefs, bool updateCompleterNow)
 2638 {
 2639     //update Master/Child relations
 2640     //remove old settings ...
 2641     doc->setMasterDocument(nullptr, false);
 2642     QList<LatexDocument *> docs = getDocuments();
 2643     foreach (LatexDocument *elem, docs) {
 2644         if (elem->getMasterDocument() == doc) {
 2645             elem->setMasterDocument(nullptr, recheckRefs);
 2646             doc->removeChild(elem);
 2647             //elem->recheckRefsLabels();
 2648         }
 2649     }
 2650 
 2651     //check whether document is child of other docs
 2652     QString fname = doc->getFileName();
 2653     foreach (LatexDocument *elem, docs) {
 2654         if (elem == doc)
 2655             continue;
 2656         QStringList includedFiles = elem->includedFiles();
 2657         if (includedFiles.contains(fname)) {
 2658             elem->addChild(doc);
 2659             doc->setMasterDocument(elem, recheckRefs);
 2660         }
 2661     }
 2662 
 2663     // check for already open child documents (included in this file)
 2664     QStringList includedFiles = doc->includedFiles();
 2665     foreach (const QString &fname, includedFiles) {
 2666         LatexDocument *child = this->findDocumentFromName(fname);
 2667         if (child) {
 2668             doc->addChild(child);
 2669             child->setMasterDocument(doc, recheckRefs);
 2670             if (recheckRefs)
 2671                 child->reCheckSyntax(); // redo syntax checking (in case of defined commands)
 2672         }
 2673     }
 2674 
 2675     //recheck references
 2676     if (recheckRefs)
 2677         doc->recheckRefsLabels();
 2678 
 2679     if (updateCompleterNow)
 2680         doc->emitUpdateCompleter();
 2681 }
 2682 
 2683 const LatexDocument *LatexDocument::getRootDocument(QSet<const LatexDocument *> *visitedDocs) const
 2684 {
 2685     // special handling if explicit master is set
 2686     if(!parent) return nullptr;
 2687     if (parent && parent->masterDocument)
 2688         return parent->masterDocument;
 2689     const LatexDocument *result = this;
 2690     bool deleteVisitedDocs = false;
 2691     if (!visitedDocs) {
 2692         visitedDocs = new QSet<const LatexDocument *>();
 2693         deleteVisitedDocs = true;
 2694     }
 2695     visitedDocs->insert(this);
 2696     if (masterDocument && !visitedDocs->contains(masterDocument))
 2697         result = masterDocument->getRootDocument(visitedDocs);
 2698     if (result->getFileName().endsWith("bib"))
 2699         foreach (const LatexDocument *d, parent->documents) {
 2700             QMultiHash<QDocumentLineHandle *, FileNamePair>::const_iterator it = d->mentionedBibTeXFiles().constBegin();
 2701             QMultiHash<QDocumentLineHandle *, FileNamePair>::const_iterator itend = d->mentionedBibTeXFiles().constEnd();
 2702             for (; it != itend; ++it) {
 2703                 //qDebug() << it.value().absolute << " <> "<<result->getFileName();
 2704                 if (it.value().absolute == result->getFileName()) {
 2705                     result = d->getRootDocument(visitedDocs);
 2706                     break;
 2707                 }
 2708             }
 2709             if (result == d) break;
 2710         }
 2711     if (deleteVisitedDocs)
 2712         delete visitedDocs;
 2713     return result;
 2714 }
 2715 
 2716 LatexDocument *LatexDocument::getRootDocument()
 2717 {
 2718     return const_cast<LatexDocument *>(getRootDocument(nullptr));
 2719 }
 2720 
 2721 QStringList LatexDocument::includedFiles()
 2722 {
 2723     QStringList helper = mIncludedFilesList.values();
 2724     QStringList result;
 2725     foreach (const QString elem, helper) {
 2726         if (!elem.isEmpty() && !result.contains(elem))
 2727             result << elem;
 2728     }
 2729 
 2730     return result;
 2731 }
 2732 
 2733 QStringList LatexDocument::includedFilesAndParent()
 2734 {
 2735     QStringList result = includedFiles();
 2736     QString t = getMagicComment("root");
 2737     if (!t.isEmpty() && !result.contains(t)) result << t;
 2738     t = getMagicComment("texroot");
 2739     if (!t.isEmpty() && !result.contains(t)) result << t;
 2740     if (masterDocument && !result.contains(masterDocument->getFileName()))
 2741         result << masterDocument->getFileName();
 2742     return result;
 2743 }
 2744 
 2745 CodeSnippetList LatexDocument::additionalCommandsList()
 2746 {
 2747     LatexPackage pck;
 2748     QStringList loadedFiles, files;
 2749     files = mCWLFiles.values();
 2750     gatherCompletionFiles(files, loadedFiles, pck, true);
 2751     return pck.completionWords;
 2752 }
 2753 
 2754 bool LatexDocument::updateCompletionFiles(const bool forceUpdate, const bool forceLabelUpdate, const bool delayUpdate, const bool dontPatch)
 2755 {
 2756 
 2757     QStringList files = mUsepackageList.values();
 2758     bool update = forceUpdate;
 2759     LatexParser &latexParser = LatexParser::getInstance();
 2760 
 2761     //recheck syntax of ALL documents ...
 2762     LatexPackage pck;
 2763     pck.commandDescriptions = latexParser.commandDefs;
 2764     pck.specialDefCommands = latexParser.specialDefCommands;
 2765     QStringList loadedFiles;
 2766     for (int i = 0; i < files.count(); i++) {
 2767         if (!files.at(i).endsWith(".cwl"))
 2768             files[i] = files[i] + ".cwl";
 2769     }
 2770     gatherCompletionFiles(files, loadedFiles, pck);
 2771     update = true;
 2772 
 2773     mCWLFiles = convertStringListtoSet(loadedFiles);
 2774     QSet<QString> userCommandsForSyntaxCheck = ltxCommands.possibleCommands["user"];
 2775     QSet<QString> columntypeForSyntaxCheck = ltxCommands.possibleCommands["%columntypes"];
 2776     ltxCommands.optionCommands = pck.optionCommands;
 2777     ltxCommands.specialTreatmentCommands = pck.specialTreatmentCommands;
 2778     ltxCommands.specialDefCommands = pck.specialDefCommands;
 2779     ltxCommands.possibleCommands = pck.possibleCommands;
 2780     ltxCommands.environmentAliases = pck.environmentAliases;
 2781     ltxCommands.commandDefs = pck.commandDescriptions;
 2782     QSet<QString> pckSet = pck.possibleCommands["user"];
 2783     ltxCommands.possibleCommands["user"] = userCommandsForSyntaxCheck.unite(pckSet);
 2784     ltxCommands.possibleCommands["%columntypes"] = columntypeForSyntaxCheck;
 2785 
 2786     // user commands
 2787     QList<UserCommandPair> commands = mUserCommandList.values();
 2788     foreach (UserCommandPair cmd, commands) {
 2789         QString elem = cmd.snippet.word;
 2790         if (elem.startsWith("%")) { // insert specialArgs
 2791             int i = elem.indexOf('%', 1);
 2792             QString category = elem.left(i);
 2793             elem = elem.mid(i + 1);
 2794             ltxCommands.possibleCommands[category].insert(elem);
 2795             continue;
 2796         }
 2797         if (!elem.startsWith("\\begin{") && !elem.startsWith("\\end{")) {
 2798             int i = elem.indexOf(QRegExp("\\W"), 1);
 2799             //int j=elem.indexOf("[");
 2800             if (i >= 0) elem = elem.left(i);
 2801             //if(j>=0 && j<i) elem=elem.left(j);
 2802         }
 2803     }
 2804 
 2805     //patch lines for new commands (ref,def, etc)
 2806 
 2807     QStringList categories;
 2808     categories << "%ref" << "%label" << "%definition" << "%cite" << "%citeExtended" << "%citeExtendedCommand" << "%usepackage" << "%graphics" << "%file" << "%bibliography" << "%include" << "%url" << "%todo" << "%replace";
 2809     QStringList newCmds;
 2810     foreach (const QString elem, categories) {
 2811         QStringList cmds = ltxCommands.possibleCommands[elem].values();
 2812         foreach (const QString cmd, cmds) {
 2813             if (!latexParser.possibleCommands[elem].contains(cmd) || forceLabelUpdate) {
 2814                 newCmds << cmd;
 2815                 latexParser.possibleCommands[elem] << cmd;
 2816             }
 2817         }
 2818     }
 2819     bool needQNFAupdate = false;
 2820     for (int i = 0; i < latexParser.MAX_STRUCTURE_LEVEL; i++) {
 2821         QString elem = QString("%structure%1").arg(i);
 2822         QStringList cmds = ltxCommands.possibleCommands[elem].values();
 2823         foreach (const QString cmd, cmds) {
 2824             bool update = !latexParser.possibleCommands[elem].contains(cmd);
 2825             if (update) {
 2826                 latexParser.possibleCommands[elem] << cmd;
 2827                 //only update QNFA for added commands. When the default commands are not in ltxCommands.possibleCommands[elem], ltxCommands.possibleCommands[elem] and latexParser.possibleCommands[elem] will always differ and regenerate the QNFA needlessly after every key press
 2828                 needQNFAupdate = true;
 2829             }
 2830             if (update || forceLabelUpdate)
 2831                 newCmds << cmd;
 2832         }
 2833     }
 2834     if (needQNFAupdate)
 2835         parent->requestQNFAupdate();
 2836 
 2837 
 2838     if (!dontPatch && !newCmds.isEmpty()) {
 2839         patchLinesContaining(newCmds);
 2840     }
 2841 
 2842     if (delayUpdate)
 2843         return update;
 2844 
 2845     if (update) {
 2846         updateLtxCommands(true);
 2847     }
 2848     return false;
 2849 }
 2850 
 2851 const QSet<QString> &LatexDocument::getCWLFiles() const
 2852 {
 2853     return mCWLFiles;
 2854 }
 2855 
 2856 void LatexDocument::emitUpdateCompleter()
 2857 {
 2858     emit updateCompleter();
 2859 }
 2860 
 2861 void LatexDocument::gatherCompletionFiles(QStringList &files, QStringList &loadedFiles, LatexPackage &pck, bool gatherForCompleter)
 2862 {
 2863     LatexPackage zw;
 2864     LatexCompleterConfig *completerConfig = edView->getCompleter()->getConfig();
 2865     foreach (const QString &elem, files) {
 2866         if (loadedFiles.contains(elem))
 2867             continue;
 2868         if (parent->cachedPackages.contains(elem)) {
 2869             zw = parent->cachedPackages.value(elem);
 2870         } else {
 2871             QString fileName = LatexPackage::keyToCwlFilename(elem);
 2872             QStringList options = LatexPackage::keyToOptions(elem);
 2873             zw = loadCwlFile(fileName, completerConfig, options);
 2874             if (!zw.notFound) {
 2875                 parent->cachedPackages.insert(elem, zw); // cache package
 2876             } else {
 2877                 LatexPackage zw;
 2878                 zw.packageName = elem;
 2879                 parent->cachedPackages.insert(elem, zw); // cache package as empty/not found package
 2880             }
 2881         }
 2882         if (zw.notFound) {
 2883             QString name = elem;
 2884             LatexDocument *masterDoc = getRootDocument();
 2885             if (masterDoc) {
 2886                 QString fn = masterDoc->getFileInfo().absolutePath();
 2887                 name += "/" + fn;
 2888                 // TODO: oha, the key can be even more complex: option#filename.cwl/masterfile
 2889                 // consider this in the key-handling functions of LatexPackage
 2890             }
 2891             emit importPackage(name);
 2892         } else {
 2893             pck.unite(zw, gatherForCompleter);
 2894             loadedFiles.append(elem);
 2895             if (!zw.requiredPackages.isEmpty())
 2896                 gatherCompletionFiles(zw.requiredPackages, loadedFiles, pck, gatherForCompleter);
 2897         }
 2898     }
 2899 }
 2900 
 2901 QString LatexDocument::getMagicComment(const QString &name) const
 2902 {
 2903     QString seName;
 2904     QString val;
 2905     StructureEntryIterator iter(magicCommentList);
 2906     while (iter.hasNext()) {
 2907         StructureEntry *se = iter.next();
 2908         splitMagicComment(se->title, seName, val);
 2909         if (seName.toLower() == name.toLower())
 2910             return val;
 2911     }
 2912     return QString();
 2913 }
 2914 
 2915 StructureEntry *LatexDocument::getMagicCommentEntry(const QString &name) const
 2916 {
 2917     QString seName;
 2918     QString val;
 2919 
 2920     if (!magicCommentList) return nullptr;
 2921 
 2922     StructureEntryIterator iter(magicCommentList);
 2923     while (iter.hasNext()) {
 2924         StructureEntry *se = iter.next();
 2925         splitMagicComment(se->title, seName, val);
 2926         if (seName == name) return se;
 2927     }
 2928     return nullptr;
 2929 }
 2930 
 2931 /*!
 2932   replaces the value of the magic comment
 2933  */
 2934 void LatexDocument::updateMagicComment(const QString &name, const QString &val, bool createIfNonExisting,QString prefix)
 2935 {
 2936     QString line(QString("% %1 %2 = %3").arg(prefix).arg(name).arg(val));
 2937 
 2938     StructureEntry *se = getMagicCommentEntry(name);
 2939     QDocumentLineHandle *dlh = se ? se->getLineHandle() : nullptr;
 2940     if (dlh) {
 2941         QString n, v;
 2942         splitMagicComment(se->title, n, v);
 2943         if (v != val) {
 2944             QDocumentCursor cur(this, indexOf(dlh));
 2945             cur.select(QDocumentCursor::LineUnderCursor);
 2946             cur.replaceSelectedText(line);
 2947         }
 2948     } else {
 2949         if (createIfNonExisting) {
 2950             QDocumentCursor cur(this);
 2951             cur.insertText(line + "\n");
 2952         }
 2953     }
 2954 }
 2955 
 2956 void LatexDocument::updateMagicCommentScripts()
 2957 {
 2958     if (!magicCommentList) return;
 2959 
 2960     localMacros.clear();
 2961 
 2962     QRegExp rxTrigger(" *// *(Trigger) *[:=](.*)");
 2963 
 2964     StructureEntryIterator iter(magicCommentList);
 2965     while (iter.hasNext()) {
 2966         StructureEntry *se = iter.next();
 2967         QString seName, val;
 2968         splitMagicComment(se->title, seName, val);
 2969         if (seName == "TXS-SCRIPT") {
 2970             QString name = val;
 2971             QString trigger = "";
 2972             QString tag;
 2973 
 2974             int l = se->getRealLineNumber() + 1;
 2975             for (; l < lineCount(); l++) {
 2976                 QString lt = line(l).text().trimmed();
 2977                 if (lt.endsWith("TXS-SCRIPT-END") || !(lt.isEmpty() || lt.startsWith("%"))  ) break;
 2978                 lt.remove(0, 1);
 2979                 tag += lt + "\n";
 2980                 if (rxTrigger.exactMatch(lt))
 2981                     trigger = rxTrigger.cap(2).trimmed();
 2982             }
 2983 
 2984             Macro newMacro(name, Macro::Script, tag, "", trigger);
 2985             newMacro.document = this;
 2986             localMacros.append(newMacro);
 2987         }
 2988     }
 2989 }
 2990 
 2991 /*!
 2992  * Return whether the use of package \a name is declared in this document.
 2993  */
 2994 bool LatexDocument::containsPackage(const QString &name)
 2995 {
 2996     return containedPackages().contains(name);
 2997 }
 2998 
 2999 /*!
 3000  * Return all package names of packages that are declared in this document.
 3001  */
 3002 QStringList LatexDocument::containedPackages()
 3003 {
 3004     QStringList packages;
 3005     foreach(QString elem, mUsepackageList.values()) {
 3006         int i = elem.indexOf('#');
 3007         if (i >= 0) {
 3008             elem = elem.mid(i + 1);
 3009         }
 3010         packages << elem;
 3011     }
 3012     return packages;
 3013 }
 3014 
 3015 /*!
 3016  * Return a list of packages that are available in the document.
 3017  * This includes all packages declared in all project files.
 3018  */
 3019 QSet<QString> LatexDocument::usedPackages()
 3020 {
 3021     QSet<QString> packages;
 3022     foreach (LatexDocument *doc, getListOfDocs()) {
 3023         packages.unite(convertStringListtoSet(doc->containedPackages()));
 3024     }
 3025     return packages;
 3026 }
 3027 
 3028 LatexDocument *LatexDocuments::getRootDocumentForDoc(LatexDocument *doc) const   // doc==0 means current document
 3029 {
 3030     if (masterDocument)
 3031         return masterDocument;
 3032     LatexDocument *current = currentDocument;
 3033     if (doc)
 3034         current = doc;
 3035     if (!current)
 3036         return current;
 3037     return current->getRootDocument();
 3038 }
 3039 
 3040 QString LatexDocument::getAbsoluteFilePath(const QString &relName, const QString &extension, const QStringList &additionalSearchPaths) const
 3041 {
 3042     QStringList searchPaths;
 3043     const LatexDocument *rootDoc = getRootDocument();
 3044     QString compileFileName = rootDoc->getFileName();
 3045     if (compileFileName.isEmpty()) compileFileName = rootDoc->getTemporaryFileName();
 3046     QString fallbackPath;
 3047     if (!compileFileName.isEmpty()) {
 3048         fallbackPath = QFileInfo(compileFileName).absolutePath(); //when the file does not exist, resolve it relative to document (e.g. to create it there)
 3049         searchPaths << fallbackPath;
 3050     }
 3051     searchPaths << additionalSearchPaths;
 3052     return findAbsoluteFilePath(relName, extension, searchPaths, fallbackPath);
 3053 }
 3054 
 3055 void LatexDocuments::lineGrammarChecked(const void *doc, const void *line, int lineNr, const QList<GrammarError> &errors)
 3056 {
 3057     int d = documents.indexOf(static_cast<LatexDocument *>(const_cast<void *>(doc)));
 3058     if (d == -1) return;
 3059     if (!documents[d]->getEditorView()) return;
 3060     documents[d]->getEditorView()->lineGrammarChecked(doc, line, lineNr, errors);
 3061 }
 3062 
 3063 void LatexDocument::patchLinesContaining(const QStringList cmds)
 3064 {
 3065     foreach (LatexDocument *elem, getListOfDocs()) {
 3066         // search all cmds in all lines, patch line if cmd is found
 3067         for (int i = 0; i < elem->lines(); i++) {
 3068             QString text = elem->line(i).text();
 3069             foreach (const QString cmd, cmds) {
 3070                 if (text.contains(cmd)) {
 3071                     elem->patchStructure(i, 1);
 3072                     //patchStructure(i,1);
 3073                     break;
 3074                 }
 3075             }
 3076         }
 3077     }
 3078 }
 3079 
 3080 void LatexDocuments::enablePatch(const bool enable)
 3081 {
 3082     m_patchEnabled = enable;
 3083 }
 3084 
 3085 bool LatexDocuments::patchEnabled()
 3086 {
 3087     return m_patchEnabled;
 3088 }
 3089 
 3090 void LatexDocuments::requestQNFAupdate()
 3091 {
 3092     emit updateQNFA();
 3093 }
 3094 
 3095 QString LatexDocuments::findPackageByCommand(const QString command)
 3096 {
 3097     // go through all cached packages (cwl) and find command in one of them
 3098     QString result;
 3099     foreach (const QString key, cachedPackages.keys()) {
 3100         const LatexPackage pck = cachedPackages.value(key);
 3101         foreach (const QString envs, pck.possibleCommands.keys()) {
 3102             if (pck.possibleCommands.value(envs).contains(command)) {
 3103                 result = LatexPackage::keyToCwlFilename(key); //pck.packageName;
 3104                 break;
 3105             }
 3106         }
 3107         if (!result.isEmpty())
 3108             break;
 3109     }
 3110     return result;
 3111 }
 3112 
 3113 
 3114 void LatexDocument::updateLtxCommands(bool updateAll)
 3115 {
 3116     lp.init();
 3117     lp.append(LatexParser::getInstance()); // append commands set in config
 3118     QList<LatexDocument *>listOfDocs = getListOfDocs();
 3119     foreach (const LatexDocument *elem, listOfDocs) {
 3120         lp.append(elem->ltxCommands);
 3121     }
 3122 
 3123     if (updateAll) {
 3124         foreach (LatexDocument *elem, listOfDocs) {
 3125             elem->setLtxCommands(lp);
 3126             if(elem!=this){
 3127                 elem->reCheckSyntax();
 3128             }
 3129         }
 3130         // check if other document have this doc as child as well (reused doc...)
 3131         LatexDocuments *docs = parent;
 3132         QList<LatexDocument *>lstOfAllDocs = docs->getDocuments();
 3133         foreach (LatexDocument *elem, lstOfAllDocs) {
 3134             if (listOfDocs.contains(elem))
 3135                 continue; // already handled
 3136             if (elem->containsChild(this)) {
 3137                 // unhandled parent/child
 3138                 LatexParser lp;
 3139                 lp.init();
 3140                 lp.append(LatexParser::getInstance()); // append commands set in config
 3141                 QList<LatexDocument *>listOfDocs = elem->getListOfDocs();
 3142                 foreach (const LatexDocument *elem, listOfDocs) {
 3143                     lp.append(elem->ltxCommands);
 3144                 }
 3145                 foreach (LatexDocument *elem, listOfDocs) {
 3146                     elem->setLtxCommands(lp);
 3147                     elem->reCheckSyntax();
 3148                 }
 3149             }
 3150         }
 3151     } else {
 3152         SynChecker.setLtxCommands(lp);
 3153     }
 3154 
 3155     LatexEditorView *view = getEditorView();
 3156     if (view) {
 3157         view->updateReplamentList(lp, false);
 3158     }
 3159 }
 3160 
 3161 void LatexDocument::setLtxCommands(const LatexParser &cmds)
 3162 {
 3163     SynChecker.setLtxCommands(cmds);
 3164     lp = cmds;
 3165 
 3166     LatexEditorView *view = getEditorView();
 3167     if (view) {
 3168         view->updateReplamentList(cmds, false);
 3169     }
 3170 }
 3171 
 3172 void LatexDocument::setSpeller(SpellerUtility *speller)
 3173 {
 3174     SynChecker.setSpeller(speller);
 3175 }
 3176 
 3177 void LatexDocument::setReplacementList(QMap<QString, QString> replacementList)
 3178 {
 3179     SynChecker.setReplacementList(replacementList);
 3180 }
 3181 
 3182 void LatexDocument::updateSettings()
 3183 {
 3184     SynChecker.setErrFormat(syntaxErrorFormat);
 3185     QMap<QString,int> fmtList;
 3186     QList<QPair<QString,QString> >formats;
 3187     formats<<QPair<QString,QString>("math","numbers")<<QPair<QString,QString>("verbatim","verbatim")<<QPair<QString,QString>("picture","picture")
 3188             <<QPair<QString,QString>("#math","math-keyword")<<QPair<QString,QString>("#picture","picture-keyword")<<QPair<QString,QString>("&math","math-delimiter")
 3189             <<QPair<QString,QString>("align-ampersand","align-ampersand");
 3190     for(const auto &elem : formats){
 3191         fmtList.insert(elem.first,getFormatId(elem.second));
 3192     }
 3193     SynChecker.setFormats(fmtList);
 3194 }
 3195 
 3196 void LatexDocument::checkNextLine(QDocumentLineHandle *dlh, bool clearOverlay, int ticket, int hint)
 3197 {
 3198     Q_ASSERT_X(dlh != nullptr, "checkNextLine", "empty dlh used in checkNextLine");
 3199     if (dlh->getRef() > 1 && dlh->getCurrentTicket() == ticket) {
 3200         StackEnvironment env;
 3201         QVariant envVar = dlh->getCookieLocked(QDocumentLine::STACK_ENVIRONMENT_COOKIE);
 3202         if (envVar.isValid())
 3203             env = envVar.value<StackEnvironment>();
 3204         int index = indexOf(dlh,hint);
 3205         if (index == -1) return; //deleted
 3206         REQUIRE(dlh->document() == this);
 3207         if (index + 1 >= lines()) {
 3208             //remove old errror marker
 3209             if (unclosedEnv.id != -1) {
 3210                 unclosedEnv.id = -1;
 3211                 int unclosedEnvIndex = indexOf(unclosedEnv.dlh);
 3212                 if (unclosedEnvIndex >= 0 && unclosedEnv.dlh->getCookieLocked(QDocumentLine::UNCLOSED_ENVIRONMENT_COOKIE).isValid()) {
 3213                     StackEnvironment env;
 3214                     Environment newEnv;
 3215                     newEnv.name = "normal";
 3216                     newEnv.id = 1;
 3217                     env.push(newEnv);
 3218                     TokenStack remainder;
 3219                     if (unclosedEnvIndex >= 1) {
 3220                         QDocumentLineHandle *prev = line(unclosedEnvIndex - 1).handle();
 3221                         QVariant result = prev->getCookieLocked(QDocumentLine::STACK_ENVIRONMENT_COOKIE);
 3222                         if (result.isValid())
 3223                             env = result.value<StackEnvironment>();
 3224                         remainder = prev->getCookieLocked(QDocumentLine::LEXER_REMAINDER_COOKIE).value<TokenStack >();
 3225                     }
 3226                     SynChecker.putLine(unclosedEnv.dlh, env, remainder, true, unclosedEnvIndex);
 3227                 }
 3228             }
 3229             if (env.size() > 1) {
 3230                 //at least one env has not been closed
 3231                 Environment environment = env.top();
 3232                 unclosedEnv = env.top();
 3233                 SynChecker.markUnclosedEnv(environment);
 3234             }
 3235             return;
 3236         }
 3237         TokenStack remainder = dlh->getCookieLocked(QDocumentLine::LEXER_REMAINDER_COOKIE).value<TokenStack >();
 3238         SynChecker.putLine(line(index + 1).handle(), env, remainder, clearOverlay,index+1);
 3239     }
 3240     dlh->deref();
 3241 }
 3242 
 3243 bool LatexDocument::languageIsLatexLike() const
 3244 {
 3245     QLanguageDefinition *ld = languageDefinition();
 3246     if (!ld) return false;
 3247     return LATEX_LIKE_LANGUAGES.contains(ld->language());
 3248 }
 3249 
 3250 /*
 3251  * \brief Forces syntax recheck of a group of lines
 3252  * \param[in] lineStart Starting line number to be checked
 3253  * \param[in] lineNum Total number of lines to be checked. If -1, then check all lines to the end of the document.
 3254  */
 3255 void LatexDocument::reCheckSyntax(int lineStart, int lineNum)
 3256 {
 3257     // Basic sanity checks
 3258     Q_ASSERT(lineStart >= 0);
 3259     Q_ASSERT((lineNum == -1) || (lineNum > 0));
 3260 
 3261     // If the document does not support syntax checking just return silently
 3262     if (!languageIsLatexLike()) {
 3263         return;
 3264     }
 3265 
 3266     int lineTotal = lineCount();
 3267     int lineEnd;
 3268     if (lineNum == -1) {
 3269         lineEnd = lineTotal;
 3270     } else {
 3271         if ((lineEnd = lineStart + lineNum) > lineTotal) {
 3272             lineEnd = lineTotal;
 3273         }
 3274     }
 3275     // Fast return if zero lines will be checked
 3276     if (lineStart == lineEnd) {
 3277         return;
 3278     }
 3279 
 3280     // Delete the environment cookies for the specified lines to force their re-check
 3281     for (int i = lineStart; i < lineEnd; ++i) {
 3282         // We rely on the fact that QDocumentLine::removeCookie() holds a write lock of the corresponding
 3283         // line handle while removing the cookie. Lack of write locking causes crashes due to simultaneous
 3284         // access from the syntax checker thread.
 3285         line(i).removeCookie(QDocumentLine::STACK_ENVIRONMENT_COOKIE);
 3286     }
 3287 
 3288     // Enqueue the first line for syntax checking. The remaining lines will be enqueued automatically
 3289     // through the checkNextLine signal because we deleted their STACK_ENVIRONMENT_COOKIE cookies.
 3290     StackEnvironment prevEnv;
 3291     getEnv(lineStart, prevEnv);
 3292     TokenStack prevTokens;
 3293     if (lineStart) {
 3294         prevTokens = line(lineStart-1).getCookie(QDocumentLine::LEXER_REMAINDER_COOKIE).value<TokenStack>();
 3295     }
 3296     SynChecker.putLine(line(lineStart).handle(), prevEnv, prevTokens, true, lineStart);
 3297 }
 3298 
 3299 QString LatexDocument::getErrorAt(QDocumentLineHandle *dlh, int pos, StackEnvironment previous, TokenStack stack)
 3300 {
 3301     return SynChecker.getErrorAt(dlh, pos, previous, stack);
 3302 }
 3303 
 3304 int LatexDocument::syntaxErrorFormat;
 3305 
 3306 void LatexDocument::getEnv(int lineNumber, StackEnvironment &env)
 3307 {
 3308     Environment newEnv;
 3309     newEnv.name = "normal";
 3310     newEnv.id = 1;
 3311     env.push(newEnv);
 3312     if (lineNumber > 0) {
 3313         QDocumentLine prev = this->line(lineNumber - 1);
 3314         REQUIRE(prev.isValid());
 3315         QVariant result = prev.getCookie(QDocumentLine::STACK_ENVIRONMENT_COOKIE);
 3316         if (result.isValid())
 3317             env = result.value<StackEnvironment>();
 3318     }
 3319 }
 3320 
 3321 QString LatexDocument::getLastEnvName(int lineNumber)
 3322 {
 3323     StackEnvironment env;
 3324     getEnv(lineNumber, env);
 3325     if (env.isEmpty())
 3326         return "";
 3327     return env.top().name;
 3328 }