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