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