"Fossies" - the Fresh Open Source Software Archive

Member "texstudio-3.1.1/src/syntaxcheck.cpp" (21 Feb 2021, 43147 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 "syntaxcheck.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 "syntaxcheck.h"
    2 #include "latexdocument.h"
    3 #include "latexeditorview_config.h"
    4 #include "spellerutility.h"
    5 #include "tablemanipulation.h"
    6 #include "latexparser/latexparsing.h"
    7 
    8 /*! \class SyntaxCheck
    9 *
   10 * asynchrnous thread which checks latex syntax of the text lines
   11 * It gets the linehandle via a queue, together with a ticket number.
   12 * The ticket number is increased with every change of the text of a line, thus it can be determined of the processed handle is still unchanged and can be discarded otherwise.
   13 * Syntaxinformation are stated via markers on the text.
   14 * Furthermore environment information, especially tabular information are stored in "cookies" as they are needed in subsequent lines.
   15 *
   16 */
   17 
   18 /*!
   19 * \brief contructor
   20 * \param parent
   21 */
   22 SyntaxCheck::SyntaxCheck(QObject *parent) :
   23     SafeThread(parent), mSyntaxChecking(true), syntaxErrorFormat(-1), ltxCommands(nullptr), newLtxCommandsAvailable(false), speller(nullptr), newSpeller(nullptr)
   24 {
   25     mLinesLock.lock();
   26     stopped = false;
   27     mLines.clear();
   28     mLinesEnqueuedCounter.fetchAndStoreOrdered(0);
   29     mLinesLock.unlock();
   30 }
   31 
   32 /*!
   33 * \brief set the errorformat for syntax errors
   34 * \param errFormat
   35 */
   36 void SyntaxCheck::setErrFormat(int errFormat)
   37 {
   38     syntaxErrorFormat = errFormat;
   39 }
   40 
   41 /*!
   42 * \brief add line to queue
   43 * \param dlh linehandle
   44 * \param previous linehandle of previous line
   45 * \param stack tokenstack at line start (for handling open arguments of previous commands)
   46 * \param clearOverlay clear syntaxcheck overlay
   47 */
   48 void SyntaxCheck::putLine(QDocumentLineHandle *dlh, StackEnvironment previous, TokenStack stack, bool clearOverlay, int hint)
   49 {
   50     REQUIRE(dlh);
   51     SyntaxLine newLine;
   52     dlh->ref(); // impede deletion of handle while in syntax check queue
   53     dlh->lockForRead();
   54     newLine.ticket = dlh->getCurrentTicket();
   55     dlh->unlock();
   56     newLine.stack = stack;
   57     newLine.dlh = dlh;
   58     newLine.prevEnv = previous;
   59     newLine.clearOverlay = clearOverlay;
   60     newLine.hint=hint;
   61     mLinesLock.lock();
   62     mLines.enqueue(newLine);
   63     mLinesEnqueuedCounter.ref();
   64     mLinesLock.unlock();
   65     //avoid reading of any results before this execution is stopped
   66     //mResultLock.lock(); not possible under windows
   67     mLinesAvailable.release();
   68 }
   69 
   70 /*!
   71 * \brief stop processing syntax checks
   72 */
   73 void SyntaxCheck::stop()
   74 {
   75     stopped = true;
   76     mLinesAvailable.release();
   77 }
   78 
   79 /*!
   80 * \brief actual thread loop
   81 */
   82 void SyntaxCheck::run()
   83 {
   84     ltxCommands = new LatexParser();
   85 
   86     forever {
   87         //wait for enqueued lines
   88         mLinesAvailable.acquire();
   89         if (stopped) break;
   90 
   91         if (newLtxCommandsAvailable) {
   92             mLtxCommandLock.lock();
   93             if (newLtxCommandsAvailable) {
   94                 newLtxCommandsAvailable = false;
   95                 *ltxCommands = newLtxCommands;
   96                 speller=newSpeller;
   97                 mReplacementList=newReplacementList;
   98                 mFormatList=newFormatList;
   99             }
  100             mLtxCommandLock.unlock();
  101         }
  102 
  103         // get Linedata
  104         mLinesLock.lock();
  105         SyntaxLine newLine = mLines.dequeue();
  106         mLinesLock.unlock();
  107         // do syntax check
  108         newLine.dlh->lockForRead();
  109         QString line = newLine.dlh->text();
  110         if (newLine.dlh->hasCookie(QDocumentLine::UNCLOSED_ENVIRONMENT_COOKIE)) {
  111             newLine.dlh->unlock();
  112             newLine.dlh->lockForWrite();
  113             newLine.dlh->removeCookie(QDocumentLine::UNCLOSED_ENVIRONMENT_COOKIE); //remove possible errors from unclosed envs
  114         }
  115         TokenList tl = newLine.dlh->getCookie(QDocumentLine::LEXER_COOKIE).value<TokenList>();
  116         QPair<int,int> commentStart = newLine.dlh->getCookie(QDocumentLine::LEXER_COMMENTSTART_COOKIE).value<QPair<int,int> >();
  117         newLine.dlh->unlock();
  118 
  119         StackEnvironment activeEnv = newLine.prevEnv;
  120         Ranges newRanges;
  121 
  122         checkLine(line, newRanges, activeEnv, newLine.dlh, tl, newLine.stack, newLine.ticket,commentStart.first);
  123         // place results
  124         if (newLine.clearOverlay){
  125             QList<int> fmtList={syntaxErrorFormat,SpellerUtility::spellcheckErrorFormat};
  126             fmtList.append(mFormatList.values());
  127             newLine.dlh->clearOverlays(fmtList);
  128         }
  129         //if(newRanges.isEmpty()) continue;
  130         newLine.dlh->lockForWrite();
  131         if (newLine.ticket == newLine.dlh->getCurrentTicket()) { // discard results if text has been changed meanwhile
  132             newLine.dlh->setCookie(QDocumentLine::LEXER_COOKIE,QVariant::fromValue<TokenList>(tl));
  133             foreach (const Error &elem, newRanges){
  134                 if(!mSyntaxChecking && (elem.type!=ERR_spelling) && (elem.type!=ERR_highlight) ){
  135                     // skip all syntax errors
  136                     continue;
  137                 }
  138                 int fmt= elem.type == ERR_spelling ? SpellerUtility::spellcheckErrorFormat : syntaxErrorFormat;
  139                 fmt= elem.type == ERR_highlight ? elem.format : fmt;
  140                 newLine.dlh->addOverlayNoLock(QFormatRange(elem.range.first, elem.range.second, fmt));
  141             }
  142             // active envs
  143             QVariant oldEnvVar = newLine.dlh->getCookie(QDocumentLine::STACK_ENVIRONMENT_COOKIE);
  144             StackEnvironment oldEnv;
  145             if (oldEnvVar.isValid())
  146                 oldEnv = oldEnvVar.value<StackEnvironment>();
  147             bool cookieChanged = !equalEnvStack(oldEnv, activeEnv);
  148             //if excessCols has changed the subsequent lines need to be rechecked.
  149             // don't on initial check
  150             if (cookieChanged) {
  151                 QVariant env;
  152                 env.setValue(activeEnv);
  153                 newLine.dlh->setCookie(QDocumentLine::STACK_ENVIRONMENT_COOKIE, env);
  154                 newLine.dlh->ref(); // avoid being deleted while in queue
  155                 //qDebug() << newLine.dlh->text() << ":" << activeEnv.size();
  156                 emit checkNextLine(newLine.dlh, true, newLine.ticket, newLine.hint);
  157             }
  158         }
  159         newLine.dlh->unlock();
  160 
  161         newLine.dlh->deref(); //if deleted, delete now
  162     }
  163 
  164     delete ltxCommands;
  165     ltxCommands = nullptr;
  166 }
  167 
  168 /*!
  169 * \brief get error description for syntax error in line 'dlh' at column 'pos'
  170 * \param dlh linehandle
  171 * \param pos column
  172 * \param previous environment stack at start of line
  173 * \param stack tokenstack at start of line
  174 * \return error description
  175 */
  176 QString SyntaxCheck::getErrorAt(QDocumentLineHandle *dlh, int pos, StackEnvironment previous, TokenStack stack)
  177 {
  178     // do syntax check
  179     QString line = dlh->text();
  180     QStack<Environment> activeEnv = previous;
  181     TokenList tl = dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>();
  182     QPair<int,int> commentStart = dlh->getCookieLocked(QDocumentLine::LEXER_COMMENTSTART_COOKIE).value<QPair<int,int> >();
  183     Ranges newRanges;
  184     checkLine(line, newRanges, activeEnv, dlh, tl, stack, dlh->getCurrentTicket(),commentStart.first);
  185     // add Error for unclosed env
  186     QVariant var = dlh->getCookieLocked(QDocumentLine::UNCLOSED_ENVIRONMENT_COOKIE);
  187     if (var.isValid()) {
  188         activeEnv = var.value<StackEnvironment>();
  189         Q_ASSERT_X(activeEnv.size() == 1, "SyntaxCheck", "Cookie error");
  190         Environment env = activeEnv.top();
  191         QString cmd = "\\begin{" + env.name + "}";
  192         int index = line.lastIndexOf(cmd);
  193         if (index >= 0) {
  194             Error elem;
  195             elem.range = QPair<int, int>(index, cmd.length());
  196             elem.type = ERR_EnvNotClosed;
  197             newRanges.append(elem);
  198         }
  199     }
  200     // find Error at Position
  201     ErrorType result = ERR_none;
  202     foreach (const Error &elem, newRanges) {
  203         if (elem.range.second + elem.range.first < pos) continue;
  204         if (elem.range.first > pos) break;
  205         result = elem.type;
  206     }
  207     // now generate Error message
  208 
  209     QStringList messages;  // indices have to match ErrorType
  210     messages << tr("no error")
  211             << tr("unrecognized environment")
  212             << tr("unrecognized command")
  213             << tr("unrecognized math command")
  214             << tr("unrecognized tabular command")
  215             << tr("tabular command outside tabular env")
  216             << tr("math command outside math env")
  217             << tr("tabbing command outside tabbing env")
  218             << tr("more cols in tabular than specified")
  219             << tr("cols in tabular missing")
  220             << tr("\\\\ missing")
  221             << tr("closing environment which has not been opened")
  222             << tr("environment not closed")
  223             << tr("unrecognized key in key option")
  224             << tr("unrecognized value in key option")
  225             << tr("command outside suitable env")
  226             << tr("spelling")
  227             << "highlight"; // mock message for arbitrary highlight. Will not be shown.
  228     Q_ASSERT(messages.length() == ERR_MAX);
  229     return messages.value(int(result), tr("unknown"));
  230 }
  231 
  232 /*!
  233 * \brief set latex commands which are referenced for syntax checking
  234 * \param cmds
  235 */
  236 void SyntaxCheck::setLtxCommands(const LatexParser &cmds)
  237 {
  238     if (stopped) return;
  239     mLtxCommandLock.lock();
  240     newLtxCommandsAvailable = true;
  241     newLtxCommands = cmds;
  242     mLtxCommandLock.unlock();
  243 }
  244 
  245 /*!
  246 * \brief set new spellchecker engine (language)
  247 * \param su new spell checker
  248 */
  249 void SyntaxCheck::setSpeller(SpellerUtility *su)
  250 {
  251     if (stopped) return;
  252     mLtxCommandLock.lock();
  253     newLtxCommandsAvailable = true;
  254     newSpeller=su;
  255     mLtxCommandLock.unlock();
  256 }
  257 /*!
  258  * \brief enable showing of Syntax errors
  259  * Since the syntax checker is also used for asynchronous syntax highligting/spell checking, it will not be disabled any more. Only syntax error will not be shown any more.
  260  * \param enable
  261  */
  262 void SyntaxCheck::enableSyntaxCheck(const bool enable){
  263     if (stopped) return;
  264     mSyntaxChecking=enable;
  265 }
  266 /*!
  267  * \brief set character/text replacementList for spell checking
  268  * \param replacementList Map for characater/text replacement prior to spellchecking words. E.g. "u -> ΓΌ when german is activated
  269  */
  270 void SyntaxCheck::setReplacementList(QMap<QString, QString> replacementList)
  271 {
  272     if (stopped) return;
  273     mLtxCommandLock.lock();
  274     newLtxCommandsAvailable = true;
  275     newReplacementList=replacementList;
  276     mLtxCommandLock.unlock();
  277 }
  278 
  279 void SyntaxCheck::setFormats(QMap<QString, int> formatList)
  280 {
  281     if (stopped) return;
  282     mLtxCommandLock.lock();
  283     newLtxCommandsAvailable = true;
  284     newFormatList=formatList;
  285     mLtxCommandLock.unlock();
  286 }
  287 
  288 #ifndef NO_TESTS
  289 
  290 /*!
  291 * \brief Wait for syntax checker to finish processing.
  292 * \details Wait for syntax checker to finish processing. This method should be used only in self-tests because
  293 * in some rare cases it could return too early before the syntax checker queue is fully processsed.
  294 */
  295 void SyntaxCheck::waitForQueueProcess(void)
  296 {
  297     int linesBefore, linesAfter;
  298 
  299     /*
  300      * The logic in the following loop is not perfect because it could terminate the loop too early if it takes more
  301      * than 10ms between the call to mLinesAvailable.acquire() and the following call to mLinesAvailable.release().
  302      * Implementing the check properly requires bi-directional communication with the worker thread with commands to
  303      * pause/unpause the worker thread which complicates the code too much just to handle testing.
  304      */
  305     linesBefore = mLinesEnqueuedCounter.fetchAndAddOrdered(0);
  306     forever {
  307         for (int i = 0; i < 2; ++i) {
  308             QCoreApplication::processEvents(QEventLoop::AllEvents, 1000);           // Process queued checkNextLine events
  309             QCoreApplication::sendPostedEvents(Q_NULLPTR, QEvent::DeferredDelete);      // Deferred delete must be processed explicitly. Using 0 for event_type does not work.
  310             wait(5); // Give the checkNextLine signal handler time to queue the next line
  311         }
  312         linesAfter = mLinesEnqueuedCounter.fetchAndAddOrdered(0);
  313         if ((linesBefore == linesAfter) && !mLinesAvailable.available()) {
  314             break;
  315         }
  316         linesBefore = linesAfter;
  317     }
  318 }
  319 
  320 #endif
  321 
  322 /*!
  323 * \brief check if top-most environment in 'envs' is `name`
  324 * \param name environment name which is checked
  325 * \param envs stack of environments
  326 * \param id check for `id` of the environment, <0 means check is disabled
  327 * \return environment id or 0
  328 */
  329 int SyntaxCheck::topEnv(const QString &name, const StackEnvironment &envs, const int id)
  330 {
  331     if (envs.isEmpty())
  332         return 0;
  333 
  334     Environment env = envs.top();
  335     if (env.name == name) {
  336         if (id < 0 || env.id == id)
  337             return env.id;
  338     }
  339     if (id < 0 && ltxCommands->environmentAliases.contains(env.name)) {
  340         QStringList altEnvs = ltxCommands->environmentAliases.values(env.name);
  341         foreach (const QString &altEnv, altEnvs) {
  342             if (altEnv == name)
  343                 return env.id;
  344         }
  345     }
  346     return 0;
  347 }
  348 
  349 /*!
  350 * \brief check if the environment stack contains a environment with name `name`
  351 * \param parser reference to LatexParser. It is used to access environment aliases, e.g. equation is also a math environment
  352 * \param name name of the checked environment
  353 * \param envs stack of environements
  354 * \param id if >=0 check if the env has the given id.
  355 * \return environment id of  found env otherwise 0
  356 */
  357 int SyntaxCheck::containsEnv(const LatexParser &parser, const QString &name, const StackEnvironment &envs, const int id)
  358 {
  359     for (int i = envs.size() - 1; i > -1; --i) {
  360         Environment env = envs.at(i);
  361         if (env.name == name) {
  362             if (id < 0 || env.id == id)
  363                 return env.id;
  364         }
  365         if (id < 0 && parser.environmentAliases.contains(env.name)) {
  366             QStringList altEnvs = parser.environmentAliases.values(env.name);
  367             foreach (const QString &altEnv, altEnvs) {
  368                 if (altEnv == name)
  369                     return env.id;
  370             }
  371         }
  372     }
  373     return 0;
  374 }
  375 
  376 /*!
  377 * \brief check if the command is valid in the environment stack
  378 * \param cmd name of command
  379 * \param envs environment stack
  380 * \return is valid
  381 */
  382 bool SyntaxCheck::checkCommand(const QString &cmd, const StackEnvironment &envs)
  383 {
  384     for (int i = 0; i < envs.size(); ++i) {
  385         Environment env = envs.at(i);
  386         if (ltxCommands->possibleCommands.contains(env.name) && ltxCommands->possibleCommands.value(env.name).contains(cmd))
  387             return true;
  388         if (ltxCommands->environmentAliases.contains(env.name)) {
  389             QStringList altEnvs = ltxCommands->environmentAliases.values(env.name);
  390             foreach (const QString &altEnv, altEnvs) {
  391                 if (ltxCommands->possibleCommands.contains(altEnv) && ltxCommands->possibleCommands.value(altEnv).contains(cmd))
  392                     return true;
  393             }
  394         }
  395     }
  396     return false;
  397 }
  398 
  399 /*!
  400 * \brief compare two environment stacks
  401 * \param env1
  402 * \param env2
  403 * \return are equal
  404 */
  405 bool SyntaxCheck::equalEnvStack(StackEnvironment env1, StackEnvironment env2)
  406 {
  407     if (env1.isEmpty() || env2.isEmpty())
  408         return env1.isEmpty() && env2.isEmpty();
  409     if (env1.size() != env2.size())
  410         return false;
  411     for (int i = 0; i < env1.size(); i++) {
  412         if (env1.value(i) != env2.value(i))
  413             return false;
  414     }
  415     return true;
  416 }
  417 
  418 /*!
  419 * \brief mark environment start
  420 *
  421 * This function is used to mark unclosed environment,i.e. environments which are unclosed at the end of the text
  422 * \param env used environment
  423 */
  424 void SyntaxCheck::markUnclosedEnv(Environment env)
  425 {
  426     QDocumentLineHandle *dlh = env.dlh;
  427     if (!dlh)
  428         return;
  429     dlh->lockForWrite();
  430     if (dlh->getCurrentTicket() == env.ticket) {
  431         QString line = dlh->text();
  432         line = ltxCommands->cutComment(line);
  433         QString cmd = "\\begin{" + env.name + "}";
  434         int index = line.lastIndexOf(cmd);
  435         if (index >= 0) {
  436             Error elem;
  437             elem.range = QPair<int, int>(index, cmd.length());
  438             elem.type = ERR_EnvNotClosed;
  439             int fmt= elem.type == ERR_spelling ? SpellerUtility::spellcheckErrorFormat : syntaxErrorFormat;
  440             fmt= elem.type == ERR_highlight ? elem.format : fmt;
  441             dlh->addOverlayNoLock(QFormatRange(elem.range.first, elem.range.second, fmt));
  442             QVariant var_env;
  443             StackEnvironment activeEnv;
  444             activeEnv.append(env);
  445             var_env.setValue(activeEnv);
  446             dlh->setCookie(QDocumentLine::UNCLOSED_ENVIRONMENT_COOKIE, var_env); //ERR_EnvNotClosed;
  447         }
  448     }
  449     dlh->unlock();
  450 }
  451 
  452 /*!
  453 * \brief check if the tokenstack contains a definition-token
  454 * \param stack tokenstack
  455 * \return contains a definition
  456 */
  457 bool SyntaxCheck::stackContainsDefinition(const TokenStack &stack) const
  458 {
  459     for (int i = 0; i < stack.size(); i++) {
  460         if (stack[i].subtype == Token::definition)
  461             return true;
  462     }
  463     return false;
  464 }
  465 
  466 /*!
  467 * \brief check one line
  468 *
  469 * Checks one line. Context information needs to be given by newRanges,activeEnv,dlh and ticket.
  470 * This method is obsolete as the new system relies on tokens.
  471 * \param line text of line as string
  472 * \param newRanges will return the result as ranges
  473 * \param activeEnv environment context
  474 * \param dlh linehandle
  475 * \param tl tokenlist of line
  476 * \param stack token stack at start of line
  477 * \param ticket ticket number for current processed line
  478 */
  479 void SyntaxCheck::checkLine(const QString &line, Ranges &newRanges, StackEnvironment &activeEnv, QDocumentLineHandle *dlh, TokenList &tl, TokenStack stack, int ticket,int commentStart)
  480 {
  481     // do syntax check on that line
  482     int cols = containsEnv(*ltxCommands, "tabular", activeEnv);
  483 
  484     // special treatment for empty lines with $/$$ math environmens
  485     // latex treats them as error, so do we
  486     if(tl.length()==0 && line.simplified().isEmpty() && !activeEnv.isEmpty() && activeEnv.top().name=="math"){
  487         if(activeEnv.top().origName=="$" || activeEnv.top().origName=="$$"){
  488             Environment env=activeEnv.pop();
  489             /* how to present an error without character present ?
  490             Error elem;
  491             elem.type = ERR_highlight;
  492             elem.format=mFormatList["math"];
  493             elem.range = QPair<int, int>(0, 0);
  494             newRanges.prepend(elem);
  495             */
  496         }
  497     }
  498 
  499     // check command-words
  500     for (int i = 0; i < tl.length(); i++) {
  501         Token &tk = tl[i];
  502         // ignore commands in definition arguments e.g. \newcommand{cmd}{definition}
  503         if (stackContainsDefinition(stack)) {
  504             Token top = stack.top();
  505             if (top.dlh != tk.dlh) {
  506                 if (tk.type == Token::closeBrace) {
  507                     stack.pop();
  508                 } else
  509                     continue;
  510             } else {
  511                 if (tk.start < top.start + top.length)
  512                     continue;
  513                 else
  514                     stack.pop();
  515             }
  516         }
  517         if (tk.subtype == Token::definition ) { // don't check command definitions
  518             if(tk.type == Token::braces || tk.type == Token::openBrace){
  519                 stack.push(tk);
  520             }
  521             continue;
  522         }
  523         if (tk.type == Token::verbatim ) { // don't check command definitions
  524             // highlight
  525             Error elem;
  526             elem.range = QPair<int, int>(tk.start, tk.length);
  527             elem.type = ERR_highlight;
  528             elem.format=mFormatList["verbatim"];
  529             newRanges.append(elem);
  530             continue;
  531         }
  532         if (tk.type == Token::punctuation || tk.type == Token::symbol) {
  533             QString word = line.mid(tk.start, tk.length);
  534             QStringList forbiddenSymbols;
  535             forbiddenSymbols<<"^"<<"_";
  536             if(forbiddenSymbols.contains(word) && !containsEnv(*ltxCommands, "math", activeEnv) && tk.subtype!=Token::formula){
  537                 Error elem;
  538                 elem.range = QPair<int, int>(tk.start, tk.length);
  539                 elem.type = ERR_MathCommandOutsideMath;
  540                 newRanges.append(elem);
  541             }
  542         }
  543         // math highlighting of formula
  544         if(tk.subtype==Token::formula){
  545             // highlight
  546             Error elem;
  547             elem.range = QPair<int, int>(tk.start, tk.length);
  548             elem.type = ERR_highlight;
  549             if(tk.type==Token::command){
  550                 elem.format=mFormatList["#math"];
  551             }else{
  552                 elem.format=mFormatList["math"];
  553             }
  554             newRanges.append(elem);
  555         }
  556         // spell checking
  557         if (speller->inlineSpellChecking && tk.type == Token::word && (tk.subtype == Token::text || tk.subtype == Token::title || tk.subtype == Token::shorttitle || tk.subtype == Token::todo || tk.subtype == Token::none)  && tk.length >= 3 && speller) {
  558             int tkLength=tk.length;
  559             QString word = tk.getText();
  560             if(i+1 < tl.length()){
  561                 //check if next token is . or -
  562                 Token tk1 = tl.at(i+1);
  563                 if(tk1.type==Token::punctuation && tk1.start==(tk.start+tk.length) && !word.endsWith("\"")){
  564                     QString add=tk1.getText();
  565                     if(add=="."||add=="-"){
  566                         word+=add;
  567                         i++;
  568                         tkLength+=tk1.length;
  569                     }
  570                     if(add=="'"){
  571                         if(i+2 < tl.length()){
  572                             Token tk2 = tl.at(i+2);
  573                             if(tk2.type==Token::word && tk2.start==(tk1.start+tk1.length)){
  574                                 add+=tk2.getText();
  575                                 word+=add;
  576                                 i+=2;
  577                                 tkLength+=tk1.length+tk2.length;
  578                             }
  579                         }
  580                     }
  581                 }
  582             }
  583             word = latexToPlainWordwithReplacementList(word, mReplacementList); //remove special chars
  584             if (speller->hideNonTextSpellingErrors && (containsEnv(*ltxCommands, "math", activeEnv)||containsEnv(*ltxCommands, "picture", activeEnv))){
  585                 word.clear();
  586                 tk.ignoreSpelling=true;
  587             }else{
  588                 tk.ignoreSpelling=false;
  589             }
  590             if (!word.isEmpty() && !speller->check(word) ) {
  591                 if (word.endsWith('-') && speller->check(word.left(word.length() - 1)))
  592                     continue; // word ended with '-', without that letter, word is correct (e.g. set-up / german hypehantion)
  593                 if(word.endsWith('.')){
  594                     tkLength--; // don't take point into misspelled word
  595                 }
  596                 Error elem;
  597                 elem.range = QPair<int, int>(tk.start, tk.length);
  598                 elem.type = ERR_spelling;
  599                 newRanges.append(elem);
  600             }
  601         }
  602         if (tk.type == Token::commandUnknown) {
  603             QString word = line.mid(tk.start, tk.length);
  604             if (word.contains('@')) {
  605                 continue; //ignore commands containg @
  606             }
  607             if (ltxCommands->mathStartCommands.contains(word) && (activeEnv.isEmpty() || activeEnv.top().name != "math")) {
  608                 Environment env;
  609                 env.name = "math";
  610                 env.origName=word;
  611                 env.id = 1; // to be changed
  612                 env.dlh = dlh;
  613                 env.ticket = ticket;
  614                 env.level = tk.level;
  615                 env.startingColumn=tk.start+tk.length;
  616                 activeEnv.push(env);
  617                 // highlight delimiter
  618                 Error elem;
  619                 elem.type = ERR_highlight;
  620                 elem.format=mFormatList["&math"];
  621                 elem.range = QPair<int, int>(tk.start, tk.length);
  622                 newRanges.append(elem);
  623                 continue;
  624             }
  625             if (ltxCommands->mathStopCommands.contains(word) && !activeEnv.isEmpty() && activeEnv.top().name == "math") {
  626                 int i=ltxCommands->mathStopCommands.indexOf(word);
  627                 QString txt=ltxCommands->mathStartCommands.value(i);
  628                 if(activeEnv.top().origName==txt){
  629                     Environment env=activeEnv.pop();
  630                     Error elem;
  631                     elem.type = ERR_highlight;
  632                     elem.format=mFormatList["math"];
  633                     if(dlh == env.dlh){
  634                         //inside line
  635                         elem.range = QPair<int, int>(env.startingColumn, tk.start-env.startingColumn);
  636                     }else{
  637                         elem.range = QPair<int, int>(0, tk.start);
  638                     }
  639                     newRanges.prepend(elem);
  640                     // highlight delimiter
  641                     elem.type = ERR_highlight;
  642                     elem.format=mFormatList["&math"];
  643                     elem.range = QPair<int, int>(tk.start, tk.length);
  644                     newRanges.append(elem);
  645                 }// ignore mismatching mathstop commands
  646                 continue;
  647             }
  648             if (word == "\\\\" && topEnv("tabular", activeEnv) != 0 && tk.level == activeEnv.top().level) {
  649                 if (activeEnv.top().excessCol < (activeEnv.top().id - 1)) {
  650                     Error elem;
  651                     elem.range = QPair<int, int>(tk.start, tk.length);
  652                     elem.type = ERR_tooLittleCols;
  653                     newRanges.append(elem);
  654                 }
  655                 if (activeEnv.top().excessCol >= (activeEnv.top().id)) {
  656                     Error elem;
  657                     elem.range = QPair<int, int>(tk.start, tk.length);
  658                     elem.type = ERR_tooManyCols;
  659                     newRanges.append(elem);
  660                 }
  661                 activeEnv.top().excessCol = 0;
  662                 continue;
  663             }
  664             // command highlighing
  665             // this looks slow
  666             // TODO: optimize !
  667             for(const Environment &env:activeEnv){
  668                 if(!env.dlh)
  669                     continue; //ignore "normal" env
  670                 if(env.name=="document")
  671                     continue; //ignore "document" env
  672                 for(const QString &key: mFormatList.keys()){
  673                     if(key.at(0)=='#'){
  674                         QStringList altEnvs = ltxCommands->environmentAliases.values(env.name);
  675                         altEnvs<<env.name;
  676                         if(altEnvs.contains(key.mid(1))){
  677                             Error elem;
  678                             elem.range = QPair<int, int>(tk.start, tk.length);
  679                             elem.type = ERR_highlight;
  680                             elem.format=mFormatList.value(key);
  681                             newRanges.append(elem);
  682                         }
  683                     }
  684                 }
  685             }
  686             if (ltxCommands->possibleCommands["user"].contains(word) || ltxCommands->customCommands.contains(word))
  687                 continue;
  688             if (!checkCommand(word, activeEnv)) {
  689                 Error elem;
  690                 elem.range = QPair<int, int>(tk.start, tk.length);
  691                 elem.type = ERR_unrecognizedCommand;
  692                 newRanges.append(elem);
  693                 continue;
  694             }
  695         }
  696         if (tk.type == Token::env) {
  697             QString env = line.mid(tk.start, tk.length);
  698             // corresponds \end{env}
  699             if (!activeEnv.isEmpty()) {
  700                 Environment tp = activeEnv.top();
  701                 if (tp.name == env) {
  702                     Environment closingEnv=activeEnv.pop();
  703                     if (tp.name == "tabular" || ltxCommands->environmentAliases.values(tp.name).contains("tabular")) {
  704                         // correct length of col error if it exists
  705                         if (!newRanges.isEmpty()) {
  706                             Error &elem = newRanges.last();
  707                             if (elem.type == ERR_tooManyCols && elem.range.first + elem.range.second > tk.start) {
  708                                 elem.range.second = tk.start - elem.range.first;
  709                             }
  710                         }
  711                         // get new cols
  712                         cols = containsEnv(*ltxCommands, "tabular", activeEnv);
  713                     }
  714                     // handle higlighting
  715                     QStringList altEnvs = ltxCommands->environmentAliases.values(env);
  716                     altEnvs<<env;
  717                     for(const QString &key: mFormatList.keys()){
  718                         if(altEnvs.contains(key)){
  719                             Error elem;
  720                             int start= closingEnv.dlh==dlh ? closingEnv.startingColumn : 0;
  721                             int end=tk.start-1;
  722                             if(i>1){
  723                                 Token tk=tl.at(i-2);
  724                                 if(tk.type==Token::command && line.mid(tk.start, tk.length)=="\\end"){
  725                                     end=tk.start;
  726                                 }
  727                             }
  728                             // trick to avoid coloring of end
  729                             if(!newRanges.isEmpty() && newRanges.last().type==ERR_highlight){
  730                                 if(i>1){
  731                                     Token tk=tl.at(i-2); // skip over brace
  732                                     if(tk.type==Token::command && line.mid(tk.start,tk.length)=="\\end"){
  733                                         //previous token is end
  734                                         // see whether it was colored with *-keyword i.e. #math or #picture
  735                                         if(newRanges.last().range==QPair<int,int>(tk.start,tk.length)){
  736                                             // yes, remove !
  737                                             newRanges.removeLast();
  738                                         }
  739                                     }
  740                                 }
  741                             }
  742                             elem.range = QPair<int, int>(start, end);
  743                             elem.type = ERR_highlight;
  744                             elem.format=mFormatList.value(key);
  745                             newRanges.append(elem);
  746                         }
  747                     }
  748                 } else {
  749                     Error elem;
  750                     elem.range = QPair<int, int>(tk.start, tk.length);
  751                     elem.type = ERR_closingUnopendEnv;
  752                     newRanges.append(elem);
  753                 }
  754             } else {
  755                 Error elem;
  756                 elem.range = QPair<int, int>(tk.start, tk.length);
  757                 elem.type = ERR_closingUnopendEnv;
  758                 newRanges.append(elem);
  759             }
  760         }
  761 
  762         if (tk.type == Token::beginEnv) {
  763             QString env = line.mid(tk.start, tk.length);
  764             // corresponds \begin{env}
  765             Environment tp;
  766             tp.name = env;
  767             tp.id = 1; //needs correction
  768             tp.excessCol = 0;
  769             tp.dlh = dlh;
  770             tp.startingColumn=tk.start+tk.length+1; // after closing brace
  771             tp.ticket = ticket;
  772             tp.level = tk.level-1; // tk is the argument, not the command, hence -1
  773             if (env == "tabular" || ltxCommands->environmentAliases.values(env).contains("tabular")) {
  774                 // tabular env opened
  775                 // get cols !!!!
  776                 QString option;
  777                 if ((env == "tabu") || (env == "longtabu")) { // special treatment as the env is rather not latex standard
  778                     for (int k = i + 1; k < tl.length(); k++) {
  779                         Token elem = tl.at(k);
  780                         if (elem.level < tk.level-1)
  781                             break;
  782                         if (elem.level > tk.level)
  783                             continue;
  784                         if (elem.type == Token::braces) { // take the first mandatory argument at the correct level -> TODO: put colDef also for tabu correctly in lexer
  785                             option = line.mid(elem.start + 1, elem.length - 2); // strip {}
  786                             break; // first argument only !
  787                         }
  788                     }
  789                 } else {
  790                     if(env=="tikztimingtable"){
  791                         option="ll"; // is always 2 columns
  792                     }else{
  793                         for (int k = i + 1; k < tl.length(); k++) {
  794                             Token elem = tl.at(k);
  795                             if (elem.level < tk.level)
  796                                 break;
  797                             if (elem.level > tk.level)
  798                                 continue;
  799                             if (elem.subtype == Token::colDef) {
  800                                 option = line.mid(elem.start + 1, elem.length - 2); // strip {}
  801                                 break;
  802                             }
  803                         }
  804                     }
  805                 }
  806                 QSet<QString> translationMap=ltxCommands->possibleCommands.value("%columntypes");
  807                 QStringList res = LatexTables::splitColDef(option);
  808                 QStringList res2;
  809                 for(const auto &elem: res){
  810                     bool add=true;
  811                     for(const auto &i:translationMap){
  812                         if(i.left(1)==elem && add){
  813                             res2 << LatexTables::splitColDef(i.mid(1));
  814                             add=false;
  815                         }
  816                     }
  817                     if(add){
  818                         res2<<elem;
  819                     }
  820                 }
  821                 cols = res2.count();
  822                 tp.id = cols;
  823             }
  824             activeEnv.push(tp);
  825         }
  826 
  827 
  828         if (tk.type == Token::command) {
  829             QString word = line.mid(tk.start, tk.length);
  830             if(!tk.optionalCommandName.isEmpty()){
  831                 word=tk.optionalCommandName;
  832             }
  833             Token tkEnvName;
  834 
  835             if (word == "\\begin" || word == "\\end") {
  836                 // check complete expression e.g. \begin{something}
  837                 if (tl.length() > i + 1 && tl.at(i + 1).type == Token::braces) {
  838                     tkEnvName = tl.at(i + 1);
  839                     word = word + line.mid(tkEnvName.start, tkEnvName.length);
  840                 }
  841             }
  842             // special treatment for & in math
  843             if(word=="&" && containsEnv(*ltxCommands, "math", activeEnv)){
  844                 Error elem;
  845                 elem.range = QPair<int, int>(tk.start, tk.length);
  846                 elem.type = ERR_highlight;
  847                 elem.format=mFormatList.value("align-ampersand");
  848                 newRanges.append(elem);
  849                 continue;
  850             }
  851 
  852             if (ltxCommands->mathStartCommands.contains(word) && (activeEnv.isEmpty() || activeEnv.top().name != "math")) {
  853                 Environment env;
  854                 env.name = "math";
  855                 env.origName=word;
  856                 env.id = 1; // to be changed
  857                 env.dlh = dlh;
  858                 env.ticket = ticket;
  859                 env.level = tk.level;
  860                 env.startingColumn=tk.start+tk.length;
  861                 activeEnv.push(env);
  862                 // highlight delimiter
  863                 Error elem;
  864                 elem.type = ERR_highlight;
  865                 elem.format=mFormatList["&math"];
  866                 elem.range = QPair<int, int>(tk.start, tk.length);
  867                 newRanges.append(elem);
  868                 continue;
  869             }
  870             if (ltxCommands->mathStopCommands.contains(word) && !activeEnv.isEmpty() && activeEnv.top().name == "math") {
  871                 int i=ltxCommands->mathStopCommands.indexOf(word);
  872                 QString txt=ltxCommands->mathStartCommands.value(i);
  873                 if(activeEnv.top().origName==txt){
  874                     Environment env=activeEnv.pop();
  875                     Error elem;
  876                     elem.type = ERR_highlight;
  877                     elem.format=mFormatList["math"];
  878                     if(dlh == env.dlh){
  879                         //inside line
  880                         elem.range = QPair<int, int>(env.startingColumn, tk.start-env.startingColumn);
  881                     }else{
  882                         elem.range = QPair<int, int>(0, tk.start);
  883                     }
  884                     newRanges.prepend(elem);
  885                     // highlight delimiter
  886                     elem.type = ERR_highlight;
  887                     elem.format=mFormatList["&math"];
  888                     elem.range = QPair<int, int>(tk.start, tk.length);
  889                     newRanges.append(elem);
  890                 }// ignore mismatching mathstop commands
  891                 continue;
  892             }
  893 
  894             //tabular checking
  895             if (topEnv("tabular", activeEnv) != 0) {
  896                 if (word == "&") {
  897                     activeEnv.top().excessCol++;
  898                     if (activeEnv.top().excessCol >= activeEnv.top().id) {
  899                         Error elem;
  900                         elem.range = QPair<int, int>(tk.start, tk.length);
  901                         elem.type = ERR_tooManyCols;
  902                         newRanges.append(elem);
  903                     }
  904                     continue;
  905                 }
  906 
  907                 if ((word == "\\\\") || (word == "\\tabularnewline")) {
  908                     if (activeEnv.top().excessCol < (activeEnv.top().id - 1)) {
  909                         Error elem;
  910                         elem.range = QPair<int, int>(tk.start, tk.length);
  911                         elem.type = ERR_tooLittleCols;
  912                         newRanges.append(elem);
  913                     }
  914                     if (activeEnv.top().excessCol >= (activeEnv.top().id)) {
  915                         Error elem;
  916                         elem.range = QPair<int, int>(tk.start, tk.length);
  917                         elem.type = ERR_tooManyCols;
  918                         newRanges.append(elem);
  919                     }
  920                     activeEnv.top().excessCol = 0;
  921                     continue;
  922                 }
  923                 if (word == "\\multicolumn") {
  924                     QRegExp rxMultiColumn("\\\\multicolumn\\{(\\d+)\\}\\{.+\\}\\{.+\\}");
  925                     rxMultiColumn.setMinimal(true);
  926                     int res = rxMultiColumn.indexIn(line, tk.start);
  927                     if (res > -1) {
  928                         // multicoulmn before &
  929                         bool ok;
  930                         int c = rxMultiColumn.cap(1).toInt(&ok);
  931                         if (ok) {
  932                             activeEnv.top().excessCol += c - 1;
  933                         }
  934                     }
  935                     if (activeEnv.top().excessCol >= activeEnv.top().id) {
  936                         Error elem;
  937                         elem.range = QPair<int, int>(tk.start, tk.length);
  938                         elem.type = ERR_tooManyCols;
  939                         newRanges.append(elem);
  940                     }
  941                     continue;
  942                 }
  943 
  944             }
  945 
  946             // command highlighing
  947             // this looks slow
  948             // TODO: optimize !
  949             for(const Environment &env:activeEnv){
  950                 if(!env.dlh)
  951                     continue; //ignore "normal" env
  952                 if(env.name=="document")
  953                     continue; //ignore "document" env
  954                 for(const QString &key: mFormatList.keys()){
  955                     if(key.at(0)=='#'){
  956                         QStringList altEnvs = ltxCommands->environmentAliases.values(env.name);
  957                         altEnvs<<env.name;
  958                         if(altEnvs.contains(key.mid(1))){
  959                             Error elem;
  960                             elem.range = QPair<int, int>(tk.start, tk.length);
  961                             elem.type = ERR_highlight;
  962                             elem.format=mFormatList.value(key);
  963                             newRanges.append(elem);
  964                         }
  965                     }
  966                 }
  967             }
  968 
  969             if (ltxCommands->possibleCommands["user"].contains(word) || ltxCommands->customCommands.contains(word))
  970                 continue;
  971 
  972             if (!checkCommand(word, activeEnv)) {
  973                 Error elem;
  974                 if (tkEnvName.type == Token::braces) {
  975                     Token tkEnvName = tl.at(i+1);
  976                     elem.range = QPair<int, int>(tkEnvName.innerStart(), tkEnvName.innerLength());
  977                     elem.type = ERR_unrecognizedEnvironment;
  978                 } else {
  979                     elem.range = QPair<int, int>(tk.start, tk.length);
  980                     elem.type = ERR_unrecognizedCommand;
  981                 }
  982 
  983 
  984                 if (ltxCommands->possibleCommands["math"].contains(word))
  985                     elem.type = ERR_MathCommandOutsideMath;
  986                 if (ltxCommands->possibleCommands["tabular"].contains(word))
  987                     elem.type = ERR_TabularCommandOutsideTab;
  988                 if (ltxCommands->possibleCommands["tabbing"].contains(word))
  989                     elem.type = ERR_TabbingCommandOutside;
  990                 if(elem.type== ERR_unrecognizedEnvironment){
  991                     // try to find command in unspecified envs
  992                     QStringList keys=ltxCommands->possibleCommands.keys();
  993                     keys.removeAll("math");
  994                     keys.removeAll("tabular");
  995                     keys.removeAll("tabbing");
  996                     keys.removeAll("normal");
  997                     foreach (QString key, keys) {
  998                         if(key.contains("%"))
  999                             continue;
 1000                         if(ltxCommands->possibleCommands[key].contains(word)){
 1001                             elem.type = ERR_commandOutsideEnv;
 1002                             break;
 1003                         }
 1004                     }
 1005                 }
 1006                 if(elem.type != ERR_MathCommandOutsideMath || tk.subtype!=Token::formula){
 1007                     newRanges.append(elem);
 1008                 }
 1009             }
 1010         }
 1011         if (tk.type == Token::specialArg) {
 1012             QString value = line.mid(tk.start, tk.length);
 1013             QString special = ltxCommands->mapSpecialArgs.value(int(tk.type - Token::specialArg));
 1014             if (!ltxCommands->possibleCommands[special].contains(value)) {
 1015                 Error elem;
 1016                 elem.range = QPair<int, int>(tk.start, tk.length);
 1017                 elem.type = ERR_unrecognizedKey;
 1018                 newRanges.append(elem);
 1019             }
 1020         }
 1021         if (tk.type == Token::keyVal_key) {
 1022             // special treatment for key val checking
 1023             QString command = tk.optionalCommandName;
 1024             QString value = line.mid(tk.start, tk.length);
 1025 
 1026             // search stored keyvals
 1027             QString elem;
 1028             foreach (elem, ltxCommands->possibleCommands.keys()) {
 1029                 if (elem.startsWith("key%") && elem.mid(4) == command)
 1030                     break;
 1031                 if (elem.startsWith("key%") && elem.mid(4, command.length()) == command && elem.mid(4 + command.length(), 1) == "/" && !elem.endsWith("#c")) {
 1032                     // special treatment for distinguishing \command[keyvals]{test} where argument needs to equal test (used in yathesis.cwl)
 1033                     // now find mandatory argument
 1034                     QString subcommand;
 1035                     for (int k = i + 1; k < tl.length(); k++) {
 1036                         Token tk_elem = tl.at(k);
 1037                         if (tk_elem.level > tk.level)
 1038                             continue;
 1039                         if (tk_elem.level < tk.level)
 1040                             break;
 1041                         if (tk_elem.type == Token::braces) {
 1042                             subcommand = line.mid(tk_elem.start + 1, tk_elem.length - 2);
 1043                             if (elem == "key%" + command + "/" + subcommand) {
 1044                                 break;
 1045                             } else {
 1046                                 subcommand.clear();
 1047                             }
 1048                         }
 1049                     }
 1050                     if (!subcommand.isEmpty())
 1051                         elem = "key%" + command + "/" + subcommand;
 1052                     else
 1053                         elem.clear();
 1054                     break;
 1055                 }
 1056                 elem.clear();
 1057             }
 1058             if (!elem.isEmpty()) {
 1059                 QStringList lst = ltxCommands->possibleCommands[elem].values();
 1060                 QStringList::iterator iterator;
 1061                 QStringList toAppend;
 1062                 for (iterator = lst.begin(); iterator != lst.end(); ++iterator) {
 1063                     int i = iterator->indexOf("#");
 1064                     if (i > -1)
 1065                         *iterator = iterator->left(i);
 1066 
 1067                     i = iterator->indexOf("=");
 1068                     if (i > -1) {
 1069                         *iterator = iterator->left(i);
 1070                     }
 1071                     if (iterator->startsWith("%")) {
 1072                         toAppend << ltxCommands->possibleCommands[*iterator].values();
 1073                     }
 1074                 }
 1075                 lst << toAppend;
 1076                 if (!lst.contains(value)) {
 1077                     Error elem;
 1078                     elem.range = QPair<int, int>(tk.start, tk.length);
 1079                     elem.type = ERR_unrecognizedKey;
 1080                     newRanges.append(elem);
 1081                 }
 1082             }
 1083         }
 1084         if (tk.subtype == Token::keyVal_val) {
 1085             //figure out keyval
 1086             QString word = line.mid(tk.start, tk.length);
 1087             // first get command
 1088             Token cmd = Parsing::getCommandTokenFromToken(tl, tk);
 1089             QString command = line.mid(cmd.start, cmd.length);
 1090             // figure out key
 1091             QString key;
 1092             for (int k = i - 1; k >= 0; k--) {
 1093                 Token elem = tl.at(k);
 1094                 if (elem.level == tk.level - 1) {
 1095                     if (elem.type == Token::keyVal_key) {
 1096                         key = line.mid(elem.start, elem.length);
 1097                     }
 1098                     break;
 1099                 }
 1100             }
 1101             // find if values are defined
 1102             QString elem;
 1103             foreach (elem, ltxCommands->possibleCommands.keys()) {
 1104                 if (elem.startsWith("key%") && elem.mid(4) == command)
 1105                     break;
 1106                 if (elem.startsWith("key%") && elem.mid(4, command.length()) == command && elem.mid(4 + command.length(), 1) == "/" && !elem.endsWith("#c")) {
 1107                     // special treatment for distinguishing \command[keyvals]{test} where argument needs to equal test (used in yathesis.cwl)
 1108                     // now find mandatory argument
 1109                     QString subcommand;
 1110                     for (int k = i + 1; k < tl.length(); k++) {
 1111                         Token tk_elem = tl.at(k);
 1112                         if (tk_elem.level > tk.level - 2)
 1113                             continue;
 1114                         if (tk_elem.level < tk.level - 2)
 1115                             break;
 1116                         if (tk_elem.type == Token::braces) {
 1117                             subcommand = line.mid(tk_elem.start + 1, tk_elem.length - 2);
 1118                             if (elem == "key%" + command + "/" + subcommand) {
 1119                                 break;
 1120                             } else {
 1121                                 subcommand.clear();
 1122                             }
 1123                         }
 1124                     }
 1125                     if (!subcommand.isEmpty())
 1126                         elem = "key%" + command + "/" + subcommand;
 1127                     break;
 1128                 }
 1129                 elem.clear();
 1130             }
 1131             if (!elem.isEmpty()) {
 1132                 // check whether keys is valid
 1133                 QStringList lst = ltxCommands->possibleCommands[elem].values();
 1134                 QStringList::iterator iterator;
 1135                 QString options;
 1136                 for (iterator = lst.begin(); iterator != lst.end(); ++iterator) {
 1137                     int i = iterator->indexOf("#");
 1138                     options.clear();
 1139                     if (i > -1) {
 1140                         options = iterator->mid(i + 1);
 1141                         *iterator = iterator->left(i);
 1142                     }
 1143 
 1144                     if (iterator->endsWith("=")) {
 1145                         iterator->chop(1);
 1146                     }
 1147                     if (*iterator == key)
 1148                         break;
 1149                 }
 1150                 if (iterator != lst.end() && !options.isEmpty()) {
 1151                     if(options.startsWith("#")){
 1152                         continue; // ignore type keys, like width#L
 1153                     }
 1154                     if(options.startsWith("%")){
 1155                         if (!ltxCommands->possibleCommands[options].contains(word)) {
 1156                             Error elem;
 1157                             elem.range = QPair<int, int>(tk.start, tk.length);
 1158                             elem.type = ERR_unrecognizedKeyValues;
 1159                             newRanges.append(elem);
 1160                         }
 1161                     }else{
 1162                         QStringList l = options.split(",");
 1163                         if (!l.contains(word)) {
 1164                             Error elem;
 1165                             elem.range = QPair<int, int>(tk.start, tk.length);
 1166                             elem.type = ERR_unrecognizedKeyValues;
 1167                             newRanges.append(elem);
 1168                         }
 1169                     }
 1170                 }
 1171             }
 1172         }
 1173     }
 1174     if(!activeEnv.isEmpty()){
 1175         //check active env for env highlighting (math,verbatim)
 1176         for(const Environment &env: activeEnv){
 1177             QStringList altEnvs = ltxCommands->environmentAliases.values(env.name);
 1178             altEnvs<<env.name;
 1179             for(const QString &key: mFormatList.keys()){
 1180                 if(altEnvs.contains(key)){
 1181                     Error elem;
 1182                     int start= env.dlh==dlh ? env.startingColumn : 0;
 1183                     elem.range = QPair<int, int>(start, commentStart>=0 ? commentStart-start : line.length()-start);
 1184                     elem.type = ERR_highlight;
 1185                     elem.format=mFormatList.value(key);
 1186                     newRanges.prepend(elem);  // draw this first and then other on top (e.g. keyword highlighting) !
 1187                 }
 1188             }
 1189         }
 1190     }
 1191 }