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