1 #include "latexcompleter.h"
2 #include "latexcompleter_p.h"
3 #include "latexcompleter_config.h"
4 #include "help.h"
5 #include "usermacro.h"
6 
7 #include "smallUsefulFunctions.h"
8 
9 #include "qdocumentline.h"
10 #include "qdocument_p.h"
11 #include "qeditorinputbinding.h"
12 #include "qformatfactory.h"
13 #include "qdocumentline_p.h"
14 
15 #include "latexdocument.h"
16 #include "latexeditorview.h"
17 #include "qdocument.h"
18 
19 #include <algorithm>
20 #include <QtConcurrentFilter>
21 #include <QtConcurrentMap>
22 
23 
24 //------------------------------Default Input Binding--------------------------------
25 class CompleterInputBinding: public QEditorInputBinding
26 {
27 public:
CompleterInputBinding()28     CompleterInputBinding(): active(false), showAlways(false), showMostUsed(0), completer(nullptr), editor(nullptr), oldBinding(nullptr), curStart(0), maxWritten(0), curLineNumber(0) {}
id() const29 	virtual QString id() const
30 	{
31 		return "TXS::CompleterInputBinding";
32 	}
name() const33 	virtual QString name() const
34 	{
35 		return "TXS::CompleterInputBinding";
36 	}
37 
mousePressEvent(QMouseEvent * mouse,QEditor * editor)38 	virtual bool mousePressEvent(QMouseEvent *mouse, QEditor *editor)
39 	{
40 		// remove unused argument warnings
41 		Q_UNUSED(mouse)
42 		Q_UNUSED(editor)
43 		simpleRestoreAutoOverride();
44 		resetBinding();
45 		return false;
46 	}
47 
getCurWord()48 	QString getCurWord()
49 	{
50 		if (!editor) return QString();
51 		//QMessageBox::information(0,curLine.text().mid(curStart,editor->cursor().columnNumber()-curStart),"",0);
52 		return editor->text(curLineNumber).mid(curStart, editor->cursor().columnNumber() - curStart);
53 	}
54 
55     /// check if current cursor/placeholder is mirrored
isMirrored()56 	bool isMirrored()
57 	{
58 		if (!editor) return false;
59 		if ( editor->currentPlaceHolder() >= 0 && editor->currentPlaceHolder() < editor->placeHolderCount() ) {
60 			PlaceHolder ph = editor->getPlaceHolder(editor->currentPlaceHolder());
61 			return ph.mirrors.count() > 0;
62 		}
63 		return false;
64 	}
65 
insertText(const QString & text)66 	void insertText(const QString &text)
67 	{
68 		if (!editor) return;
69 		maxWritten += text.length();
70         if ( editor->currentPlaceHolder() >= 0 && editor->currentPlaceHolder() < editor->placeHolderCount() )
71             editor->document()->beginMacro();
72         editor->write(text);
73         //cursor mirrors
74 		if ( editor->currentPlaceHolder() >= 0 && editor->currentPlaceHolder() < editor->placeHolderCount() ) {
75 			PlaceHolder ph = editor->getPlaceHolder(editor->currentPlaceHolder());
76 
77 			QString baseText = ph.cursor.selectedText();
78 
79 			for ( int phm = 0; phm < ph.mirrors.count(); ++phm ) {
80 				//QString s = ph.affector ?  ph.affector->affect(text, baseText, m_curPlaceHolder, phm) : baseText;
81 
82 				ph.mirrors[phm].replaceSelectedText(baseText);
83 			}
84 			editor->document()->endMacro();
85         }
86 		//end cursor mirrors
87 		if (editor->cursor().columnNumber() > curStart + 1 && !completer->isVisible()) {
88 			QString wrd = getCurWord();
89 			completer->filterList(wrd, showMostUsed);
90 			completer->widget->show();
91 			if (showMostUsed == 1 && completer->countWords() == 0) { // if prefered list is empty, take next more extensive one
92                 completer->setTab(0); // typical
93 			}
94 			if (showMostUsed == 0 && completer->countWords() == 0) {
95                 completer->setTab(3); // all
96 			}
97 			completer->adjustWidget();
98 		}
99 	}
100 
insertCompletedWord()101 	bool insertCompletedWord()
102 	{
103 		if (!editor) return false;
104 		if (completer->list->isVisible() && maxWritten >= curStart && completer->list->currentIndex().isValid()) {
105 			QDocumentCursor cursor = editor->cursor();
106 			editor->document()->beginMacro();
107 			QVariant v = completer->list->model()->data(completer->list->currentIndex(), Qt::DisplayRole);
108 			if (!v.isValid() || !v.canConvert<CompletionWord>()) return false;
109 			CompletionWord cw = v.value<CompletionWord>();
110 			completer->listModel->incUsage(completer->list->currentIndex());
111 			//int alreadyWrittenLen=editor->cursor().columnNumber()-curStart;
112 			//remove current text for correct case
113 			if (completer->forcedRef) {
114 				QString line = cursor.line().text();
115 				int col = cursor.columnNumber();
116 				bool missingCloseBracket = (findClosingBracket(line, col) < 0);
117 				QString eow = "}],";
118 				while (!cursor.atLineEnd() && !eow.contains(cursor.nextChar())
119 				        && !(cursor.nextChar().isSpace() && missingCloseBracket)) { // spaces are allowed in labels and should be deleted, however we stop deleting at spaces if the closing bracket is missing. otherwise it deletes the complete line.
120 					cursor.deleteChar();
121 				}
122 				//if(cursor.nextChar()=='}')
123 				//	cursor.deleteChar();
124 			}
125 			if (completer->forcedCite) {
126 				QString line = cursor.line().text();
127 				int col = cursor.columnNumber();
128 				bool missingCloseBracket = (findClosingBracket(line, col) < 0);
129 				QString eow = "},";
130 				while (!cursor.atLineEnd() && !eow.contains(cursor.nextChar())
131 				        && !(cursor.nextChar().isSpace() && missingCloseBracket))
132 					cursor.deleteChar();
133 			}
134 			if (isMirrored() && (cw.lines.first().startsWith("\\begin") || cw.lines.first().startsWith("\\end"))) {
135 				QString text = cw.lines.first();
136 				int i = cursor.columnNumber() - curStart;
137 				text.remove(0, i);
138 				i = text.indexOf('}');
139 				if (i >= 0)
140 					text.remove(i, text.length());
141 				while (!cursor.atLineEnd() && cursor.nextChar() != '}') {
142 					cursor.deleteChar();
143 				}
144 				insertText(text);
145 				editor->document()->endMacro();
146 				return true;
147 			}
148             // check whether cursor is inside math in case of automatic delimter insertion
149             // this is done here as the charcter format is used for detection and here we are sure that at least 1 character was used.
150             QString cwCmd=cw.word;
151             QRegExp rx("\\\\[a-zA-Z]+");
152             int pos=rx.indexIn(cwCmd);
153             if(pos>-1){
154                 cwCmd=rx.cap(0);
155             }
156             bool inMath=false;
157             if(cw.lines.size()==1 && completer->latexParser.possibleCommands["math"].contains(cwCmd)){
158                 LatexEditorView *view = editor->property("latexEditor").value<LatexEditorView *>();
159                 Q_ASSERT(view);
160                 inMath=view->isInMathHighlighting(cursor);
161             }
162 
163             //for (int i = maxWritten - cursor.columnNumber(); i > 0; i--) cursor.deleteChar();
164             if(maxWritten>cursor.columnNumber()){
165                 cursor.movePosition(maxWritten-cursor.columnNumber(),QDocumentCursor::NextCharacter,QDocumentCursor::KeepAnchor);
166                 cursor.removeSelectedText();
167             }
168             //for (int i = cursor.columnNumber() - curStart; i > 0; i--) cursor.deletePreviousChar();
169             if(curStart<cursor.columnNumber()){
170                 cursor.movePosition(cursor.columnNumber()-curStart,QDocumentCursor::PreviousCharacter,QDocumentCursor::KeepAnchor);
171                 cursor.removeSelectedText();
172             }
173 			if (!autoOverridenText.isEmpty()) {
174 				cursor.insertText(autoOverridenText);
175 				cursor.movePosition(autoOverridenText.length(), QDocumentCursor::PreviousCharacter);
176 				editor->resizeAutoOverridenPlaceholder(cursor, autoOverridenText.size());
177 				autoOverridenText = "";
178 			}
179 			//cursor.endEditBlock(); //doesn't work and lead to crash when auto indentation is enabled => TODO:figure out why
180 			//  cursor.setColumnNumber(curStart);
181 			CodeSnippet::PlaceholderMode phMode = (LatexCompleter::config && LatexCompleter::config->usePlaceholders) ? CodeSnippet::PlacehodersActive : CodeSnippet::PlaceholdersRemoved;
182 
183             if(cw.lines.size()==1 && completer->latexParser.possibleCommands["math"].contains(cwCmd)){
184                 if(!inMath && LatexCompleter::config && LatexCompleter::config->autoInsertMathDelimiters){
185                     // add $$ to mathcommand outsiode math env
186                     cw.lines.first().prepend(LatexCompleter::config->startMathDelimiter);
187                     cw.lines.first().append(LatexCompleter::config->stopMathDelimiter);
188                     // move cursors
189                     if(cw.cursorOffset>-1) cw.cursorOffset++;
190                     if(cw.anchorOffset>-1) cw.anchorOffset++;
191                     for(int i=0;i<cw.placeHolders.size();i++){
192                         for(int j=0;j<cw.placeHolders[i].size();j++){
193                             cw.placeHolders[i][j].offset++;
194                         }
195                     }
196                 }
197             }
198 			cw.insertAt(editor, &cursor, phMode, !completer->startedFromTriggerKey, completer->forcedKeyval);
199 			editor->document()->endMacro();
200 
201 			return true;
202 		}
203 		return false;
204 	}
205 
removeRightWordPart()206 	void removeRightWordPart()
207 	{
208 		if (!editor) return;
209 		QDocumentCursor cursor = editor->cursor();
210 		for (int i = maxWritten - cursor.columnNumber(); i > 0; i--) cursor.deleteChar();
211 		maxWritten = cursor.columnNumber();
212 		editor->setCursor(cursor);//necessary to keep the cursor at the same place (but why???)  TODO: remove this line (it cause \ to disable placeholders which other keys don't disable)
213 	}
214 
215     ///selects an index in the completion suggestion list
select(const QModelIndex & ind)216 	void select(const QModelIndex &ind)
217 	{
218 		if (!completer || !completer->list) return;
219 		completer->list->setCurrentIndex(ind);
220 		completer->selectionChanged(ind);
221 	}
222 
223     ///moves the selection index to the next/previous delta-th entry in the suggestion list
selectDelta(const int delta)224 	bool selectDelta(const int delta)
225 	{
226 		if (!completer || !completer->list || !completer->list->isVisible()) {
227 			resetBinding();
228 			return false;
229 		}
230 		QModelIndex ind = completer->list->model()->index(completer->list->currentIndex().row() + delta, 0, QModelIndex());
231 		if (!ind.isValid()) {
232 			if (delta < 0) ind = completer->list->model()->index(0, 0, QModelIndex());
233 			else if (delta > 0) ind = completer->list->model()->index(completer->list->model()->rowCount() - 1, 0, QModelIndex());
234 		}
235 		if (ind.isValid()) select(ind);
236 		return true;
237 	}
238 
239 	/*!
240 	 * Complete the common part of all remaining possible completions.
241 	 * \return true if there was a common part, that could be completed.
242 	 */
completeCommonPrefix(bool reducedRange=false)243 	bool completeCommonPrefix(bool reducedRange = false)
244 	{
245 		QString my_curWord = getCurWord();
246 		if (my_curWord.isEmpty()) return false;
247 		if (!completer) return false;
248 		/*if (!completer->list->isVisible()) {
249 			resetBinding();
250 			return false;
251 		}*/
252 		// get list of most recent choices
253 		const QList<CompletionWord> &words = completer->listModel->getWords();
254 		// filter list for longest common characters
255 		if (words.count() > 1) {
256 			QString myResult = words.first().word;
257 			int curWordLength = my_curWord.length();
258 			my_curWord = completer->listModel->getLastWord().word;
259 
260 			if (reducedRange && words.count() > 10) {
261 				my_curWord = words.at(10).word;
262 			}
263 
264 
265             if(showMostUsed==2){
266                 // as the list is unsorted, all words must be checked !!!
267                 int j=0;
268                 bool allIdentical=false;
269                 for (j = 0; (j < my_curWord.length() && j < myResult.length()); j++) {
270                     for(const auto &cw : words){
271                         if(cw.word.length()<=j){
272                             allIdentical=false;
273                             break;
274                         }
275                         if(cw.word[j]!=myResult[j]){
276                             allIdentical=false;
277                             break;
278                         }
279                         allIdentical=true;
280                     }
281                     if(!allIdentical){
282                         break;
283                     }
284                 }
285                 if(!allIdentical){
286                     j=j-1;
287                 }
288                 if(j>0){
289                     myResult = myResult.left(j);
290                 }else{
291                     return true;
292                 }
293             }else{
294                 for (int j = curWordLength; (j < my_curWord.length() && j < myResult.length()); j++) {
295                     if (myResult[j] != my_curWord[j]) {
296                         myResult = myResult.left(j);
297                     }
298                 }
299             }
300 
301 			if (myResult.length() == curWordLength) {
302 				return false;  // no common segment to complete
303 			}
304 
305 			removeRightWordPart();
306             if(showMostUsed==2){
307                 // fuzzy mode
308                 // remove left hand side as well
309                 QDocumentCursor cursor = editor->cursor();
310                 for (int i = curWordLength; i > 0; i--) cursor.deletePreviousChar();
311                 maxWritten = cursor.columnNumber();
312                 editor->setCursor(cursor);
313                 curWordLength=0;
314             }
315 			insertText(myResult.right(myResult.length() - curWordLength));
316 			completer->filterList(getCurWord(), getMostUsed());
317 			if (!completer->list->currentIndex().isValid())
318 				select(completer->list->model()->index(0, 0, QModelIndex()));
319 			return true;
320 		} else {
321 			if (showMostUsed == 1) return false;
322 			insertCompletedWord();
323 			resetBinding();
324 			return true;
325 		}
326 	}
327 
simpleRestoreAutoOverride(const QString & written="????")328 	void simpleRestoreAutoOverride(const QString &written = "????") //simple means without protecting the change from undo/redo
329 	{
330 		if (!editor) return;
331 		if (!autoOverridenText.isEmpty() && !editor->isAutoOverrideText(written)) {
332 			int curpos = editor->cursor().columnNumber();
333 			if (curpos < maxWritten) {
334 				QDocumentCursor c = editor->cursor();
335 				c.movePosition(maxWritten - curpos, QDocumentCursor::NextCharacter);
336 				editor->setCursor(c);
337 			}
338 			editor->insertText(autoOverridenText);
339 			QDocumentCursor c = editor->cursor();
340 			c.movePosition(autoOverridenText.length() + (curpos < maxWritten ? maxWritten - curpos : 0), QDocumentCursor::PreviousCharacter);
341 			editor->setCursor(c);
342 			editor->resizeAutoOverridenPlaceholder(c, autoOverridenText.size());
343 		}
344 	}
345 
keyPressEvent(QKeyEvent * event,QEditor * editor)346 	virtual bool keyPressEvent(QKeyEvent *event, QEditor *editor)
347 	{
348 		Q_ASSERT (completer && completer->listModel);
349 		if (!completer || !completer->listModel) return false;
350         if (event->key() == Qt::Key_Shift || event->key() == Qt::Key_Alt || event->key() == Qt::Key_AltGr || event->key() == Qt::Key_Control || event->key() == Qt::Key_CapsLock)
351 			return false;
352 		if (!active) return false; //we should never have been called
353 		bool handled = false;
354 		if (event->key() == Qt::Key_Backspace) {
355 			maxWritten--;
356             QDocumentCursorHandle *dch=editor->cursorHandle();
357             Q_ASSERT(dch);
358             dch->deletePreviousChar();
359             if (dch->columnNumber() <= curStart) {
360 				resetBinding();
361 				return true;
362             } else if (dch->columnNumber() + 1 <= curStart && !showAlways) {
363 				completer->widget->hide();
364 				return true;
365 			}
366 			handled = true;
367 		} else if (event->key() == Qt::Key_Escape) {
368 			simpleRestoreAutoOverride("");
369 			resetBinding();
370 			return true;
371 		} else if (event->key() == Qt::Key_Delete) {
372 			if (editor->cursor().columnNumber() < maxWritten) maxWritten--;
373 			if (completer->forcedRef) {
374 				if (editor->cursor().nextChar() == '}')
375 					completer->forcedRef = false;
376 			}
377 			editor->cursor().deleteChar();
378 			handled = true;
379 		} else if (event->key() == Qt::Key_Left) {
380 			if (event->modifiers()) {
381 				resetBinding();
382 				return false;
383 			}
384 			editor->setCursorPosition(curLineNumber, editor->cursor().columnNumber() - 1);
385 			if (editor->cursor().columnNumber() <= curStart) resetBinding();
386 			handled = true;
387 		} else if (event->key() == Qt::Key_Right) {
388 			if (event->modifiers()) {
389 				resetBinding();
390 				return false;
391 			}
392 			editor->setCursorPosition(curLineNumber, editor->cursor().columnNumber() + 1);
393 			if (editor->cursor().columnNumber() > maxWritten) resetBinding();
394 			handled = true;
395 		} else if (event->key() == Qt::Key_Up) return selectDelta(-1);
396 		else if (event->key() == Qt::Key_Down) return selectDelta(+1);
397 		else if (event->key() == Qt::Key_PageUp) return  selectDelta(-5);
398 		else if (event->key() == Qt::Key_PageDown) return  selectDelta(+5);
399 		else if (event->key() == Qt::Key_Home) {
400 			if (!completer->list->isVisible()) {
401 				resetBinding();
402 				return false;
403 			}
404 			QModelIndex ind = completer->list->model()->index(0, 0, QModelIndex());
405 			if (ind.isValid()) select(ind);
406 			return true;
407 		} else if (event->key() == Qt::Key_End) {
408 			if (!completer->list->isVisible()) {
409 				resetBinding();
410 				return false;
411 			}
412 			QModelIndex ind = completer->list->model()->index(completer->list->model()->rowCount() - 1, 0, QModelIndex());
413 			if (ind.isValid()) select(ind);
414 			return true;
415 		}  else if (event->key() == Qt::Key_Tab) {
416 			if (completeCommonPrefix(true)) {
417 				return true;
418 			} else if (insertCompletedWord()) {
419 				resetBinding();
420 				return true;
421 			}
422 			return false;
423 		}  else if (event->key() == Qt::Key_Space && event->modifiers() == Qt::ShiftModifier) {
424 			//showMostUsed=!showMostUsed;
425 			//handled=true;
426 			showMostUsed++;
427             if (showMostUsed > 3)
428 				showMostUsed = 0;
429 			completer->tbAbove->setCurrentIndex(showMostUsed);
430 			completer->tbBelow->setCurrentIndex(showMostUsed);
431 			return true;
432 		} else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
433             insertCompletedWord();
434 			resetBinding();
435 			return true;
436 		} else {
437 			if (event->text().length() != 1 || event->text() == " ") {
438 				resetBinding();
439 				return false;
440 			}
441 
442 			QChar written = event->text().at(0);
443 			if (written == '\\') {
444 				if (getCurWord() == "\\") {
445 					resetBinding();
446 					return false;
447 				} else if (getCurWord() == "") {
448 					maxWritten = curStart + 1;
449 				} else {
450 					if (LatexCompleter::config && LatexCompleter::config->eowCompletes) {
451 						insertCompletedWord();
452 					}
453 					QDocumentCursor edc = editor->cursor();
454 					if (edc.hasSelection()) {
455 						edc.removeSelectedText();
456 						editor->setCursor(edc);
457 					}
458 					curStart = edc.columnNumber();
459 					maxWritten = curStart + 1;
460 				}
461 				bool autoOverride = editor->isAutoOverrideText("\\");
462 				if (!autoOverride) editor->cursor().insertText(written);
463 				else {
464 					QDocumentCursor c = editor->cursor();
465 					editor->document()->beginMacro();
466 					c.deleteChar();
467 					c.insertText("\\");
468 					editor->document()->endMacro();
469 					autoOverridenText = "\\";
470 				}
471 				//editor->insertText(written); <- can't use that since it may break the completer by auto closing brackets
472 				handled = true;
473 			} else if (completer->acceptChar(written, editor->cursor().columnNumber() - curStart)) {
474 				insertText(written);
475 				handled = true;
476 			} else if (event->text().length() == 1 && getCommonEOW().contains(event->text().at(0)) ) {
477 				QString curWord = getCurWord();
478 
479 				if (curWord == "\\" || !LatexCompleter::config || !LatexCompleter::config->eowCompletes) {
480 					resetBinding();
481 					simpleRestoreAutoOverride(written);
482 					return false;
483 				}
484 				const QList<CompletionWord> &words = completer->listModel->getWords();
485 				QString newWord;
486 				int eowchars = 10000;
487 				foreach (const CodeSnippet &w, words) {
488 					if (!w.word.startsWith(curWord)) continue;
489 					if (w.word.length() == curWord.length()) {
490 						newWord = w.word;
491 						break;
492 					}
493 					int newoffset = w.lines.first().indexOf(written, curWord.length());
494 					if (newoffset < 0) continue;
495 					int neweowchars = 0;
496 					for (int i = curWord.length(); i < newoffset; i++)
497 						if (getCommonEOW().contains(w.lines.first()[i]))
498 							neweowchars++;
499 					if (neweowchars < eowchars) {
500 						newWord = w.word;
501 						eowchars = neweowchars;
502 						if (eowchars == 1) break;
503 					}
504 				}
505 
506 				if (!newWord.isEmpty() && newWord.length() != curWord.length()) {
507 					QString insertion = newWord.mid(curWord.length(), newWord.indexOf(written, curWord.length()) - curWord.length() + 1); //choose text until written eow character
508 					insertText(insertion);
509 					//insertText(written);
510 					handled = true;
511 				}
512 
513 				if (!handled) {
514 					insertCompletedWord();
515 					if (newWord.isEmpty())
516 						simpleRestoreAutoOverride(written);
517 					//insertText(written);
518 					resetBinding();
519 					return false;//oldBinding->keyPressEvent(event,editor); //call old input binding for long words (=> key replacements after completions, but the user can still write \")
520 				}
521 			} else {
522 				int curLength = getCurWord().length();
523 				resetBinding();
524 				return curLength >= 2 &&  oldBinding->keyPressEvent(event, editor); //call old input binding for long words (=> key replacements after completions, but the user can still write \")
525 			}
526 		}
527 		completer->filterList(getCurWord(), showMostUsed);
528 		if (!completer->list->currentIndex().isValid())
529 			select(completer->list->model()->index(0, 0, QModelIndex()));
530 		return handled;
531 	}
532 
inputMethodEvent(QInputMethodEvent * event,QEditor * editor)533     virtual bool inputMethodEvent(QInputMethodEvent* event, QEditor *editor)
534     {
535         Q_ASSERT (completer && completer->listModel);
536         if (!completer || !completer->listModel) return false;
537         if(event->commitString().isEmpty()){
538                 return false;
539         }
540 
541         bool handled=false;
542         QString text=event->commitString();
543         if (text.length() != 1 || text == " ") {
544             resetBinding();
545             return false;
546         }
547 
548         QChar written = text.at(0);
549         if (written == '\\') {
550             if (getCurWord() == "\\") {
551                 resetBinding();
552                 return false;
553             } else if (getCurWord() == "") {
554                 maxWritten = curStart + 1;
555             } else {
556                 if (LatexCompleter::config && LatexCompleter::config->eowCompletes) {
557                     insertCompletedWord();
558                 }
559                 QDocumentCursor edc = editor->cursor();
560                 if (edc.hasSelection()) {
561                     edc.removeSelectedText();
562                     editor->setCursor(edc);
563                 }
564                 curStart = edc.columnNumber();
565                 maxWritten = curStart + 1;
566             }
567             bool autoOverride = editor->isAutoOverrideText("\\");
568             if (!autoOverride) editor->cursor().insertText(written);
569             else {
570                 QDocumentCursor c = editor->cursor();
571                 editor->document()->beginMacro();
572                 c.deleteChar();
573                 c.insertText("\\");
574                 editor->document()->endMacro();
575                 autoOverridenText = "\\";
576             }
577             //editor->insertText(written); <- can't use that since it may break the completer by auto closing brackets
578             handled = true;
579         } else if (completer->acceptChar(written, editor->cursor().columnNumber() - curStart)) {
580             insertText(written);
581             handled = true;
582         } else if (getCommonEOW().contains(written) ) {
583             QString curWord = getCurWord();
584 
585             if (curWord == "\\" || !LatexCompleter::config || !LatexCompleter::config->eowCompletes) {
586                 resetBinding();
587                 simpleRestoreAutoOverride(written);
588                 return false;
589             }
590             const QList<CompletionWord> &words = completer->listModel->getWords();
591             QString newWord;
592             int eowchars = 10000;
593             foreach (const CodeSnippet &w, words) {
594                 if (!w.word.startsWith(curWord)) continue;
595                 if (w.word.length() == curWord.length()) {
596                     newWord = w.word;
597                     break;
598                 }
599                 int newoffset = w.lines.first().indexOf(written, curWord.length());
600                 if (newoffset < 0) continue;
601                 int neweowchars = 0;
602                 for (int i = curWord.length(); i < newoffset; i++)
603                     if (getCommonEOW().contains(w.lines.first()[i]))
604                         neweowchars++;
605                 if (neweowchars < eowchars) {
606                     newWord = w.word;
607                     eowchars = neweowchars;
608                     if (eowchars == 1) break;
609                 }
610             }
611 
612             if (!newWord.isEmpty() && newWord.length() != curWord.length()) {
613                 QString insertion = newWord.mid(curWord.length(), newWord.indexOf(written, curWord.length()) - curWord.length() + 1); //choose text until written eow character
614                 insertText(insertion);
615                 //insertText(written);
616                 handled = true;
617             }
618 
619             if (!handled) {
620                 insertCompletedWord();
621                 if (newWord.isEmpty())
622                     simpleRestoreAutoOverride(written);
623                 //insertText(written);
624                 resetBinding();
625                 return false;//oldBinding->keyPressEvent(event,editor); //call old input binding for long words (=> key replacements after completions, but the user can still write \")
626             }
627         }
628 
629         completer->filterList(getCurWord(), showMostUsed);
630         if (!completer->list->currentIndex().isValid())
631             select(completer->list->model()->index(0, 0, QModelIndex()));
632         return handled;
633     }
634 
cursorPositionChanged(QEditor * edit)635 	void cursorPositionChanged(QEditor *edit)
636 	{
637 		if (edit != editor) return; //should never happen
638 		QDocumentCursor c = editor->cursor();
639 		if (c.lineNumber() != curLineNumber || c.columnNumber() < curStart) resetBinding();
640 	}
641 
setMostUsed(int mu,bool quiet=false)642 	void setMostUsed(int mu, bool quiet = false)
643 	{
644 		showMostUsed = mu;
645 		if (quiet)
646 			return;
647 		completer->filterList(getCurWord(), showMostUsed);
648 		if (!completer->list->currentIndex().isValid())
649 			select(completer->list->model()->index(0, 0, QModelIndex()));
650 	}
651 
getMostUsed()652 	int getMostUsed()
653 	{
654 		return showMostUsed;
655 	}
656 
resetBinding()657 	void resetBinding()
658 	{
659 		if (completer)
660 			completer->listModel->setEnvironMode(false);
661 		showMostUsed = false;
662 		QString curWord = getCurWord();
663 		if (!active) return;
664 		QToolTip::hideText();
665 		//reenable auto close chars
666         if(completer)
667             editor->setFlag(QEditor::AutoCloseChars, completer->editorAutoCloseChars);
668 		editor->setInputBinding(oldBinding);
669         if (completer && completer->widget && completer->widget->isVisible()){
670             completer->widget->hide();
671 			editor->setFocus();
672         }
673 		if (completer) {
674 			completer->disconnect(editor, SIGNAL(cursorPositionChanged()), completer, SLOT(cursorPositionChanged()));
675 		}
676 		active = false;
677 		//editor=0; this leads to a crash, as the editor is still in use after reseting the cursor
678 		if (completer && completer->completingKey() && curWord.endsWith("=")) {
679 			LatexEditorView *view = editor->property("latexEditor").value<LatexEditorView *>();
680 			Q_ASSERT(view);
681 			view->mayNeedToOpenCompleter();
682 		}
683         if (completer && completer->completingGraphic() && curWord.endsWith("/")) {
684 			completer->complete(editor, LatexCompleter::CompletionFlags(LatexCompleter::CF_FORCE_VISIBLE_LIST | LatexCompleter::CF_FORCE_GRAPHIC));
685 		}
686 	}
687 
bindTo(QEditor * edit,LatexCompleter * caller,bool forced,int start)688 	void bindTo(QEditor *edit, LatexCompleter *caller, bool forced, int start)
689 	{
690 		if (active) resetBinding();
691 		active = true;
692 		completer = caller;
693 		editor = edit;
694         oldBinding = (editor->inputBindings().count() > 0 ? editor->inputBindings()[0] : nullptr);
695 		editor->setInputBinding(this);
696 		curStart = start > 0 ? start : 0;
697 		maxWritten = editor->cursor().columnNumber();
698 		if (maxWritten < start) maxWritten = start;
699 		curLineNumber = editor->cursor().lineNumber();
700 		showAlways = forced; //curWord!="\\";
701 		completer->filterList(getCurWord());
702 		if (showAlways) {
703 			completer->widget->show();
704 			select(completer->list->model()->index(0, 0, QModelIndex()));
705 		}
706 	}
707 
isActive()708 	bool isActive()
709 	{
710 		return active;
711 	}
712 
713 	QString autoOverridenText;
714 private:
715 	bool active;
716 	bool showAlways;
717 	int showMostUsed;
718 	LatexCompleter *completer;
719 	QEditor *editor;
720 	QEditorInputBindingInterface *oldBinding;
721 	QString completionWord;
722 	int curStart, maxWritten;
723 	int curLineNumber;
724 };
725 
726 //Q_DECLARE_METATYPE(LatexEditorView *)
727 
728 CompleterInputBinding *completerInputBinding = new CompleterInputBinding();
729 //------------------------------Item Delegate--------------------------------
730 class CompletionItemDelegate: public QItemDelegate
731 {
732 public:
CompletionItemDelegate(QObject * parent=nullptr)733     explicit CompletionItemDelegate(QObject *parent = nullptr): QItemDelegate(parent)
734 	{
735 	}
736 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const737 	void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
738 	{
739 		QVariant v = index.model()->data(index);
740 		if (!v.isValid() || !v.canConvert<CompletionWord>()) return;
741 		CompletionWord cw = v.value<CompletionWord>();
742 		if (cw.lines.empty() || cw.placeHolders.empty()) return;
743 		QFont fNormal = option.font;
744 		QFont fPlHolder = option.font;
745 		//fPlHolder.setItalic(true);
746 		QColor normalColor, plHolderColor;
747 		if ((QStyle::State_HasFocus | QStyle::State_Selected) & option.state) {
748 			painter->fillRect(option.rect, option.palette.brush(QPalette::Highlight));
749 			normalColor = option.palette.color(QPalette::HighlightedText);
750 		} else {
751 			painter->fillRect(option.rect, option.palette.brush(QPalette::Base)); //doesn't seem to be necessary
752 			normalColor = option.palette.color(QPalette::Text);
753 		}
754 		painter->setPen(normalColor);
755 		plHolderColor = normalColor;
756 		plHolderColor.setAlpha(128);
757 
758         QRectF r = option.rect;
759 		r.setLeft(r.left() + 2);
760 		bool drawPlaceholder = !cw.placeHolders.empty();
761 		QString firstLine = cw.lines[0];
762 		if (!cw.getName().isEmpty()) {
763 			drawPlaceholder = false;
764 			firstLine = cw.getName();
765 		}
766 		if (!drawPlaceholder)
767             painter->drawText(r, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, firstLine);
768 		else {
769             QFontMetricsF fmn(fNormal);
770             QFontMetricsF fmi(fPlHolder);
771 			int p = 0;
772 			for (int i = 0; i < cw.placeHolders[0].size(); i++) {
773 				QString temp = firstLine.mid(p, cw.placeHolders[0][i].offset - p);
774                 painter->drawText(r, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, temp);
775 				r.setLeft(r.left() + UtilsUi::getFmWidth(fmn, temp));
776 				temp = firstLine.mid(cw.placeHolders[0][i].offset, cw.placeHolders[0][i].length);
777 				painter->setFont(fPlHolder);
778 				painter->setPen(plHolderColor);
779                 painter->drawText(r, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, temp);
780 				r.setLeft(r.left() + UtilsUi::getFmWidth(fmi, temp) + 1);
781 				p = cw.placeHolders[0][i].offset + cw.placeHolders[0][i].length;
782 				painter->setFont(fNormal);
783 				painter->setPen(normalColor);
784 				if (p > firstLine.length()) break;
785 			}
786             painter->drawText(r, Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine, firstLine.mid(p));
787 		}
788 	}
789 };
790 
791 
792 
793 //----------------------------list model------------------------------------
794 LatexCompleterConfig *CompletionListModel::config = nullptr;
rowCount(const QModelIndex & parent) const795 int CompletionListModel::rowCount(const QModelIndex &parent) const
796 {
797 	Q_UNUSED(parent)
798 	return words.count();
799 }
800 
data(const QModelIndex & index,int role) const801 QVariant CompletionListModel::data(const QModelIndex &index, int role)const
802 {
803 	if (!index.isValid())
804 		return QVariant();
805 
806 	if (index.row() >= words.count())
807 		return QVariant();
808 
809 	if (role == Qt::DisplayRole) {
810 		QVariant temp;
811 		temp.setValue(words.at(index.row()));
812 		return temp;
813 	} else
814 		return QVariant();
815 }
816 
headerData(int section,Qt::Orientation orientation,int role) const817 QVariant CompletionListModel::headerData(int section, Qt::Orientation orientation,
818         int role) const
819 {
820 	Q_UNUSED(role)
821 	Q_UNUSED(orientation)
822 	Q_UNUSED(section)
823 
824 	return QVariant();
825 }
826 
isNextCharPossible(const QChar & c)827 bool CompletionListModel::isNextCharPossible(const QChar &c)
828 {
829 	//if (words.count()>100) //probable that every char is there (especially since acceptedChars is already checked)
830 	//	return true;  -> leads to faulty behaviour (\b{ is supposed to be within list ...)
831 	Qt::CaseSensitivity cs = Qt::CaseInsensitive;
832 	if (LatexCompleter::config &&
833 	        LatexCompleter::config->caseSensitive == LatexCompleterConfig::CCS_CASE_SENSITIVE)
834 		cs = Qt::CaseSensitive;
835 	QString extension = curWord + c;
836 	foreach (const CompletionWord &cw, words)
837 		if (cw.word.startsWith(extension, cs)) return true;
838 	return false;
839 }
840 
canFetchMore(const QModelIndex &) const841 bool CompletionListModel::canFetchMore(const QModelIndex &) const
842 {
843 	return mCanFetchMore;
844 }
845 
fetchMore(const QModelIndex &)846 void CompletionListModel::fetchMore(const QModelIndex &)
847 {
848 	beginInsertRows(QModelIndex(), words.count(), qMin(words.count() + 100, mWordCount));
849 	filterList(mLastWord, mLastMU, true, mLastType);
850 	endInsertRows();
851 }
852 
getLastWord()853 CompletionWord CompletionListModel::getLastWord()
854 {
855 	return mLastWordInList;
856 }
857 
setKeyValWords(const QString & name,const QSet<QString> & newwords)858 void CompletionListModel::setKeyValWords(const QString &name, const QSet<QString> &newwords)
859 {
860 	QList<CompletionWord> newWordList;
861 	newWordList.clear();
862 	for (QSet<QString>::const_iterator i = newwords.constBegin(); i != newwords.constEnd(); ++i) {
863 		QString str = *i;
864 		QString validValues;
865 		if (str.contains("#") && !str.startsWith("#")) {
866 			int j = str.indexOf("#");
867 			validValues = str.mid(j + 1);
868 			str = str.left(j);
869 			QStringList lst = validValues.split(",");
870 			QString key = str;
871 			if (key.endsWith("="))
872 				key.chop(1);
873             setKeyValWords(name + "/" + key, convertStringListtoSet(lst));
874 		}
875 		CompletionWord cw(str, false);
876 		cw.index = 0;
877 		cw.usageCount = -2;
878 		cw.snippetLength = 0;
879 		newWordList.append(cw);
880 	}
881     std::sort(newWordList.begin(), newWordList.end());
882 
883 	keyValLists.insert(name, newWordList);
884 }
885 
setContextWords(const QSet<QString> & newwords,const QString & context)886 void CompletionListModel::setContextWords(const QSet<QString> &newwords, const QString &context)
887 {
888 	QList<CompletionWord> newWordList;
889 	newWordList.clear();
890 	for (QSet<QString>::const_iterator i = newwords.constBegin(); i != newwords.constEnd(); ++i) {
891 		QString str = *i;
892 		QString validValues;
893 		if (str.contains("#")) {
894 			int j = str.indexOf("#");
895 			validValues = str.mid(j + 1);
896 			str = str.left(j);
897 			QStringList lst = validValues.split(",");
898 			QString key = str;
899 			if (key.endsWith("="))
900 				key.chop(1);
901             setKeyValWords(context + "/" + key, convertStringListtoSet(lst));
902 		}
903 		CompletionWord cw(str);
904 		cw.index = 0;
905         cw.usageCount = 2;
906 
907 		cw.snippetLength = 0;
908 		newWordList.append(cw);
909 	}
910     std::sort(newWordList.begin(), newWordList.end());
911 
912 	contextLists.insert(context, newWordList);
913 }
914 
makeSortWord(const QString & normal)915 QString makeSortWord(const QString &normal)
916 {
917 	QString res = normal.toLower();
918 	res.replace("{", "!"); //js: still using dirty hack, however order should be ' '{[* abcde...
919 	res.replace("}", "!"); // needs to be replaced as well for sorting \bgein{abc*} after \bgein{abc}
920 	res.replace("[", "\\"); //(first is a space->) !"# follow directly in the ascii table
921 	res.replace("*", "#");
922 	return res;
923 }
924 
setEnvironMode(bool mode)925 void CompletionListModel::setEnvironMode(bool mode)
926 {
927 	mEnvMode = mode;
928 }
929 
cwLessThan(const CompletionWord & s1,const CompletionWord & s2)930 bool cwLessThan(const CompletionWord &s1, const CompletionWord &s2)
931 {
932     return s1.score > s2.score;
933 }
934 
935 
filterList(const QString & word,int mostUsed,bool fetchMore,CodeSnippet::Type type)936 void CompletionListModel::filterList(const QString &word, int mostUsed, bool fetchMore, CodeSnippet::Type type)
937 {
938 	if (mostUsed < 0)
939 		mostUsed = LatexCompleter::config->preferedCompletionTab;
940 	if (!word.isEmpty() && word == curWord && mostUsed == mostUsedUpdated && !fetchMore) return; //don't return if mostUsed differnt from last call
941 	mLastWord = word;
942 	mLastMU = mostUsed;
943 	mLastType = type;
944 	mCanFetchMore = false;
945 	mostUsedUpdated = mostUsed;
946 	if (!fetchMore)
947 		words.clear();
948 	Qt::CaseSensitivity cs = Qt::CaseInsensitive;
949 	bool checkFirstChar = false;
950 	if (LatexCompleter::config) {
951 		if (LatexCompleter::config->caseSensitive == LatexCompleterConfig::CCS_CASE_SENSITIVE)
952 			cs = Qt::CaseSensitive;
953 		checkFirstChar = LatexCompleter::config->caseSensitive == LatexCompleterConfig::CCS_FIRST_CHARACTER_CASE_SENSITIVE && word.length() > 1;
954 	}
955 	int cnt = 0;
956 	QString sortWord = makeSortWord(word);
957     if(mostUsed==2){
958         //fuzzy search
959         // proof of concept
960         // generate regexp
961         //QTime tm;
962         //tm.start();
963 #if (QT_VERSION>=QT_VERSION_CHECK(5,14,0))
964         QStringList chars=word.split("",Qt::SkipEmptyParts);
965 #else
966         QStringList chars=word.split("",QString::SkipEmptyParts);
967 #endif
968         if(chars.value(0)==QChar('\\')){
969             chars.takeFirst();
970         }
971         QString regExpression=chars.join(".*");
972 
973         QRegularExpression rx(regExpression);
974 
975         words=QtConcurrent::blockingFiltered(baselist,[rx](const CompletionWord &item){
976             return item.sortWord.contains(rx);
977         });
978 
979         QtConcurrent::blockingMap(words,[word](CompletionWord &item){
980             int score=0;
981             int l=0;
982             int lastMatch=-2;
983             for(int i=0;i<item.sortWord.length();i++){
984                 if(l>=word.length())
985                     break;
986                 if(item.sortWord.at(i)==word.at(l)){
987                     if(lastMatch+1==i)
988                         score+=20;
989                     score-=i; // later position are degraded
990                     lastMatch=i;
991                     l++;
992                 }
993             }
994             if (item.word.contains('@')) {
995                 if(item.word.contains("@@")){ // special treatment for command-names containing @
996                     QString ln = item.lines[0];
997                     ln.replace("@@", "@");
998                     item=CompletionWord(ln);
999                 }else{
1000                     QString ln = item.lines[0];
1001                     ln.replace("{@}", "%<bibid%>");
1002                     ln.replace("{@l}", "%<label%>");
1003                     item=CompletionWord(ln);
1004                 }
1005             }
1006             // reduce score for atypical or unused
1007             if(item.index){
1008                 // don't upvote reference commands
1009                 score+=item.usageCount<=0 ? 10*item.usageCount : 10;
1010             }
1011             item.score=score;
1012         });
1013 
1014         std::stable_sort(words.begin(),words.end(),cwLessThan);
1015         //qDebug()<<tm.elapsed();
1016     }else{
1017         // normal sorting
1018         if (!fetchMore) {
1019             it = std::lower_bound(baselist.begin(), baselist.end(), CompletionWord(word));
1020         }
1021         // special treatment for citation commands as they generated on the fly
1022         //TODO: needs to be adapted to later code
1023         if (it == baselist.end() || !it->word.startsWith(word, cs)) {
1024             int i = word.lastIndexOf("{");
1025             QString test = word.left(i) + "{";
1026             QList<CompletionWord>::iterator lIt = std::lower_bound(baselist.begin(), baselist.end(), CompletionWord(test));
1027             if(lIt != baselist.end()){
1028                 // part of command without argument is in baselist
1029                 // is it a on-the-fly filled command ?
1030                 if(lIt->word.contains('@')){
1031                     QString citeStart = word.mid(i + 1);
1032                     QString ln = lIt->lines[0];
1033                     struct Rpl {
1034                         QString id;
1035                         QList<CompletionWord>& lst;
1036                     };
1037 
1038                     QMap<QString,std::shared_ptr<Rpl>>replacement;
1039                     std::shared_ptr<Rpl> r0(new Rpl{"{%<bibid%>}",wordsCitations});
1040                     replacement.insert("@",r0);
1041                     std::shared_ptr<Rpl> r1(new Rpl{"{%<label%>}",wordsLabels});
1042                     replacement.insert("@l",r1);
1043                     for(QMap<QString,std::shared_ptr<Rpl>>::const_iterator localIt=replacement.cbegin();localIt!=replacement.cend();++localIt){
1044                         QString searchWord="{"+localIt.key()+"}";
1045                         QString key=localIt.key();
1046                         std::shared_ptr<Rpl> repl=*localIt;
1047                         if(ln.contains(searchWord)){
1048                             if(repl->id.startsWith("{%<"+citeStart)){
1049                                 // keep general id if it matches input
1050                                 ln.replace(searchWord, repl->id);
1051                                 words.append(CompletionWord(ln));
1052                             }
1053                             cnt++;
1054                             foreach (const CompletionWord id, repl->lst) {
1055                                 if(!id.word.startsWith(citeStart))
1056                                     continue;
1057                                 CompletionWord cw = *lIt;
1058                                 int index = cw.lines[0].indexOf(key);
1059                                 cw.word.replace(key, id.word);
1060                                 cw.sortWord.replace(key, id.word);
1061                                 cw.lines[0].replace(key, id.word);
1062                                 for (int i = 0; i < cw.placeHolders.count(); i++) {
1063                                     if (cw.placeHolders[i].isEmpty())
1064                                         continue;
1065                                     for (int j = 0; j < cw.placeHolders[i].count(); j++) {
1066                                         CodeSnippetPlaceHolder &ph = cw.placeHolders[i][j];
1067                                         if (ph.offset > index)
1068                                             ph.offset += id.word.length() - 1;
1069                                     }
1070                                 }
1071                                 words.append(cw);
1072                             }
1073                             cnt += repl->id.length();
1074                         }
1075                     }
1076                 }
1077             }
1078         }
1079         //
1080         while (it != baselist.end()) {
1081             if (it->word.startsWith(word, cs) &&
1082                     (!checkFirstChar || it->word[1] == word[1]) ) {
1083 
1084                 if (mostUsed == 3 || it->usageCount >= mostUsed || it->usageCount == -2) {
1085                     if (mostUsed < 2 && type != CodeSnippet::none && it->type != type) {
1086                         ++it;
1087                         continue; // leave out words which don't have the proper type (except for all-mode)
1088                     }
1089                     if (mEnvMode) {
1090                         CompletionWord cw = *it;
1091                         if (cw.word.startsWith("\\begin") || cw.word.startsWith("\\end")) {
1092                             QString text = cw.word;
1093                             int i = text.indexOf('}');
1094                             text.truncate(i + 1);
1095                             cw.word = text;
1096                             cw.sortWord = text;
1097                             cw.lines = QStringList(text);
1098                             cw.placeHolders.clear();
1099                             cw.placeHolders.append(QList<CodeSnippetPlaceHolder>());
1100                         }
1101 
1102                         if (!words.contains(cw))
1103                             words.append(cw);
1104                     } else {
1105                         // cite command
1106                         if (it->word.contains('@')) {
1107                             if(it->word.contains("@@")){ // special treatment for command-names containing @
1108                                 QString ln = it->lines[0];
1109                                 ln.replace("@@", "@");
1110                                 words.append(CompletionWord(ln));
1111                             }else{
1112                                 QString ln = it->lines[0];
1113                                 struct Rpl {
1114                                     QString id;
1115                                     QList<CompletionWord>& lst;
1116                                 };
1117 
1118                                 QMap<QString,std::shared_ptr<Rpl>>replacement;
1119                                 std::shared_ptr<Rpl> r0(new Rpl{"{%<bibid%>}",wordsCitations});
1120                                 replacement.insert("@",r0);
1121                                 std::shared_ptr<Rpl> r1(new Rpl{"{%<label%>}",wordsLabels});
1122                                 replacement.insert("@l",r1);
1123                                 for(QMap<QString,std::shared_ptr<Rpl>>::const_iterator localIt=replacement.cbegin();localIt!=replacement.cend();++localIt){
1124                                     QString searchWord="{"+localIt.key()+"}";
1125                                     QString key=localIt.key();
1126                                     std::shared_ptr<Rpl> repl=*localIt;
1127                                     if(ln.contains(searchWord)){
1128                                         ln.replace(searchWord, repl->id);
1129                                         words.append(CompletionWord(ln));
1130                                         cnt++;
1131                                         foreach (const CompletionWord id, repl->lst) {
1132                                             CompletionWord cw = *it;
1133                                             int index = cw.lines[0].indexOf(key);
1134                                             cw.word.replace(key, id.word);
1135                                             cw.sortWord.replace(key, id.word);
1136                                             cw.lines[0].replace(key, id.word);
1137                                             for (int i = 0; i < cw.placeHolders.count(); i++) {
1138                                                 if (cw.placeHolders[i].isEmpty())
1139                                                     continue;
1140                                                 for (int j = 0; j < cw.placeHolders[i].count(); j++) {
1141                                                     CodeSnippetPlaceHolder &ph = cw.placeHolders[i][j];
1142                                                     if (ph.offset > index)
1143                                                         ph.offset += id.word.length() - 1;
1144                                                 }
1145                                             }
1146                                             words.append(cw);
1147                                         }
1148                                         cnt += repl->id.length();
1149                                     }
1150                                 }
1151                             }
1152                         } else {
1153                             words.append(*it);
1154                         }
1155                     }
1156                     cnt++;
1157                 }
1158             } else {
1159                 if (!it->sortWord.startsWith(sortWord))
1160                     break; // sorted list
1161             }
1162             ++it;
1163             if (cnt > 100) {
1164                 mCanFetchMore = true;
1165                 break;
1166             }
1167         }
1168     }
1169     curWord = word;
1170     if (!fetchMore) {
1171         mWordCount = words.count();
1172         if (!words.isEmpty())
1173             mLastWordInList = words.last();
1174     }
1175     if (mCanFetchMore && !fetchMore) {
1176         // calculate real number of rows
1177         QString wordp = word;
1178         if (wordp.isEmpty()) {
1179             mWordCount = baselist.count();
1180             mLastWordInList = baselist.last();
1181         } else {
1182             QChar lst = wordp[wordp.length() - 1];
1183             ushort nr = lst.unicode();
1184             wordp[wordp.length() - 1] = QChar(nr + 1);
1185             QList<CompletionWord>::const_iterator it2 = std::lower_bound(baselist.constBegin(),baselist.constEnd(), CompletionWord(wordp));
1186             mWordCount = it2 - it;
1187             if(it2==baselist.constBegin()){
1188                 mLastWordInList = baselist.last();
1189             }else{
1190                 mLastWordInList = *(--it2);
1191             }
1192         }
1193     }
1194 
1195 	if (!fetchMore) {
1196 		beginResetModel();
1197 		endResetModel();
1198 	}
1199 }
1200 
incUsage(const QModelIndex & index)1201 void CompletionListModel::incUsage(const QModelIndex &index)
1202 {
1203 	if (!index.isValid())
1204 		return ;
1205 
1206 	if (index.row() >= words.size())
1207 		return ;
1208 
1209 	int j = index.row();
1210 	CompletionWord curWord = words.at(j);
1211 	if (curWord.usageCount < -1)
1212 		return; // don't count text words
1213 
1214     CodeSnippetList::iterator it = std::lower_bound(wordsCommands.begin(), wordsCommands.end(), curWord);
1215 	if (it == wordsCommands.end()) // not found, e.g. citations
1216 		return;
1217 	if (it->word == curWord.word) {
1218 		it->usageCount++;
1219 		if (curWord.snippetLength > 0) {
1220 			bool replaced = false;
1221 			QList<QPair<int, int> >res = config->usage.values(curWord.index);
1222 			for (int j = 0; j < res.size(); ++j) {
1223 				if (res.at(j).first == curWord.snippetLength) {
1224 					config->usage.remove(curWord.index, res.at(j));
1225                     config->usage.insert(curWord.index, qMakePair(curWord.snippetLength, it->usageCount));
1226 					replaced = true;
1227 					break;
1228 				}
1229 			}
1230 			if (!replaced)
1231 				config->usage.insert(curWord.index, qMakePair(curWord.snippetLength, it->usageCount)); // new word
1232 		}
1233 	}
1234 }
1235 
1236 typedef QPair<int, int> PairIntInt;
setBaseWords(const QSet<QString> & newwords,CompletionType completionType)1237 void CompletionListModel::setBaseWords(const QSet<QString> &newwords, CompletionType completionType)
1238 {
1239 	QList<CompletionWord> newWordList;
1240 	newWordList.clear();
1241 	for (QSet<QString>::const_iterator i = newwords.constBegin(); i != newwords.constEnd(); ++i) {
1242 		QString str = *i;
1243 		CompletionWord cw(str);
1244 		if (completionType == CT_COMMANDS) {
1245 			cw.index = qHash(str);
1246 			cw.snippetLength = str.length();
1247 			cw.usageCount = 0;
1248 			QList<QPair<int, int> >res = config->usage.values(cw.index);
1249 			foreach (const PairIntInt &elem, res) {
1250 				if (elem.first == cw.snippetLength) {
1251 					cw.usageCount = elem.second;
1252 					break;
1253 				}
1254 			}
1255 		} else {
1256 			cw.index = 0;
1257 			cw.usageCount = -2;
1258 			cw.snippetLength = 0;
1259 		}
1260 		newWordList.append(cw);
1261 	}
1262     std::sort(newWordList.begin(), newWordList.end());
1263 
1264 	switch (completionType) {
1265 	case CT_NORMALTEXT:
1266 		wordsText = newWordList;
1267 		break;
1268 	case CT_CITATIONS:
1269 		wordsCitations = newWordList;
1270 		break;
1271 	default:
1272 		wordsCommands = newWordList;
1273 	}
1274 
1275 	//if (completionType==CT_NORMALTEXT) wordsText=newWordList;
1276 	//else wordsCommands=newWordList;
1277 	baselist = wordsCommands;
1278 }
1279 
setBaseWords(const std::set<QString> & newwords,CompletionType completionType)1280 void CompletionListModel::setBaseWords(const std::set<QString> &newwords, CompletionType completionType)
1281 {
1282     QList<CompletionWord> newWordList;
1283     newWordList.clear();
1284     for (std::set<QString>::const_iterator i = newwords.cbegin(); i != newwords.cend(); ++i) {
1285         QString str = *i;
1286         CompletionWord cw(str);
1287         if (completionType == CT_COMMANDS) {
1288             cw.index = qHash(str);
1289             cw.snippetLength = str.length();
1290             cw.usageCount = 0;
1291             QList<QPair<int, int> >res = config->usage.values(cw.index);
1292             foreach (const PairIntInt &elem, res) {
1293                 if (elem.first == cw.snippetLength) {
1294                     cw.usageCount = elem.second;
1295                     break;
1296                 }
1297             }
1298         } else {
1299             cw.index = 0;
1300             cw.usageCount = -2;
1301             cw.snippetLength = 0;
1302         }
1303         newWordList.append(cw);
1304     }
1305 
1306     switch (completionType) {
1307     case CT_NORMALTEXT:
1308         wordsText = newWordList;
1309         break;
1310     case CT_CITATIONS:
1311         wordsCitations = newWordList;
1312         break;
1313     default:
1314         wordsCommands = newWordList;
1315     }
1316 
1317     //if (completionType==CT_NORMALTEXT) wordsText=newWordList;
1318     //else wordsCommands=newWordList;
1319     baselist = wordsCommands;
1320 }
1321 
setBaseWords(const QList<CompletionWord> & newwords,CompletionType completionType)1322 void CompletionListModel::setBaseWords(const QList<CompletionWord> &newwords, CompletionType completionType)
1323 {
1324 	QList<CompletionWord> newWordList;
1325 	newWordList.clear();
1326 	foreach (const CompletionWord &cw, newwords) {
1327 		newWordList.append(cw);
1328 	}
1329     std::sort(newWordList.begin(), newWordList.end());
1330 
1331 	//if (completionType==CT_NORMALTEXT) wordsText=newWordList;
1332 	//else wordsCommands=newWordList;
1333 	switch (completionType) {
1334 	case CT_NORMALTEXT:
1335 		wordsText = newWordList;
1336 		break;
1337 	case CT_CITATIONS:
1338 		wordsCitations = newWordList;
1339 		break;
1340 	default:
1341 		wordsCommands = newWordList;
1342 	}
1343 
1344 	baselist = wordsCommands;
1345 }
1346 
setBaseWords(const CodeSnippetList & baseCommands,const CodeSnippetList & newwords,CompletionType completionType)1347 void CompletionListModel::setBaseWords(const CodeSnippetList &baseCommands, const CodeSnippetList &newwords, CompletionType completionType)
1348 {
1349 	CodeSnippetList newWordList;
1350 
1351 	newWordList << baseCommands;
1352 	newWordList.unite(newwords);
1353 
1354 	CodeSnippetList::iterator last = std::unique(newWordList.begin(), newWordList.end());
1355 	newWordList.erase(last, newWordList.end());
1356 
1357 	switch (completionType) {
1358 	case CT_NORMALTEXT:
1359 		wordsText = newWordList;
1360 		break;
1361 	case CT_CITATIONS:
1362 		wordsCitations = newWordList;
1363 		break;
1364 	case CT_LABELS:
1365 		wordsLabels = newWordList;
1366 		break;
1367 	default:
1368 		wordsCommands = newWordList;
1369 	}
1370 
1371 	baselist = newWordList;
1372 }
1373 
setAbbrevWords(const QList<CompletionWord> & newwords)1374 void CompletionListModel::setAbbrevWords(const QList<CompletionWord> &newwords)
1375 {
1376 	wordsAbbrev = newwords;
1377     std::sort(wordsAbbrev.begin(), wordsAbbrev.end());
1378 }
1379 
setConfig(LatexCompleterConfig * newConfig)1380 void CompletionListModel::setConfig(LatexCompleterConfig *newConfig)
1381 {
1382 	config = newConfig;
1383 }
1384 
1385 
1386 //------------------------------completer-----------------------------------
1387 LatexReference *LatexCompleter::latexReference = nullptr;
1388 LatexCompleterConfig *LatexCompleter::config = nullptr;
1389 
LatexCompleter(const LatexParser & latexParser,QObject * p)1390 LatexCompleter::LatexCompleter(const LatexParser &latexParser, QObject *p): QObject(p), latexParser(latexParser), maxWordLen(0), editorAutoCloseChars(false), forcedRef(false),
1391 	forcedGraphic(false), forcedCite(false), forcedPackage(false), forcedKeyval(false), forcedSpecialOption(false), forcedLength(false), startedFromTriggerKey(false)
1392 {
1393 	//   addTrigger("\\");
1394 	if (!qobject_cast<QWidget *>(parent()))
1395         QMessageBox::critical(nullptr, "Serious PROBLEM", QString("The completer has been created without a parent widget. This is impossible!\n") +
1396 		                      QString("Please report it ASAP to the bug tracker on texstudio.sf.net and check if your computer is going to explode!\n") +
1397 		                      QString("(please report the bug *before* going to a safe place, you could rescue others)"), QMessageBox::Ok);
1398 	list = new QListView(qobject_cast<QWidget *>(parent()));
1399 	listModel = new CompletionListModel(list);
1400     connect(list, SIGNAL(clicked(const QModelIndex&)) , this, SLOT(selectionChanged(const QModelIndex&)));
1401 	list->setModel(listModel);
1402 	list->setFocusPolicy(Qt::NoFocus);
1403 	list->setItemDelegate(new CompletionItemDelegate(list));
1404 	list->setAutoFillBackground(true);
1405     editor = nullptr;
1406 	workingDir = "/";
1407     dirReader = nullptr;
1408     bibReader = nullptr;
1409     packageList = nullptr;
1410 	widget = new QWidget(qobject_cast<QWidget *>(parent()));
1411 	//widget->setAutoFillBackground(true);
1412 	QVBoxLayout *layout = new QVBoxLayout;
1413 	layout->setSpacing(0);
1414 	tbAbove = new QTabBar();
1415 	tbAbove->setShape(QTabBar::RoundedNorth);
1416 	tbAbove->addTab(tr("typical"));
1417 	tbAbove->addTab(tr("most used"));
1418     tbAbove->addTab(tr("fuzzy"));
1419 	tbAbove->addTab(tr("all"));
1420 	tbAbove->setToolTip(tr("press shift+space to change view"));
1421 	layout->addWidget(tbAbove);
1422 	tbAbove->hide();
1423 	layout->addWidget(list);
1424 	tbBelow = new QTabBar();
1425 	tbBelow->setShape(QTabBar::RoundedSouth);
1426 	tbBelow->addTab(tr("typical"));
1427 	tbBelow->addTab(tr("most used"));
1428     tbBelow->addTab(tr("fuzzy"));
1429 	tbBelow->addTab(tr("all"));
1430 	tbBelow->setToolTip(tr("press shift+space to change view"));
1431 	layout->addWidget(tbBelow);
1432 	widget->setLayout(layout);
1433 	widget->setContextMenuPolicy(Qt::PreventContextMenu);
1434 	connect(list, SIGNAL(activated(QModelIndex)), this, SLOT(listClicked(QModelIndex)));
1435 	// todo: change tab when shift+space is pressed ...
1436 	//connect(tbBelow,SIGNAL(currentChanged(int)),this,SLOT(changeView(int)));
1437 	//connect(tbAbove,SIGNAL(currentChanged(int)),this,SLOT(changeView(int)));
1438 }
1439 
~LatexCompleter()1440 LatexCompleter::~LatexCompleter()
1441 {
1442 	if (dirReader) {
1443 		dirReader->quit();
1444 		dirReader->wait();
1445         delete dirReader;
1446 	}
1447 	if (bibReader) {
1448 		bibReader->quit();
1449         bibReader->wait();
1450         delete bibReader;
1451 	}
1452 }
1453 
changeView(int pos)1454 void LatexCompleter::changeView(int pos)
1455 {
1456 	completerInputBinding->setMostUsed(pos);
1457 }
1458 
listClicked(QModelIndex index)1459 void LatexCompleter::listClicked(QModelIndex index)
1460 {
1461 
1462 	Q_UNUSED(index)
1463 	if (!completerInputBinding->insertCompletedWord()) {
1464 		editor->insertText("\n");
1465 	}
1466 	completerInputBinding->resetBinding();
1467 }
1468 
insertText(QString txt)1469 void LatexCompleter::insertText(QString txt)
1470 {
1471 	if (!isVisible())
1472 		return;
1473 	completerInputBinding->insertText(txt);
1474 	QString cur = completerInputBinding->getCurWord();
1475 	filterList(cur, completerInputBinding->getMostUsed());
1476 }
1477 
setAdditionalWords(const QSet<QString> & newwords,CompletionType completionType)1478 void LatexCompleter::setAdditionalWords(const QSet<QString> &newwords, CompletionType completionType)
1479 {
1480 	// convert to codesnippets
1481 	CodeSnippetList newWordList;
1482 	for (QSet<QString>::const_iterator i = newwords.constBegin(); i != newwords.constEnd(); ++i) {
1483 		QString str = *i;
1484 		bool isReference = str.startsWith('@');
1485 		if (isReference)
1486 			str = str.mid(1);
1487 		CompletionWord cw(str);
1488 		if (completionType == CT_COMMANDS) {
1489 			cw.index = qHash(str);
1490 			cw.snippetLength = str.length();
1491 			cw.usageCount = isReference ? 2 : 0; // make reference always visible (most used) in completer
1492 			QList<QPair<int, int> >res = config->usage.values(cw.index);
1493 			foreach (const PairIntInt &elem, res) {
1494 				if (elem.first == cw.snippetLength) {
1495 					cw.usageCount = elem.second;
1496 					break;
1497 				}
1498 			}
1499 		} else {
1500 			cw.index = 0;
1501 			cw.usageCount = -2;
1502 			cw.snippetLength = 0;
1503 		}
1504 		newWordList.append(cw);
1505 	}
1506     std::sort(newWordList.begin(), newWordList.end());
1507 	//
1508 	CodeSnippetList concated;
1509 	if (config && completionType == CT_COMMANDS) concated.unite(config->words);
1510 	//concated.unite(newwords);
1511 	listModel->setBaseWords(concated, newWordList, completionType);
1512 	widget->resize(200, 200);
1513 }
1514 /*!
1515  * \brief setAdditionalWords from a std::set<QString>
1516  * The advantage is that a std::set is already sorted.
1517  * \param newwords
1518  * \param completionType
1519  */
setAdditionalWords(const std::set<QString> & newwords,CompletionType completionType)1520 void LatexCompleter::setAdditionalWords(const std::set<QString> &newwords, CompletionType completionType)
1521 {
1522     // convert to codesnippets
1523     CodeSnippetList newWordList;
1524     for (std::set<QString>::const_iterator i = newwords.cbegin(); i != newwords.cend(); ++i) {
1525         QString str = *i;
1526         bool isReference = str.startsWith('@');
1527         if (isReference)
1528             str = str.mid(1);
1529         CompletionWord cw(str);
1530         if (completionType == CT_COMMANDS) {
1531             cw.index = qHash(str);
1532             cw.snippetLength = str.length();
1533             cw.usageCount = isReference ? 2 : 0; // make reference always visible (most used) in completer
1534             QList<QPair<int, int> >res = config->usage.values(cw.index);
1535             foreach (const PairIntInt &elem, res) {
1536                 if (elem.first == cw.snippetLength) {
1537                     cw.usageCount = elem.second;
1538                     break;
1539                 }
1540             }
1541         } else {
1542             cw.index = 0;
1543             cw.usageCount = -2;
1544             cw.snippetLength = 0;
1545         }
1546         newWordList.append(cw);
1547     }
1548 
1549     CodeSnippetList concated;
1550     if (config && completionType == CT_COMMANDS) concated.unite(config->words);
1551     //concated.unite(newwords);
1552     listModel->setBaseWords(concated, newWordList, completionType);
1553     widget->resize(200, 200);
1554 }
1555 
setAdditionalWords(const CodeSnippetList & newwords,CompletionType completionType)1556 void LatexCompleter::setAdditionalWords(const CodeSnippetList &newwords, CompletionType completionType)
1557 {
1558 	CodeSnippetList concated;
1559 	if (config && completionType == CT_COMMANDS) concated.unite(config->words);
1560 	//concated.unite(newwords);
1561 	listModel->setBaseWords(concated, newwords, completionType);
1562 	widget->resize(200, 200);
1563 }
1564 
setKeyValWords(const QString & name,const QSet<QString> & newwords)1565 void LatexCompleter::setKeyValWords(const QString &name, const QSet<QString> &newwords)
1566 {
1567 	listModel->setKeyValWords(name, newwords);
1568 }
1569 
setContextWords(const QSet<QString> & newwords,const QString & context)1570 void LatexCompleter::setContextWords(const QSet<QString> &newwords, const QString &context)
1571 {
1572 	listModel->setContextWords(newwords, context);
1573 }
1574 
1575 /*!
1576  * \brief adjust width and position of the widget.
1577  * \return BelowCursor if the calculated position is below the cursor, otherwise AboveCursor
1578  */
adjustWidget()1579 void LatexCompleter::adjustWidget()
1580 {
1581 	// adjust width
1582 	int newWordMax = 0;
1583 	QFont f = QApplication::font();
1584 	f.setItalic(true);
1585 	QFontMetrics fm(f);
1586 	const QList<CompletionWord> &words = listModel->getWords();
1587 	for (int i = 0; i < words.size(); i++) {
1588 		if (words[i].lines.empty() || words[i].placeHolders.empty()) continue;
1589 		int temp = UtilsUi::getFmWidth(fm, words[i].lines[0]) + words[i].placeHolders[0].size() + 10;
1590 		if (temp > newWordMax) newWordMax = temp;
1591 	}
1592 	maxWordLen = newWordMax;
1593     int width = qMax(400, maxWordLen);
1594 	QScrollBar *bar = list->verticalScrollBar();
1595 	if (bar && bar->isVisible()) {
1596 		width += bar->width() * 4;
1597 	}
1598 	widget->resize(width, 200);
1599 
1600 	// adjust position
1601     QPointF offset;
1602 	bool isAboveCursor = false;
1603 	if (editor->getPositionBelowCursor(offset, widget->width(), widget->height(), isAboveCursor))
1604         widget->move(editor->mapTo(qobject_cast<QWidget *>(parent()), offset.toPoint()));
1605 
1606 	// adjust visible tab bar depending on location relative to cursor
1607 	QTabBar *tbOn = (isAboveCursor) ? tbAbove : tbBelow;
1608 	QTabBar *tbOff = (isAboveCursor) ? tbBelow : tbAbove;
1609 	disconnect(tbOn, SIGNAL(currentChanged(int)), this, SLOT(changeView(int)));
1610 	disconnect(tbOff, SIGNAL(currentChanged(int)), this, SLOT(changeView(int)));
1611 	tbOn->show();
1612 	tbOff->hide();
1613 	tbOn->setCurrentIndex(config->preferedCompletionTab);
1614 	connect(tbOn, SIGNAL(currentChanged(int)), this, SLOT(changeView(int)));
1615 }
1616 
updateAbbreviations()1617 void LatexCompleter::updateAbbreviations()
1618 {
1619 	REQUIRE(config);
1620 	QList<CompletionWord> wordsAbbrev;
1621 	foreach (const Macro &macro, config->userMacros) {
1622 		if (macro.abbrev.isEmpty() || macro.snippet().isEmpty())
1623 			continue;
1624 		//CompletionWord cw(abbr);
1625 		// for compatibility to texmaker ...
1626 		CompletionWord cw(macro.snippet());
1627 		// <!compatibility>
1628 		cw.word = macro.abbrev;
1629 		cw.sortWord = makeSortWord(cw.word);
1630 		cw.setName(macro.abbrev + tr(" (Usertag)"));
1631 		wordsAbbrev << cw;
1632 	}
1633 	listModel->setAbbrevWords(wordsAbbrev);
1634 }
1635 
complete(QEditor * newEditor,const CompletionFlags & flags)1636 void LatexCompleter::complete(QEditor *newEditor, const CompletionFlags &flags)
1637 {
1638 	Q_ASSERT(list);
1639 	Q_ASSERT(listModel);
1640 	Q_ASSERT(completerInputBinding);
1641 
1642     bool alreadyActive=completerInputBinding->isActive(); // called with already open completer
1643 
1644 	forcedRef = flags & CF_FORCE_REF;
1645 	forcedGraphic = flags & CF_FORCE_GRAPHIC;
1646 	forcedCite = flags & CF_FORCE_CITE;
1647 	forcedPackage = flags & CF_FORCE_PACKAGE;
1648 	forcedKeyval = flags & CF_FORCE_KEYVAL;
1649 	forcedSpecialOption = flags & CF_FORCE_SPECIALOPTION;
1650 	forcedLength = flags & CF_FORCE_LENGTH;
1651 	startedFromTriggerKey = !(flags & CF_FORCE_VISIBLE_LIST);
1652 	if (editor != newEditor) {
1653 		if (editor) disconnect(editor, SIGNAL(destroyed()), this, SLOT(editorDestroyed()));
1654 		if (newEditor) connect(newEditor, SIGNAL(destroyed()), this, SLOT(editorDestroyed()));
1655 		editor = newEditor;
1656 	}
1657 	if (!editor) return;
1658 	QDocumentCursor c = editor->cursor();
1659 	if (!c.isValid()) return;
1660 	/*int phId=editor->currentPlaceHolder();
1661 	if(phId>-1 && phId<editor->placeHolderCount()){
1662 		PlaceHolder ph=editor->getPlaceHolder(phId);
1663 		if(ph.cursor.isWithinSelection(c) && !ph.mirrors.isEmpty()){
1664 			editor->removePlaceHolder(phId);
1665 		}
1666 	}*/
1667 	if (editor->cursorMirrorCount() > 0) return; // completer does not handle cursor mirrors. It would just remove them. -> don't use completer
1668 
1669 	if (c.hasSelection()) {
1670 		c.setColumnNumber(qMax(c.columnNumber(), c.anchorColumnNumber()));
1671 		editor->setCursor(c);
1672 	}
1673 
1674 	//disable auto close char while completer is open
1675     if(!alreadyActive)
1676         editorAutoCloseChars = editor->flag(QEditor::AutoCloseChars); // don't change again from open completer (and therin changed flag, see #1347)
1677 
1678     editor->setFlag(QEditor::AutoCloseChars, false);
1679 
1680     completerInputBinding->setMostUsed(config->preferedCompletionTab, true);
1681     bool handled = false;
1682     if (forcedGraphic) {
1683         if (!dirReader) {
1684             dirReader = new directoryReader(this);
1685             connect(dirReader, &directoryReader::directoryLoaded, this, &LatexCompleter::directoryLoaded);
1686             connect(this, SIGNAL(setDirectoryForCompletion(QString)), dirReader, SLOT(readDirectory(QString)));
1687             dirReader->start();
1688         }
1689         QSet<QString> files;
1690         listModel->setBaseWords(files, CT_NORMALTEXT);
1691         listModel->baselist = listModel->wordsText;
1692         handled = true;
1693     }
1694 	if (forcedCite) {
1695 		listModel->baselist = listModel->wordsCitations;
1696 		handled = true;
1697 	}
1698 	if (forcedRef) {
1699 		listModel->baselist = listModel->wordsLabels;
1700 		handled = true;
1701 	}
1702 	if (forcedPackage) {
1703 		listModel->setBaseWords(*packageList, CT_NORMALTEXT);
1704 		listModel->baselist = listModel->wordsText;
1705 		handled = true;
1706 	}
1707 	if (forcedKeyval) {
1708 		listModel->baselist.clear();
1709 		handled = true;
1710 		foreach (const CompletionWord &cw, listModel->keyValLists.value(workingDir)) {
1711 			if (cw.word.startsWith("#")) {
1712 				if (cw.word == "#L") {
1713 					// complete with length
1714 					forcedLength = true;
1715                     forcedKeyval = false;
1716                     handled = false;
1717                     break;
1718                 }
1719                 handled=true;
1720                 break;
1721 			}
1722 			if (cw.word.startsWith('%')) {
1723 				QString specialList = cw.word;
1724 				if (listModel->contextLists.contains(specialList)) {
1725 					listModel->baselist << listModel->contextLists.value(specialList);
1726 					QList<CompletionWord>::iterator middle = listModel->baselist.end() - listModel->contextLists.value(specialList).length();
1727 					std::inplace_merge(listModel->baselist.begin(), middle, listModel->baselist.end());
1728 				}
1729 			} else {
1730 				// nothing special, simply add
1731                 QList<CompletionWord>::iterator it;
1732                 it = std::lower_bound(listModel->baselist.begin(), listModel->baselist.end(), cw);
1733 				listModel->baselist.insert(it, cw); // keep sorting
1734 			}
1735 		}
1736 
1737 
1738 	}
1739 	if (forcedSpecialOption) {
1740 		listModel->baselist = listModel->contextLists.value(workingDir);
1741 
1742 		handled = true;
1743 	}
1744 	if (!handled) {
1745 		if (flags & CF_NORMAL_TEXT) listModel->baselist = listModel->wordsText;
1746 		else listModel->baselist = listModel->wordsCommands;
1747 		listModel->baselist << listModel->wordsAbbrev;
1748 		QList<CompletionWord>::iterator middle = listModel->baselist.end() - listModel->wordsAbbrev.length();
1749 		std::inplace_merge(listModel->baselist.begin(), middle, listModel->baselist.end());
1750 	}
1751 	if ( editor->currentPlaceHolder() >= 0 && editor->currentPlaceHolder() < editor->placeHolderCount() ) {
1752 		PlaceHolder ph = editor->getPlaceHolder(editor->currentPlaceHolder());
1753 		if (ph.mirrors.count() > 0)
1754 			listModel->setEnvironMode(true);
1755 	}
1756 	//qSort(listModel->baselist.begin(),listModel->baselist.end());
1757 	if (c.previousChar() != '\\' || (flags & CF_FORCE_VISIBLE_LIST)) {
1758 		int start = c.columnNumber() - 1;
1759 		if (flags & CF_NORMAL_TEXT) start = 0;
1760 		if (flags & CF_FORCE_GRAPHIC) start = 0;
1761 		QString eow = "~!@#$%^&*()_+{}|:\"<>?,./;[]-= \n\r`+�\t";
1762 		if (flags & CF_FORCE_CITE) {
1763 			// the prohibited chars in bibtex keys are not well documented and differ among bibtex tools
1764 			// this is what JabRef uses (assuming they have a good understanding due to the maturity of the project):
1765 			eow = "\n\r\t #{}\\\"~,^'";
1766 		}
1767 		if (flags & CF_FORCE_GRAPHIC) {
1768 			eow.remove("/");
1769 			eow.remove("\\");
1770 			eow.remove(".");
1771 			eow.remove(":");
1772 			eow.remove("_");
1773 		}
1774 		if (flags & CF_FORCE_PACKAGE) {
1775 			eow.remove("_");
1776             eow.remove("-");
1777 		}
1778 		if (flags == CF_FORCE_VISIBLE_LIST)
1779 			eow.remove("{");
1780 		if (flags & CF_FORCE_REF) eow = "[]{}\\";
1781 		if (flags & CF_FORCE_REFLIST) eow = "[]{}\\,";
1782 		QString lineText = c.line().text();
1783         int i = c.columnNumber() - 1;
1784         if(i>lineText.length()){
1785             i=lineText.length()-1; // avoid crash
1786         }
1787         for (; i >= 0; i--) {
1788 			if ((lineText.at(i) == QChar('\\')) && !(flags & CF_FORCE_GRAPHIC)) {
1789 				start = i;
1790 				break;
1791 			} else if (eow.contains(lineText.at(i))) {
1792 				if (flags & CF_NORMAL_TEXT) start = i + 1;
1793 				if (flags & CF_FORCE_GRAPHIC) start = i + 1;
1794 				if (flags & CF_FORCE_CITE) start = i + 1;
1795 				if (flags & CF_FORCE_REF) start = i + 1;
1796 				if (flags & CF_FORCE_PACKAGE) start = i + 1;
1797 				if (flags & CF_FORCE_KEYVAL) start = i + 1;
1798 				if (flags & CF_FORCE_SPECIALOPTION) start = i + 1;
1799 				break;
1800 			}
1801 		}
1802 		QString path;
1803 		if (flags & CF_FORCE_GRAPHIC) {
1804 			QString fn = lineText.mid(start, c.columnNumber() - start);
1805             int lastIndex = fn.lastIndexOf("/");
1806 			if (lastIndex >= 0)
1807 				start = start + lastIndex + 1;
1808 			if (fn.isEmpty())
1809                 fn = workingDir + "/";
1810 
1811             QFileInfo fi(QDir(workingDir),fn);
1812 			path = fi.absolutePath();
1813 		}
1814 		completerInputBinding->bindTo(editor, this, true, start);
1815 		adjustWidget();
1816 
1817 		if ((flags & CF_FORCE_GRAPHIC) && !path.isEmpty()) {
1818 			emit setDirectoryForCompletion(path);
1819 		}
1820 	} else completerInputBinding->bindTo(editor, this, false, c.columnNumber() - 1);
1821 
1822 	if (completerInputBinding->getMostUsed() == 1 && countWords() == 0) { // if prefered list is empty, take next more extensive one
1823         setTab(0); // typical
1824 		adjustWidget();
1825 	}
1826 	if (completerInputBinding->getMostUsed() && countWords() == 0) {
1827         setTab(3); // all
1828 		adjustWidget();
1829 	}
1830 
1831 	if (completerInputBinding->getMostUsed() == config->preferedCompletionTab)
1832 		completerInputBinding->setMostUsed(config->preferedCompletionTab, false);
1833 
1834 	//line.document()->cursor(0,0).insertText(QString::number(offset.x())+":"+QString::number(offset.y()));
1835 	connect(editor, SIGNAL(cursorPositionChanged()), this, SLOT(cursorPositionChanged()));
1836 
1837 	completerInputBinding->autoOverridenText = (flags & CF_OVERRIDEN_BACKSLASH) ? "\\" : "";
1838 
1839     if (config && config->completeCommonPrefix && alreadyActive) completerInputBinding->completeCommonPrefix(); // only complete common prefix if the completer was visible when called
1840 }
1841 
directoryLoaded(QString,QSet<QString> content)1842 void LatexCompleter::directoryLoaded(QString , QSet<QString> content)
1843 {
1844 	listModel->setBaseWords(content, CT_NORMALTEXT);
1845 	listModel->baselist = listModel->wordsText;
1846 	//setTab(2);
1847     completerInputBinding->setMostUsed(3); // all
1848 	adjustWidget();
1849 }
1850 
acceptTriggerString(const QString & trigger)1851 bool LatexCompleter::acceptTriggerString(const QString &trigger)
1852 {
1853 	return trigger == "\\" && (!config || config->enabled);
1854 }
1855 
setConfig(LatexCompleterConfig * config)1856 void LatexCompleter::setConfig(LatexCompleterConfig *config)
1857 {
1858 	REQUIRE(config);
1859 	this->config = config;
1860 	listModel->setConfig(config);
1861 
1862 	QFont f(QApplication::font());
1863 	f.setPointSize( f.pointSize() * config->tabRelFontSizePercent / 100);
1864 	tbAbove->setFont(f);
1865 	tbBelow->setFont(f);
1866 }
1867 
getConfig() const1868 LatexCompleterConfig *LatexCompleter::getConfig() const
1869 {
1870 	return config;
1871 }
1872 
setPackageList(std::set<QString> * lst)1873 void LatexCompleter::setPackageList(std::set<QString> *lst)
1874 {
1875 	packageList = lst;
1876 }
1877 
countWords()1878 int LatexCompleter::countWords()
1879 {
1880 	return listModel->getWords().count();
1881 }
1882 
setTab(int index)1883 void LatexCompleter::setTab(int index)
1884 {
1885     Q_ASSERT(index >= 0 && index < 4);
1886 	if (tbBelow->isVisible())
1887 		tbBelow->setCurrentIndex(index);
1888 	if (tbAbove->isVisible())
1889 		tbAbove->setCurrentIndex(index);
1890 }
1891 
filterList(QString word,int showMostUsed)1892 void LatexCompleter::filterList(QString word, int showMostUsed)
1893 {
1894 	QString cur = ""; //needed to preserve selection
1895 	if (list->isVisible() && list->currentIndex().isValid())
1896 		cur = list->model()->data(list->currentIndex(), Qt::DisplayRole).toString();
1897 	CodeSnippet::Type type = CodeSnippet::none;
1898 	if (forcedLength)
1899 		type = CodeSnippet::length;
1900 	listModel->filterList(word, showMostUsed, false, type);
1901 	if (cur != "") {
1902 		int p = listModel->getWords().indexOf(cur);
1903 		if (p >= 0) list->setCurrentIndex(list->model()->index(p, 0, QModelIndex()));
1904 	}
1905 }
1906 
acceptChar(QChar c,int pos)1907 bool LatexCompleter::acceptChar(QChar c, int pos)
1908 {
1909 	//always accept alpha numerical characters
1910 	if (((c >= QChar('a')) && (c <= QChar('z'))) ||
1911 	        ((c >= QChar('A')) && (c <= QChar('Z'))) ||
1912 	        ((c >= QChar('0')) && (c <= QChar('9')))) return true;
1913 	if (pos <= 1) return false;
1914 	if (listModel->isNextCharPossible(c))
1915 		return true; //only accept non standard character, if one of the current words contains it
1916 	return false;
1917 }
1918 
cursorPositionChanged()1919 void LatexCompleter::cursorPositionChanged()
1920 {
1921 	if (!completerInputBinding || !completerInputBinding->isActive()) {
1922 		disconnect(editor, SIGNAL(cursorPositionChanged()), this, SLOT(cursorPositionChanged()));
1923 		return;
1924 	}
1925 	completerInputBinding->cursorPositionChanged(editor);
1926 }
1927 
1928 //called when item is clicked, more important: normal (not signal/slot) called when completerbinding change selection
selectionChanged(const QModelIndex & index)1929 void LatexCompleter::selectionChanged(const QModelIndex &index)
1930 {
1931 	// Note on tooltip usage: It is not possible to hide and shortly afterwards reshow a tooltip
1932 	// with the same text (there's some interference between the internal hideTimer of the tooltip and
1933 	// the internal tooltip-reuse when QToolTip::showText() for the same text. As a result, the tooltip
1934 	// does not persist. This is a Qt-Bug.
1935 	// Workaround: we cannot QToolTip::hideText generally and QToolTip::showText as needed. Therefore we have to
1936 	// hide the tooltip in every single exit branch of the function that does not show the tooltip.
1937 	if (!index.isValid() || !isVisible()) {
1938 		QToolTip::hideText();
1939 		return;
1940 	}
1941 	if (config->tooltipPreview && forcedGraphic) { // picture preview even if help is disabled (maybe the same for cite/ref ?)
1942 		QString fn = workingDir + QDir::separator() + listModel->words[index.row()].word;
1943 		QToolTip::hideText();
1944 		emit showImagePreview(fn);
1945 		return;
1946 	}
1947 
1948 	if (index.row() < 0 || index.row() >= listModel->words.size()) {
1949 		QToolTip::hideText();
1950 		return;
1951 	}
1952 	if (config->tooltipPreview && forcedSpecialOption && workingDir == "%color") {
1953 		QToolTip::hideText();
1954 		QString text;
1955 		text = QString("{\\color{%1} \\rule{1cm}{1cm}}").arg(listModel->words[index.row()].word);
1956 		emit showPreview(text);
1957 		return;
1958 	}
1959 	QRegExp wordrx("^\\\\([^ {[*]+|begin\\{[^ {}]+)");
1960 	if (!forcedCite && wordrx.indexIn(listModel->words[index.row()].word) == -1) {
1961 		QToolTip::hideText();
1962 		return;
1963 	}
1964 	QString cmd = wordrx.cap(0);
1965 	QString topic;
1966 	if (config->tooltipPreview && latexParser.possibleCommands["%ref"].contains(cmd)) {
1967 		QString value = listModel->words[index.row()].word;
1968 		int i = value.indexOf("{");
1969 		value.remove(0, i + 1);
1970 		i = value.indexOf("}");
1971 		value = value.left(i);
1972 		LatexDocument *document = qobject_cast<LatexDocument *>(editor->document());
1973 		int cnt = document->countLabels(value);
1974 		topic = "";
1975 		if (cnt == 0) {
1976 			topic = tr("label missing!");
1977 		} else if (cnt > 1) {
1978 			topic = tr("label defined multiple times!");
1979 		} else {
1980 			QMultiHash<QDocumentLineHandle *, int> result = document->getLabels(value);
1981             QDocumentLineHandle *mLine = result.keys().constFirst();
1982 			int l = mLine->document()->indexOf(mLine);
1983 			if (mLine->document() != editor->document()) {
1984 				//LatexDocument *doc=document->parent->findDocument(mLine->document());
1985 				LatexDocument *doc = qobject_cast<LatexDocument *>(mLine->document());
1986 				Q_ASSERT_X(doc, "missing latexdoc", "qdoc is not latex document !");
1987 				if (doc) topic = tr("<p style='white-space:pre'><b>Filename: %1</b>\n").arg(doc->getFileName());
1988 			}
1989 			for (int i = qMax(0, l - 2); i < qMin(mLine->document()->lines(), l + 3); i++) {
1990 				topic += mLine->document()->line(i).text().left(80);
1991 				if (mLine->document()->line(i).text().length() > 80) topic += "...";
1992 				if (i < l + 2) topic += "\n";
1993 			}
1994 		}
1995 	} else if (config->tooltipPreview && (forcedCite || latexParser.possibleCommands["%cite"].contains(cmd))) {
1996 		QToolTip::hideText();
1997 		QString value = listModel->words[index.row()].word;
1998         const QRegularExpression re{"{([^}]+?)}"};
1999         const QRegularExpressionMatch match = re.match(value);
2000         if(match.hasMatch()){
2001             value = match.captured(1);
2002         }
2003         LatexDocument *document = qobject_cast<LatexDocument *>(editor->document());
2004 		if (!bibReader) {
2005 			bibReader = new bibtexReader(this);
2006 			connect(bibReader, SIGNAL(sectionFound(QString)), this, SLOT(bibtexSectionFound(QString)));
2007             connect(this, SIGNAL(searchBibtexSection(QString,QString)), bibReader, SLOT(searchSection(QString,QString)));
2008 			bibReader->start();
2009 		}
2010 		QString file = document->findFileFromBibId(value);
2011 		if (!file.isEmpty())
2012 			emit searchBibtexSection(file, value);
2013 		return;
2014 	} else {
2015 		if (latexReference && config->tooltipHelp)
2016 			topic = latexReference->getTextForTooltip(cmd);
2017 		if (topic.isEmpty()) {
2018 			QToolTip::hideText();
2019 			return;
2020 		}
2021 	}
2022 	showTooltip(topic);
2023 }
2024 
showTooltip(QString text)2025 void LatexCompleter::showTooltip(QString text)
2026 {
2027 	if (!isVisible()) {
2028 		QToolTip::hideText();
2029 		return;
2030 	}
2031 	QModelIndex index = list->currentIndex();
2032 	QRect r = list->visualRect(index);
2033 	QDocumentCursor c = editor->cursor();
2034     qreal lineHeight = c.line().document()->getLineSpacing();
2035 	QPoint pos = list->mapToGlobal(QPoint(list->width(), r.top() - lineHeight));
2036 	showTooltipLimited(pos, text, list->width());
2037 }
2038 
editorDestroyed()2039 void LatexCompleter::editorDestroyed()
2040 {
2041     editor = nullptr;
2042 }
2043 
bibtexSectionFound(QString content)2044 void LatexCompleter::bibtexSectionFound(QString content)
2045 {
2046 	showTooltip(content);
2047 }
2048 
2049 //ends completion (closes the list) and returns true if there was any
close()2050 bool LatexCompleter::close()
2051 {
2052 	if (completerInputBinding->isActive()) {
2053 		completerInputBinding->simpleRestoreAutoOverride();
2054 		completerInputBinding->resetBinding();
2055 		widget->setVisible(false);
2056 		listModel->curWord = "";
2057 		return true;
2058 	} else return false;
2059 }
2060 
setFiles(const QStringList & newFiles)2061 void LatexCompleterConfig::setFiles(const QStringList &newFiles)
2062 {
2063 	files = newFiles;
2064 }
2065 
getLoadedFiles() const2066 const QStringList &LatexCompleterConfig::getLoadedFiles() const
2067 {
2068 	return files;
2069 }
2070 
existValues()2071 bool LatexCompleter::existValues()
2072 {
2073 	return listModel->keyValLists.value(workingDir).size() > 0;
2074 }
2075