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