1 /***************************************************************************
2  *   copyright       : (C) 2008 by Benito van der Zander                   *
3  *   http://www.xm1math.net/texmaker/                                      *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  ***************************************************************************/
11 
12 #include "latexeditorview.h"
13 #include "latexeditorview_config.h"
14 
15 #include "filedialog.h"
16 #include "latexcompleter.h"
17 #include "latexdocument.h"
18 #include "smallUsefulFunctions.h"
19 #include "spellerutility.h"
20 #include "tablemanipulation.h"
21 
22 #include "qdocumentline.h"
23 #include "qdocumentline_p.h"
24 #include "qdocumentcommand.h"
25 
26 #include "qlinemarksinfocenter.h"
27 #include "qformatfactory.h"
28 #include "qlanguagedefinition.h"
29 #include "qnfadefinition.h"
30 #include "qnfa.h"
31 
32 #include "qcodeedit.h"
33 #include "qeditor.h"
34 #include "qeditorinputbinding.h"
35 #include "qlinemarkpanel.h"
36 #include "qlinenumberpanel.h"
37 #include "qfoldpanel.h"
38 #include "qgotolinepanel.h"
39 #include "qlinechangepanel.h"
40 #include "qstatuspanel.h"
41 #include "qsearchreplacepanel.h"
42 #include "latexrepository.h"
43 
44 #include "latexparser/latexparsing.h"
45 
46 #include "latexcompleter_config.h"
47 
48 #include "scriptengine.h"
49 #include "diffoperations.h"
50 
51 #include "help.h"
52 
53 #include "bidiextender.h"
54 
55 #include <random>
56 
57 //------------------------------Default Input Binding--------------------------------
58 /*!
59  * \brief default keyboard binding for normal operation
60  */
61 class DefaultInputBinding: public QEditorInputBinding
62 {
63 	//  Q_OBJECT not possible because inputbinding is no qobject
64 public:
DefaultInputBinding()65     DefaultInputBinding(): completerConfig(nullptr), editorViewConfig(nullptr), contextMenu(nullptr), isDoubleClick(false) {}
id() const66 	virtual QString id() const
67 	{
68 		return "TXS::DefaultInputBinding";
69 	}
name() const70 	virtual QString name() const
71 	{
72 		return "TXS::DefaultInputBinding";
73 	}
74 
75 	virtual bool keyPressEvent(QKeyEvent *event, QEditor *editor);
76 	virtual void postKeyPressEvent(QKeyEvent *event, QEditor *editor);
77 	virtual bool keyReleaseEvent(QKeyEvent *event, QEditor *editor);
78 	virtual bool mousePressEvent(QMouseEvent *event, QEditor *editor);
79 	virtual bool mouseReleaseEvent(QMouseEvent *event, QEditor *editor);
80 	virtual bool mouseDoubleClickEvent(QMouseEvent *event, QEditor *editor);
81 	virtual bool mouseMoveEvent(QMouseEvent *event, QEditor *editor);
82 	virtual bool contextMenuEvent(QContextMenuEvent *event, QEditor *editor);
83 private:
84 	bool runMacros(QKeyEvent *event, QEditor *editor);
85 	bool autoInsertLRM(QKeyEvent *event, QEditor *editor);
86 	void checkLinkOverlay(QPoint mousePos, Qt::KeyboardModifiers modifiers, QEditor *editor);
87 	friend class LatexEditorView;
88 	const LatexCompleterConfig *completerConfig;
89 	const LatexEditorViewConfig *editorViewConfig;
90 	QList<QAction *> baseActions;
91 
92 	QMenu *contextMenu;
93 	QString lastSpellCheckedWord;
94 
95 	QPoint lastMousePressLeft;
96 	bool isDoubleClick;  // event sequence of a double click: press, release, double click, release - this is true on the second release
97     Qt::KeyboardModifiers modifiersWhenPressed;
98 };
99 
100 static const QString LRMStr = QChar(LRM);
101 
runMacros(QKeyEvent * event,QEditor * editor)102 bool DefaultInputBinding::runMacros(QKeyEvent *event, QEditor *editor)
103 {
104 	Q_ASSERT(completerConfig);
105     QLanguageDefinition *language = editor->document() ? editor->document()->languageDefinition() : nullptr;
106 	QDocumentLine line = editor->cursor().selectionStart().line();
107 	int column = editor->cursor().selectionStart().columnNumber();
108 	QString prev = line.text().mid(0, column) + event->text(); //TODO: optimize
109 	foreach (const Macro &m, completerConfig->userMacros) {
110 		if (!m.isActiveForTrigger(Macro::ST_REGEX)) continue;
111 		if (!m.isActiveForLanguage(language)) continue;
112 		if (!(m.isActiveForFormat(line.getFormatAt(column)) || (column > 0 && m.isActiveForFormat(line.getFormatAt(column - 1))))) continue; //two checks, so it works at beginning and end of an environment
113 		QRegExp &r = const_cast<QRegExp &>(m.triggerRegex); //a const qregexp doesn't exist
114 		if (r.indexIn(prev) != -1) {
115 			QDocumentCursor c = editor->cursor();
116 			bool block = false;
117 			int realMatchLen = r.matchedLength();
118 			if (m.triggerLookBehind) realMatchLen -= r.cap(1).length();
119 			if (c.hasSelection() || realMatchLen > 1)
120 				block = true;
121 			if (block) editor->document()->beginMacro();
122 			if (c.hasSelection()) {
123 				editor->cutBuffer = c.selectedText();
124 				c.removeSelectedText();
125 			}
126 			if (m.triggerRegex.matchedLength() > 1) {
127 				c.movePosition(realMatchLen - 1, QDocumentCursor::PreviousCharacter, QDocumentCursor::KeepAnchor);
128 				c.removeSelectedText();
129                 editor->setCursor(c);
130 			}
131 
132 			LatexEditorView *view = editor->property("latexEditor").value<LatexEditorView *>();
133 			REQUIRE_RET(view, true);
134 			emit view->execMacro(m, MacroExecContext(Macro::ST_REGEX, r.capturedTexts()));
135 			if (block) editor->document()->endMacro();
136 			editor->cutBuffer.clear();
137 			editor->emitCursorPositionChanged(); //prevent rogue parenthesis highlightations
138 			/*			if (editor->languageDefinition())
139 			editor->languageDefinition()->clearMatches(editor->document());
140 			*/
141 			return true;
142 		}
143 	}
144 	return false;
145 }
146 
autoInsertLRM(QKeyEvent * event,QEditor * editor)147 bool DefaultInputBinding::autoInsertLRM(QKeyEvent *event, QEditor *editor)
148 {
149 	const QString &text = event->text();
150 	if (editorViewConfig->autoInsertLRM && text.length() == 1 && editor->cursor().isRTL()) {
151 		if (text.at(0) == '}') {
152 			bool autoOverride = editor->isAutoOverrideText("}");
153 			bool previousIsLRM = editor->cursor().previousChar().unicode() == LRM;
154 			bool block = previousIsLRM || autoOverride;
155 			if (block) editor->document()->beginMacro();
156 			if (previousIsLRM) editor->cursor().deletePreviousChar(); //todo mirrors
157 			if (autoOverride) {
158 				editor->write("}"); //separated, so autooverride works
159 				editor->write(LRMStr);
160 			} else editor->write("}" + LRMStr);
161 			if (block) editor->document()->endMacro();
162 			return true;
163 		}
164 	}
165 	return false;
166 }
167 
checkLinkOverlay(QPoint mousePos,Qt::KeyboardModifiers modifiers,QEditor * editor)168 void DefaultInputBinding::checkLinkOverlay(QPoint mousePos, Qt::KeyboardModifiers modifiers, QEditor *editor)
169 {
170 	if (modifiers == Qt::ControlModifier) {
171 		LatexEditorView *edView = qobject_cast<LatexEditorView *>(editor->parentWidget());
172 		QDocumentCursor cursor = editor->cursorForPosition(mousePos);
173 		edView->checkForLinkOverlay(cursor);
174 	} else {
175 		// reached for example when Ctrl+Shift is pressed
176 		LatexEditorView *edView = qobject_cast<LatexEditorView *>(editor->parentWidget()); //a qobject is necessary to retrieve events
177 		edView->removeLinkOverlay();
178 	}
179 }
180 
keyPressEvent(QKeyEvent * event,QEditor * editor)181 bool DefaultInputBinding::keyPressEvent(QKeyEvent *event, QEditor *editor)
182 {
183 	if (LatexEditorView::completer && LatexEditorView::completer->acceptTriggerString(event->text())
184 	        && (editor->currentPlaceHolder() < 0 || editor->currentPlaceHolder() >= editor->placeHolderCount() || editor->getPlaceHolder(editor->currentPlaceHolder()).mirrors.isEmpty() ||  editor->getPlaceHolder(editor->currentPlaceHolder()).affector != BracketInvertAffector::instance())
185 	        && !editor->flag(QEditor::Overwrite))  {
186 		//update completer if necessary
187 		editor->emitNeedUpdatedCompleter();
188 		bool autoOverriden = editor->isAutoOverrideText(event->text());
189 		if (editorViewConfig->autoInsertLRM && event->text() == "\\" && editor->cursor().isRTL())
190 			editor->write(LRMStr + event->text());
191 		else
192 			editor->write(event->text());
193 		if (autoOverriden) LatexEditorView::completer->complete(editor, LatexCompleter::CF_OVERRIDEN_BACKSLASH);
194 		else {
195 			int flags = Parsing::getCompleterContext(editor->cursor().line().handle(), editor->cursor().columnNumber());
196 			LatexEditorView::completer->complete(editor, LatexCompleter::CompletionFlag(flags));
197 		}
198 		return true;
199 	}
200 	if (!event->text().isEmpty()) {
201 		if (!editor->flag(QEditor::Overwrite) && runMacros(event, editor))
202 			return true;
203 		if (autoInsertLRM(event, editor))
204 			return true;
205 		if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
206 			// if cursor is at the end of a placeholder, remove that placeholder
207 			int phId = editor->currentPlaceHolder();
208 			if (phId >= 0) {
209 				PlaceHolder ph = editor->getPlaceHolder(phId);
210 				if (editor->cursor().lineNumber() == ph.cursor.lineNumber() &&
211 				        editor->cursor().columnNumber() == ph.cursor.columnNumber()) {
212 					editor->removePlaceHolder(phId);
213 					return true;
214 				}
215 			}
216 		}
217 	} else {
218 		if (event->key() == Qt::Key_Control) {
219 			editor->setMouseTracking(true);
220 			QPoint mousePos(editor->mapToFrame(editor->mapFromGlobal(QCursor::pos())));
221 			checkLinkOverlay(mousePos, event->modifiers(), editor);
222 		}
223 	}
224 	if (LatexEditorView::hideTooltipWhenLeavingLine != -1 && editor->cursor().lineNumber() != LatexEditorView::hideTooltipWhenLeavingLine) {
225 		LatexEditorView::hideTooltipWhenLeavingLine = -1;
226 		QToolTip::hideText();
227 	}
228 	return false;
229 }
230 
postKeyPressEvent(QKeyEvent * event,QEditor * editor)231 void DefaultInputBinding::postKeyPressEvent(QKeyEvent *event, QEditor *editor)
232 {
233     QString txt=event->text();
234     if(txt.length()!=1)
235         return;
236     QChar c=txt.at(0);
237     if ( c== ',' || c.isLetter()) {
238 		LatexEditorView *view = editor->property("latexEditor").value<LatexEditorView *>();
239 		Q_ASSERT(view);
240 		if (completerConfig && completerConfig->enabled)
241             view->mayNeedToOpenCompleter(c!=',');
242 	}
243 }
244 
keyReleaseEvent(QKeyEvent * event,QEditor * editor)245 bool DefaultInputBinding::keyReleaseEvent(QKeyEvent *event, QEditor *editor)
246 {
247 	if (event->key() == Qt::Key_Control) {
248 		editor->setMouseTracking(false);
249 		LatexEditorView *edView = qobject_cast<LatexEditorView *>(editor->parentWidget()); //a qobject is necessary to retrieve events
250 		edView->removeLinkOverlay();
251 	}
252 	return false;
253 }
254 
mousePressEvent(QMouseEvent * event,QEditor * editor)255 bool DefaultInputBinding::mousePressEvent(QMouseEvent *event, QEditor *editor)
256 {
257     LatexEditorView *edView = nullptr;
258 
259 	switch (event->button()) {
260 	case Qt::XButton1:
261 		edView = qobject_cast<LatexEditorView *>(editor->parentWidget());
262 		emit edView->mouseBackPressed();
263 		return true;
264 	case Qt::XButton2:
265 		edView = qobject_cast<LatexEditorView *>(editor->parentWidget());
266 		emit edView->mouseForwardPressed();
267 		return true;
268 	case Qt::LeftButton:
269 		edView = qobject_cast<LatexEditorView *>(editor->parentWidget());
270 		emit edView->cursorChangeByMouse();
271 		lastMousePressLeft = event->pos();
272         modifiersWhenPressed = event->modifiers();
273 		return false;
274 	default:
275 		return false;
276 	}
277 }
278 
mouseReleaseEvent(QMouseEvent * event,QEditor * editor)279 bool DefaultInputBinding::mouseReleaseEvent(QMouseEvent *event, QEditor *editor)
280 {
281 	if (isDoubleClick) {
282 		isDoubleClick = false;
283 		return false;
284 	}
285 	isDoubleClick = false;
286 
287     if (event->modifiers() == Qt::ControlModifier && modifiersWhenPressed == event->modifiers() && event->button() == Qt::LeftButton) {
288 		// Ctrl+LeftClick
289 		int distanceSqr = (event->pos().x() - lastMousePressLeft.x()) * (event->pos().x() - lastMousePressLeft.x()) + (event->pos().y() - lastMousePressLeft.y()) * (event->pos().y() - lastMousePressLeft.y());
290 		if (distanceSqr > 4) // allow the user to accidentially move the mouse a bit
291 			return false;
292 
293 		LatexEditorView *edView = qobject_cast<LatexEditorView *>(editor->parentWidget()); //a qobject is necessary to retrieve events
294 		if (!edView) return false;
295 		QDocumentCursor cursor = editor->cursorForPosition(editor->mapToContents(event->pos()));
296 
297 		if (edView->hasLinkOverlay()) {
298 			LinkOverlay lo = edView->getLinkOverlay();
299 			switch (lo.type) {
300 			case LinkOverlay::RefOverlay:
301 				emit edView->gotoDefinition(cursor);
302 				return true;
303 			case LinkOverlay::FileOverlay:
304                 emit edView->openFile(lo.text());
305 				return true;
306 			case LinkOverlay::UrlOverlay:
307 				if (!QDesktopServices::openUrl(lo.text())) {
308 					UtilsUi::txsWarning(LatexEditorView::tr("Could not open url:") + "\n" + lo.text());
309 				}
310 				return true;
311 			case LinkOverlay::UsepackageOverlay:
312 				edView->openPackageDocumentation(lo.text());
313 				return true;
314 			case LinkOverlay::BibFileOverlay:
315                 emit edView->openFile(lo.text(), "bib");
316 				return true;
317 			case LinkOverlay::CiteOverlay:
318 				emit edView->gotoDefinition(cursor);
319 				return true;
320 			case LinkOverlay::CommandOverlay:
321 				emit edView->gotoDefinition(cursor);
322 				return true;
323 			case LinkOverlay::EnvOverlay:
324 				emit edView->gotoDefinition(cursor);
325 				return true;
326 			case LinkOverlay::Invalid:
327 				break;
328 			}
329 		}
330 
331 		if (!editor->languageDefinition()) return false;
332 		if (editor->languageDefinition()->language() != "(La)TeX")
333 			return false;
334 		emit edView->syncPDFRequested(cursor);
335 		return true;
336 	}
337 	return false;
338 }
339 
mouseDoubleClickEvent(QMouseEvent * event,QEditor * editor)340 bool DefaultInputBinding::mouseDoubleClickEvent(QMouseEvent *event, QEditor *editor)
341 {
342 	Q_UNUSED(event)
343 	Q_UNUSED(editor)
344 	isDoubleClick = true;
345 	return false;
346 }
347 
contextMenuEvent(QContextMenuEvent * event,QEditor * editor)348 bool DefaultInputBinding::contextMenuEvent(QContextMenuEvent *event, QEditor *editor)
349 {
350     if (!contextMenu) contextMenu = new QMenu(nullptr);
351 	contextMenu->clear();
352 	contextMenu->setProperty("isSpellingPopulated", QVariant());  // delete information on spelling
353 	QDocumentCursor cursor;
354 	if (event->reason() == QContextMenuEvent::Mouse) cursor = editor->cursorForPosition(editor->mapToContents(event->pos()));
355 	else cursor = editor->cursor();
356 	LatexEditorView *edView = qobject_cast<LatexEditorView *>(editor->parentWidget()); //a qobject is necessary to retrieve events
357 	REQUIRE_RET(edView, false);
358 
359 	// check for context menu on preview picture
360 	QRect pictRect = cursor.line().getCookie(QDocumentLine::PICTURE_COOKIE_DRAWING_POS).toRect();
361 	if (pictRect.isValid()) {
362 		QPoint posInDocCoordinates(event->pos().x() + edView->editor->horizontalOffset(), event->pos().y() + edView->editor->verticalOffset());
363 		if (pictRect.contains(posInDocCoordinates)) {
364 			// get removePreviewAction
365 			// ok, this is not an ideal way of doing it because (i) it has to be in the baseActions (at least we Q_ASSERT this) and (ii) the iteration over the baseActions
366 			// Alternatives: 1) include configmanager and use ConfigManager->getInstance()->getManagedAction() - Disadvantage: additional dependency
367 			//               2) explicitly pass it to the editorView (like the base actions, but separate) - Disadvantage: code overhead
368 			//               3) Improve the concept of base actions:
369 			//                    LatexEditorView::addContextAction(QAction); called when creating the editorView
370 			//                    LatexEditorView::getContextAction(QString); used here to populate the menu
371 			bool removePreviewActionFound = false;
372 			foreach (QAction *act, baseActions) {
373 				if (act->objectName().endsWith("removePreviewLatex")) {
374 					act->setData(posInDocCoordinates);
375 					contextMenu->addAction(act);
376 					removePreviewActionFound = true;
377 					break;
378 				}
379 			}
380 			Q_ASSERT(removePreviewActionFound);
381 
382 
383 			QVariant vPixmap = cursor.line().getCookie(QDocumentLine::PICTURE_COOKIE);
384 			if (vPixmap.isValid()) {
385 				(contextMenu->addAction("Copy Image", edView, SLOT(copyImageFromAction())))->setData(vPixmap);
386 				(contextMenu->addAction("Save Image As...", edView, SLOT(saveImageFromAction())))->setData(vPixmap);
387 			}
388 			contextMenu->exec(event->globalPos());
389 			return true;
390 		}
391 	}
392 
393 	// normal context menu
394 	bool validPosition = cursor.isValid() && cursor.line().isValid();
395 	//LatexParser::ContextType context = LatexParser::Unknown;
396 	QString ctxCommand;
397 	if (validPosition) {
398 		QFormatRange fr;
399 		//spell checking
400 
401 		if (edView->speller) {
402 			int pos;
403 			if (cursor.hasSelection()) pos = (cursor.columnNumber() + cursor.anchorColumnNumber()) / 2;
404 			else pos = cursor.columnNumber();
405 
406 			foreach (const int f, edView->grammarFormats) {
407 				fr = cursor.line().getOverlayAt(pos, f);
408 				if (fr.length > 0 && fr.format == f) {
409 					QVariant var = cursor.line().getCookie(QDocumentLine::GRAMMAR_ERROR_COOKIE);
410 					if (var.isValid()) {
411                         edView->wordSelection=QDocumentCursor(editor->document(), cursor.lineNumber(), fr.offset);
412                         edView->wordSelection.movePosition(fr.length, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
413                         //editor->setCursor(wordSelection);
414 
415 						const QList<GrammarError> &errors = var.value<QList<GrammarError> >();
416 						for (int i = 0; i < errors.size(); i++)
417 							if (errors[i].offset <= cursor.columnNumber() && errors[i].offset + errors[i].length >= cursor.columnNumber()) {
418 								edView->addReplaceActions(contextMenu, errors[i].corrections, true);
419 								break;
420 							}
421 					}
422 				}
423 			}
424 
425 			fr = cursor.line().getOverlayAt(pos, SpellerUtility::spellcheckErrorFormat);
426 			if (fr.length > 0 && fr.format == SpellerUtility::spellcheckErrorFormat) {
427 				QString word = cursor.line().text().mid(fr.offset, fr.length);
428 				if (!(editor->cursor().hasSelection() && editor->cursor().selectedText().length() > 0) || editor->cursor().selectedText() == word
429 				        || editor->cursor().selectedText() == lastSpellCheckedWord) {
430 					lastSpellCheckedWord = word;
431 					word = latexToPlainWord(word);
432                     edView->wordSelection=QDocumentCursor(editor->document(), cursor.lineNumber(), fr.offset);
433                     edView->wordSelection.movePosition(fr.length, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
434                     //editor->setCursor(wordSelection);
435 
436 					if ((editorViewConfig->contextMenuSpellcheckingEntryLocation == 0) ^ (event->modifiers() & editorViewConfig->contextMenuKeyboardModifiers)) {
437 						edView->addSpellingActions(contextMenu, lastSpellCheckedWord, false);
438 						contextMenu->addSeparator();
439 					} else {
440 						QMenu *spellingMenu = contextMenu->addMenu(LatexEditorView::tr("Spelling"));
441 						spellingMenu->setProperty("word", lastSpellCheckedWord);
442 						edView->connect(spellingMenu, SIGNAL(aboutToShow()), edView, SLOT(populateSpellingMenu()));
443 					}
444 				}
445 			}
446 		}
447 		//citation checking
448 		int f = edView->citationMissingFormat;
449 		if (cursor.hasSelection()) fr = cursor.line().getOverlayAt((cursor.columnNumber() + cursor.anchorColumnNumber()) / 2, f);
450 		else fr = cursor.line().getOverlayAt(cursor.columnNumber(), f);
451 		if (fr.length > 0 && fr.format == f) {
452 			QString word = cursor.line().text().mid(fr.offset, fr.length);
453             //editor->setCursor(editor->document()->cursor(cursor.lineNumber(), fr.offset, cursor.lineNumber(), fr.offset + fr.length)); // no need to select word as it is not changed anyway, see also #1034
454 			QAction *act = new QAction(LatexEditorView::tr("New BibTeX Entry %1").arg(word), contextMenu);
455 			edView->connect(act, SIGNAL(triggered()), edView, SLOT(requestCitation()));
456 			contextMenu->addAction(act);
457 			contextMenu->addSeparator();
458 		}
459 		//check input/include
460 		//find context of cursor
461 		QDocumentLineHandle *dlh = cursor.line().handle();
462 		TokenList tl = dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>();
463 		int i = Parsing::getTokenAtCol(tl, cursor.columnNumber());
464 		Token tk;
465 		if (i >= 0)
466 			tk = tl.at(i);
467 
468 		if (tk.type == Token::file) {
469 			QAction *act = new QAction(LatexEditorView::tr("Open %1").arg(tk.getText()), contextMenu);
470 			act->setData(tk.getText());
471 			edView->connect(act, SIGNAL(triggered()), edView, SLOT(openExternalFile()));
472 			contextMenu->addAction(act);
473 		}
474 		// bibliography command
475 		if (tk.type == Token::bibfile) {
476 			QAction *act = new QAction(LatexEditorView::tr("Open Bibliography"), contextMenu);
477 			QString bibFile;
478 			bibFile = tk.getText() + ".bib";
479 			act->setData(bibFile);
480 			edView->connect(act, SIGNAL(triggered()), edView, SLOT(openExternalFile()));
481 			contextMenu->addAction(act);
482 		}
483 		//package help
484 		if (tk.type == Token::package || tk.type == Token::documentclass) {
485 			QAction *act = new QAction(LatexEditorView::tr("Open package documentation"), contextMenu);
486 			QString packageName = tk.getText();
487 			act->setText(act->text().append(QString(" (%1)").arg(packageName)));
488 			act->setData(packageName);
489 			edView->connect(act, SIGNAL(triggered()), edView, SLOT(openPackageDocumentation()));
490 			contextMenu->addAction(act);
491 		}
492 		// help for any "known" command
493 		if (tk.type == Token::command) {
494 			ctxCommand = tk.getText();
495 			QString command = ctxCommand;
496 			if (ctxCommand == "\\begin" || ctxCommand == "\\end")
497 				command = ctxCommand + "{" + Parsing::getArg(tl.mid(i + 1), dlh, 0, ArgumentList::Mandatory) + "}";
498 			QString package = edView->document->parent->findPackageByCommand(command);
499 			package.chop(4);
500 			if (!package.isEmpty()) {
501 				QAction *act = new QAction(LatexEditorView::tr("Open package documentation"), contextMenu);
502 				act->setText(act->text().append(QString(" (%1)").arg(package)));
503 				act->setData(package + "#" + command);
504 				edView->connect(act, SIGNAL(triggered()), edView, SLOT(openPackageDocumentation()));
505 				contextMenu->addAction(act);
506 			}
507 		}
508 		// help for "known" environments
509 		if (tk.type == Token::beginEnv || tk.type == Token::env) {
510 			QString command = "\\begin{" + tk.getText() + "}";
511 			QString package = edView->document->parent->findPackageByCommand(command);
512 			package.chop(4);
513 			if (!package.isEmpty()) {
514 				QAction *act = new QAction(LatexEditorView::tr("Open package documentation"), contextMenu);
515 				act->setText(act->text().append(QString(" (%1)").arg(package)));
516 				act->setData(package + "#" + command);
517 				edView->connect(act, SIGNAL(triggered()), edView, SLOT(openPackageDocumentation()));
518 				contextMenu->addAction(act);
519 			}
520 		}
521 		if (/* tk.type==Tokens::bibRef || TODO: bibliography references not yet handled by token system */ tk.type == Token::labelRef) {
522 			QAction *act = new QAction(LatexEditorView::tr("Go to Definition"), contextMenu);
523 			act->setData(QVariant().fromValue<QDocumentCursor>(cursor));
524 			edView->connect(act, SIGNAL(triggered()), edView, SLOT(emitGotoDefinitionFromAction()));
525 			contextMenu->addAction(act);
526 		}
527 		if (tk.type == Token::label || tk.type == Token::labelRef || tk.type == Token::labelRefList) {
528 			QAction *act = new QAction(LatexEditorView::tr("Find Usages"), contextMenu);
529 			act->setData(tk.getText());
530 			act->setProperty("doc", QVariant::fromValue<LatexDocument *>(edView->document));
531 			edView->connect(act, SIGNAL(triggered()), edView, SLOT(emitFindLabelUsagesFromAction()));
532 			contextMenu->addAction(act);
533 		}
534 		if (tk.type == Token::word) {
535 			QAction *act = new QAction(LatexEditorView::tr("Thesaurus..."), contextMenu);
536 			act->setData(QPoint(cursor.anchorLineNumber(), cursor.anchorColumnNumber()));
537 			edView->connect(act, SIGNAL(triggered()), edView, SLOT(triggeredThesaurus()));
538 			contextMenu->addAction(act);
539 		}
540 
541 		//resolve differences
542 		if (edView) {
543 			QList<int> fids;
544 			fids << edView->deleteFormat << edView->insertFormat << edView->replaceFormat;
545 			foreach (int fid, fids) {
546 				if (cursor.hasSelection()) fr = cursor.line().getOverlayAt((cursor.columnNumber() + cursor.anchorColumnNumber()) / 2, fid);
547 				else fr = cursor.line().getOverlayAt(cursor.columnNumber(), fid);
548 				if (fr.length > 0 ) {
549 					QVariant var = cursor.line().getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
550 					if (var.isValid()) {
551 						DiffList diffList = var.value<DiffList>();
552 						//QString word=cursor.line().text().mid(fr.offset,fr.length);
553 						DiffOp op;
554 						op.start = -1;
555 						foreach (op, diffList) {
556 							if (op.start <= cursor.columnNumber() && op.start + op.length >= cursor.columnNumber()) {
557 								break;
558 							}
559 							op.start = -1;
560 						}
561 						if (op.start >= 0) {
562 							QAction *act = new QAction(LatexEditorView::tr("use yours"), contextMenu);
563 							act->setData(QPoint(cursor.lineNumber(), cursor.columnNumber()));
564 							edView->connect(act, SIGNAL(triggered()), edView, SLOT(emitChangeDiff()));
565 							contextMenu->addAction(act);
566 							act = new QAction(LatexEditorView::tr("use other's"), contextMenu);
567 							act->setData(QPoint(-cursor.lineNumber() - 1, cursor.columnNumber()));
568 							edView->connect(act, SIGNAL(triggered()), edView, SLOT(emitChangeDiff()));
569 							contextMenu->addAction(act);
570 							break;
571 						}
572 					}
573 				}
574 			}
575 		}
576 		contextMenu->addSeparator();
577 	}
578 	contextMenu->addActions(baseActions);
579 	if (validPosition) {
580 		contextMenu->addSeparator();
581 
582 		QAction *act = new QAction(LatexEditorView::tr("Go to PDF"), contextMenu);
583 		act->setData(QVariant().fromValue<QDocumentCursor>(cursor));
584 		edView->connect(act, SIGNAL(triggered()), edView, SLOT(emitSyncPDFFromAction()));
585 		contextMenu->addAction(act);
586 	}
587 
588 
589 	if (event->reason() == QContextMenuEvent::Mouse) contextMenu->exec(event->globalPos());
590 	else {
591         QPointF curPoint = editor->cursor().documentPosition();
592 		curPoint.ry() += editor->document()->getLineSpacing();
593         contextMenu->exec(editor->mapToGlobal(editor->mapFromContents(curPoint.toPoint())));
594 	}
595 	event->accept();
596 
597 	return true;
598 }
599 
mouseMoveEvent(QMouseEvent * event,QEditor * editor)600 bool DefaultInputBinding::mouseMoveEvent(QMouseEvent *event, QEditor *editor)
601 {
602 	checkLinkOverlay(editor->mapToContents(event->pos()), event->modifiers(), editor);
603 	return false;
604 }
605 
606 DefaultInputBinding *defaultInputBinding = new DefaultInputBinding();
607 
608 
609 
610 //----------------------------------LatexEditorView-----------------------------------
611 LatexCompleter *LatexEditorView::completer = nullptr;
612 int LatexEditorView::hideTooltipWhenLeavingLine = -1;
613 
614 //Q_DECLARE_METATYPE(LatexEditorView *)
615 
LatexEditorView(QWidget * parent,LatexEditorViewConfig * aconfig,LatexDocument * doc)616 LatexEditorView::LatexEditorView(QWidget *parent, LatexEditorViewConfig *aconfig, LatexDocument *doc) : QWidget(parent), document(nullptr), latexPackageList(nullptr), spellerManager(nullptr), speller(nullptr), useDefaultSpeller(true), curChangePos(-1), config(aconfig), bibReader(nullptr), help(nullptr)
617 {
618 	Q_ASSERT(config);
619 
620 	QVBoxLayout *mainlay = new QVBoxLayout(this);
621 	mainlay->setSpacing(0);
622 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
623     mainlay->setMargin(0);
624 #else
625     mainlay->setContentsMargins(0,0,0,0);
626 #endif
627 
628 	codeeditor = new QCodeEdit(false, this, doc);
629 	editor = codeeditor->editor();
630 
631 	editor->setProperty("latexEditor", QVariant::fromValue<LatexEditorView *>(this));
632 
633 	lineMarkPanel = new QLineMarkPanel;
634 	lineMarkPanelAction = codeeditor->addPanel(lineMarkPanel, QCodeEdit::West, false);
635 	lineNumberPanel = new QLineNumberPanel;
636     lineNumberPanelAction = codeeditor->addPanel(lineNumberPanel, QCodeEdit::West, false);
637 	QFoldPanel *foldPanel = new QFoldPanel;
638 	lineFoldPanelAction = codeeditor->addPanel(foldPanel, QCodeEdit::West, false);
639 	lineChangePanelAction = codeeditor->addPanel(new QLineChangePanel, QCodeEdit::West, false);
640 
641 	statusPanel = new QStatusPanel;
642 	statusPanel->setFont(QApplication::font());
643 	statusPanelAction = codeeditor->addPanel(statusPanel, QCodeEdit::South, false);
644 
645 	gotoLinePanel = new QGotoLinePanel;
646 	gotoLinePanel->setFont(QApplication::font());
647 	gotoLinePanelAction = codeeditor->addPanel(gotoLinePanel, QCodeEdit::South, false);
648 
649 	searchReplacePanel = new QSearchReplacePanel;
650 	searchReplacePanel->setFont(QApplication::font());
651 	searchReplacePanelAction = codeeditor->addPanel(searchReplacePanel, QCodeEdit::South, false);
652 	searchReplacePanel->hide();
653 	connect(searchReplacePanel, SIGNAL(showExtendedSearch()), this, SIGNAL(showExtendedSearch()));
654 
655 	connect(lineMarkPanel, SIGNAL(lineClicked(int)), this, SLOT(lineMarkClicked(int)));
656     connect(lineMarkPanel, SIGNAL(toolTipRequested(int,int)), this, SLOT(lineMarkToolTip(int,int)));
657     connect(lineMarkPanel, SIGNAL(contextMenuRequested(int,QPoint)), this, SLOT(lineMarkContextMenuRequested(int,QPoint)));
658     connect(foldPanel, SIGNAL(contextMenuRequested(int,QPoint)), this, SLOT(foldContextMenuRequested(int,QPoint)));
659 	connect(editor, SIGNAL(hovered(QPoint)), this, SLOT(mouseHovered(QPoint)));
660 	//connect(editor->document(),SIGNAL(contentsChange(int, int)),this,SLOT(documentContentChanged(int, int)));
661     connect(editor->document(), SIGNAL(lineDeleted(QDocumentLineHandle*,int)), this, SLOT(lineDeleted(QDocumentLineHandle*,int)));
662 
663 	connect(doc, SIGNAL(spellingDictChanged(QString)), this, SLOT(changeSpellingDict(QString)));
664     connect(doc, SIGNAL(bookmarkRemoved(QDocumentLineHandle*)), this, SIGNAL(bookmarkRemoved(QDocumentLineHandle*)));
665     connect(doc, SIGNAL(bookmarkAdded(QDocumentLineHandle*,int)), this, SIGNAL(bookmarkAdded(QDocumentLineHandle*,int)));
666 
667 	//editor->setFlag(QEditor::CursorJumpPastWrap,false);
668 	editor->disableAccentHack(config->hackDisableAccentWorkaround);
669 
670 	editor->setInputBinding(defaultInputBinding);
671 	defaultInputBinding->completerConfig = completer->getConfig();
672 	defaultInputBinding->editorViewConfig = config;
673 	Q_ASSERT(defaultInputBinding->completerConfig);
674 	editor->document()->setLineEndingDirect(QDocument::Local);
675 	mainlay->addWidget(editor);
676 
677 	setFocusProxy(editor);
678 
679 	//containedLabels.setPattern("(\\\\label)\\{(.+)\\}");
680 	//containedReferences.setPattern("(\\\\ref|\\\\pageref)\\{(.+)\\}");
681 	updateSettings();
682 
683 	lp = LatexParser::getInstance();
684 }
685 
~LatexEditorView()686 LatexEditorView::~LatexEditorView()
687 {
688 	delete searchReplacePanel; // to force deletion of m_search before document. Otherwise crashes can come up (linux)
689 	delete codeeditor; //explicit call destructor of codeeditor (although it has a parent, it is no qobject itself, but passed it to editor)
690 
691 	if (bibReader) {
692 		bibReader->quit();
693 		bibReader->wait();
694 	}
695 }
696 
updateReplamentList(const LatexParser & cmds,bool forceUpdate)697 void LatexEditorView::updateReplamentList(const LatexParser &cmds, bool forceUpdate)
698 {
699 	QMap<QString, QString> replacementList;
700 	bool differenceExists = false;
701     foreach (QString elem, cmds.possibleCommands["%replace"]) {
702 		int i = elem.indexOf(" ");
703 		if (i > 0) {
704 			replacementList.insert(elem.left(i), elem.mid(i + 1));
705 			if (mReplacementList.value(elem.left(i)) != elem.mid(i + 1))
706 				differenceExists = true;
707 		}
708 	}
709 	if (differenceExists || replacementList.count() != mReplacementList.count() || forceUpdate) {
710 		mReplacementList = replacementList;
711         document->setReplacementList(mReplacementList);
712         reCheckSyntax(0); //force complete spellcheck
713     }
714 }
715 
716 /*!
717  * \brief Helper class to update the palete of editor/codeeditor
718  * QCodeeditor does not use inheritance from a widget, so any palette automatism is disabled
719  * To counteract this deficiency, codeeditors children (panels) are updated explicitely
720  *
721  * It would probably be better to adapt qcodeeditor to an inheritance based model, but that may take effort and time
722  * \param pal new palette
723  */
updatePalette(const QPalette & pal)724 void LatexEditorView::updatePalette(const QPalette &pal)
725 {
726     editor->setPalette(pal);
727     for(QPanel *p:codeeditor->panels()){
728         p->setPalette(pal);
729     }
730     editor->horizontalScrollBar()->setPalette(pal);
731     editor->verticalScrollBar()->setPalette(pal);
732 }
733 
paste()734 void LatexEditorView::paste()
735 {
736 	if (completer->isVisible()) {
737 		const QMimeData *d = QApplication::clipboard()->mimeData();
738 
739 		if ( d ) {
740 			QString txt;
741 			if ( d->hasFormat("text/plain") )
742 				txt = d->text();
743 			else if ( d->hasFormat("text/html") )
744 				txt = d->html();
745 
746 			if (txt.contains("\n"))
747 				txt.clear();
748 
749 			if (txt.isEmpty()) {
750 				completer->close();
751 				editor->paste();
752 			} else {
753 				completer->insertText(txt);
754 			}
755 		}
756 	} else {
757 		editor->paste();
758 	}
759 }
760 
insertSnippet(QString text)761 void LatexEditorView::insertSnippet(QString text)
762 {
763 	CodeSnippet(text).insert(editor);
764 }
765 
deleteLines(bool toStart,bool toEnd)766 void LatexEditorView::deleteLines(bool toStart, bool toEnd)
767 {
768 	QList<QDocumentCursor> cursors = editor->cursors();
769 	if (cursors.empty()) return;
770 	document->beginMacro();
771 	for (int i=0;i<cursors.size();i++)
772 		cursors[i].removeSelectedText();
773 
774 	int cursorLine = cursors[0].lineNumber();
775 	QMultiMap< int, QDocumentCursor* > map = getSelectedLines(cursors);
776 	QList<int> lines = map.uniqueKeys();
777 	QList<QDocumentCursor> newMirrors;
778 	for (int i=lines.size()-1;i>=0;i--) {
779 		QList<QDocumentCursor*> cursors = map.values(lines[i]);
780 		REQUIRE(cursors.size());
781 		if (toStart && toEnd) cursors[0]->eraseLine();
782 		else {
783 			int len = document->line(lines[i]).length();
784 			int column = toStart ? 0 : len;
785 			foreach (QDocumentCursor* c, cursors)
786 				if (toStart) column = qMax(c->columnNumber(), column);
787 				else column = qMin(c->columnNumber(), column);
788 			QDocumentCursor c = document->cursor(lines[i], column, lines[i], toStart ? 0 : len);
789 			c.removeSelectedText();
790 
791 			if (!toStart || !toEnd){
792 				if (lines[i] == cursorLine) editor->setCursor(c);
793 				else newMirrors << c;
794 			}
795 		}
796 	}
797 	document->endMacro();
798     editor->setCursor(cursors[0]);
799 	if (!toStart || !toEnd)
800 		for (int i=0;i<newMirrors.size();i++)
801 			editor->addCursorMirror(newMirrors[i]); //one cursor / line
802 }
803 
moveLines(int delta)804 void LatexEditorView::moveLines(int delta)
805 {
806 	REQUIRE(delta == -1 || delta == +1);
807 	QList<QDocumentCursor> cursors = editor->cursors();
808 	for (int i=0;i<cursors.length();i++)
809 		cursors[i].setAutoUpdated(false);
810 	QList<QPair<int, int> > blocks = getSelectedLineBlocks();
811 	document->beginMacro();
812 	int i = delta < 0 ? blocks.size() - 1 : 0;
813 	while (i >= 0 && i < blocks.size()) {
814 		//edit
815 		QDocumentCursor edit = document->cursor(blocks[i].first, 0, blocks[i].second);
816 		QString text = edit.selectedText();
817 		edit.removeSelectedText();
818 		edit.eraseLine();
819 		if (delta < 0) {
820 			if (blocks[i].second < document->lineCount())
821 				edit.movePosition(1, QDocumentCursor::PreviousLine);
822 			edit.movePosition(1, QDocumentCursor::StartOfLine);
823 			edit.insertText(text + "\n");
824 		} else {
825 			edit.movePosition(1, QDocumentCursor::EndOfLine);
826 			edit.insertText("\n" + text);
827 		}
828 		i += delta;
829 	}
830 	document->endMacro();
831 	//move cursors
832 	for (int i=0;i<cursors.length();i++) {
833 		cursors[i].setAutoUpdated(true);
834 		if (cursors[i].hasSelection()) {
835 			cursors[i].setAnchorLineNumber(cursors[i].anchorLineNumber() + delta);
836 			cursors[i].setLineNumber(cursors[i].lineNumber() + delta, QDocumentCursor::KeepAnchor);
837 		} else
838 			cursors[i].setLineNumber(cursors[i].lineNumber() + delta);
839 		if (i == 0) editor->setCursor(cursors[i]);
840 		else editor->addCursorMirror(cursors[i]);
841 	}
842 }
843 
getSelectedLineBlocks()844 QList<QPair<int, int> > LatexEditorView::getSelectedLineBlocks()
845 {
846 	QList<QDocumentCursor> cursors = editor->cursors();
847 	QList<int> lines;
848 	//get affected lines
849 	for (int i=0;i<cursors.length();i++) {
850 		if (cursors[i].hasSelection()) {
851 			QDocumentSelection sel = cursors[i].selection();
852 			for (int l=sel.startLine;l<=sel.endLine;l++)
853 				lines << l;
854 		} else lines << cursors[i].lineNumber();
855 	}
856     std::sort(lines.begin(),lines.end());
857 	//merge blocks as speed up and to remove duplicates
858 	QList<QPair<int, int> > result;
859 	int i = 0;
860 	while (i < lines.size()) {
861 		int start = lines[i];
862 		int end = lines[i];
863 		i++;
864 		while ( i >= 0 && i < lines.size() && (lines[i] == end || lines[i] == end + 1)) {
865 			end = lines[i];
866 			i++;
867 		}
868 		result << QPair<int,int> (start, end);
869 	}
870 	return result;
871 }
872 
getSelectedLines(QList<QDocumentCursor> & cursors)873 QMultiMap<int, QDocumentCursor* > LatexEditorView::getSelectedLines(QList<QDocumentCursor>& cursors)
874 {
875 	QMultiMap<int, QDocumentCursor* > map;
876 	for (int i=0;i<cursors.length();i++) {
877 		if (cursors[i].hasSelection()) {
878 			QDocumentSelection sel = cursors[i].selection();
879 			for (int l=sel.startLine;l<=sel.endLine;l++)
880 				map.insert(l, &cursors[i]);
881 		} else map.insert(cursors[i].lineNumber(), &cursors[i]);
882 	}
883 	return map;
884 }
885 
cursorPointerLessThan(QDocumentCursor * c1,QDocumentCursor * c2)886 bool cursorPointerLessThan(QDocumentCursor* c1, QDocumentCursor* c2)
887 {
888 	return c1->columnNumber() < c2->columnNumber();
889 }
890 
alignMirrors()891 void LatexEditorView::alignMirrors()
892 {
893 	QList<QDocumentCursor> cursors = editor->cursors();
894 	QMultiMap<int, QDocumentCursor* > map = getSelectedLines(cursors);
895 	QList<int> lines = map.uniqueKeys();
896 	QList<QList<QDocumentCursor*> > cs;
897 	int colCount = 0;
898 	foreach (int l, lines) {
899 		QList<QDocumentCursor*> row = map.values(l);
900 		colCount = qMax(colCount, row.size());
901         std::sort(row.begin(), row.end(), cursorPointerLessThan);
902 		cs.append(row);
903 	}
904 	document->beginMacro();
905 	for (int col=0;col<colCount;col++) {
906 		int pos = 0;
907 		for (int j=0;j<cs.size();j++)
908 			if (col < cs[j].size())
909 				pos = qMax(pos, cs[j][col]->columnNumber());
910 		for (int j=0;j<cs.size();j++)
911 			if (col < cs[j].size() && pos > cs[j][col]->columnNumber()) {
912 				cs[j][col]->insertText(QString(pos -  cs[j][col]->columnNumber(), ' '));
913 			}
914 	}
915 	document->endMacro();
916 }
917 
checkForLinkOverlay(QDocumentCursor cursor)918 void LatexEditorView::checkForLinkOverlay(QDocumentCursor cursor)
919 {
920 	if (cursor.atBlockEnd()) {
921 		removeLinkOverlay();
922 		return;
923 	}
924 
925 	bool validPosition = cursor.isValid() && cursor.line().isValid();
926 	if (validPosition) {
927 		QDocumentLineHandle *dlh = cursor.line().handle();
928 
929 		Token tk = Parsing::getTokenAtCol(dlh, cursor.columnNumber());
930 
931 		if (tk.type == Token::labelRef || tk.type == Token::labelRefList) {
932 			setLinkOverlay(LinkOverlay(tk, LinkOverlay::RefOverlay));
933 		} else if (tk.type == Token::file) {
934 			setLinkOverlay(LinkOverlay(tk, LinkOverlay::FileOverlay));
935 		} else if (tk.type == Token::url) {
936 			setLinkOverlay(LinkOverlay(tk, LinkOverlay::UrlOverlay));
937 		} else if (tk.type == Token::package) {
938 			setLinkOverlay(LinkOverlay(tk, LinkOverlay::UsepackageOverlay));
939 		} else if (tk.type == Token::bibfile) {
940 			setLinkOverlay(LinkOverlay(tk, LinkOverlay::BibFileOverlay));
941 		} else if (tk.type == Token::bibItem) {
942 			setLinkOverlay(LinkOverlay(tk, LinkOverlay::CiteOverlay));
943 		} else if (tk.type == Token::beginEnv || tk.type == Token::env) {
944 			setLinkOverlay(LinkOverlay(tk, LinkOverlay::EnvOverlay));
945 		} else if (tk.type == Token::commandUnknown) {
946 			setLinkOverlay(LinkOverlay(tk, LinkOverlay::CommandOverlay));
947 		} else if (tk.type == Token::command && tk.getText() != "\\begin" && tk.getText() != "\\end") {
948 			// avoid link overlays on \begin and \end; instead, the user can click the environment name
949 			setLinkOverlay(LinkOverlay(tk, LinkOverlay::CommandOverlay));
950 		} else {
951 			if (linkOverlay.isValid()) removeLinkOverlay();
952 		}
953 	} else {
954 		if (linkOverlay.isValid()) removeLinkOverlay();
955 	}
956 }
957 
setLinkOverlay(const LinkOverlay & overlay)958 void LatexEditorView::setLinkOverlay(const LinkOverlay &overlay)
959 {
960 	if (linkOverlay.isValid()) {
961 		if (overlay == linkOverlay) {
962 			return; // same overlay
963 		} else {
964 			removeLinkOverlay();
965 		}
966 	}
967 
968 	linkOverlay = overlay;
969 	linkOverlay.docLine.addOverlay(linkOverlay.formatRange);
970 	editor->viewport()->update(); // immediately apply the overlay
971 	linkOverlayStoredCursor = editor->viewport()->cursor();
972 	editor->viewport()->setCursor(Qt::PointingHandCursor);
973 }
974 
removeLinkOverlay()975 void LatexEditorView::removeLinkOverlay()
976 {
977 	if (linkOverlay.isValid()) {
978 		linkOverlay.docLine.removeOverlay(linkOverlay.formatRange);
979 		linkOverlay = LinkOverlay();
980 		editor->viewport()->update(); // immediately apply the overlay
981 		editor->viewport()->setCursor(linkOverlayStoredCursor);
982 	}
983 }
984 
isNonTextFormat(int format)985 bool LatexEditorView::isNonTextFormat(int format)
986 {
987 	if (format <= 0) return false;
988 	return format == numbersFormat
989 	       || format == verbatimFormat
990 	       || format == pictureFormat
991 	       || format == pweaveDelimiterFormat
992 	       || format == pweaveBlockFormat
993 	       || format == sweaveDelimiterFormat
994 	       || format == sweaveBlockFormat
995 	       || format == math_DelimiterFormat
996 	       || format == asymptoteBlockFormat;
997 }
998 
selectOptionInLatexArg(QDocumentCursor & cur)999 void LatexEditorView::selectOptionInLatexArg(QDocumentCursor &cur)
1000 {
1001 	QString startDelims = "[{, \t\n";
1002 	int startCol = cur.columnNumber();
1003 	while (!cur.atLineStart() && !startDelims.contains(cur.previousChar())) {
1004 		cur.movePosition(1, QDocumentCursor::PreviousCharacter);
1005 	}
1006 	cur.setColumnNumber(startCol, QDocumentCursor::KeepAnchor);
1007 	QString endDelims = "]}, \t\n";
1008 	while (!cur.atLineEnd() && !endDelims.contains(cur.nextChar())) {
1009 		cur.movePosition(1, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
1010 	}
1011 }
1012 
temporaryHighlight(QDocumentCursor cur)1013 void LatexEditorView::temporaryHighlight(QDocumentCursor cur)
1014 {
1015 	if (!cur.hasSelection()) return;
1016 	REQUIRE(editor->document());
1017 
1018 	QDocumentLine docLine(cur.selectionStart().line());
1019 	if (cur.endLineNumber() != cur.startLineNumber()) {
1020 		// TODO: proper highlighting of selections spanning more than one line. Currently just highlight to the end of the first line:
1021 		cur = cur.selectionStart();
1022 		cur.movePosition(1, QDocumentCursor::EndOfLine, QDocumentCursor::KeepAnchor);
1023 	}
1024 
1025 	QFormatRange highlight(cur.startColumnNumber(), cur.endColumnNumber() - cur.startColumnNumber(), editor->document()->getFormatId("search"));
1026 	docLine.addOverlay(highlight);
1027 	tempHighlightQueue.append(QPair<QDocumentLine, QFormatRange>(docLine, highlight));
1028 	QTimer::singleShot(1000, this, SLOT(removeTemporaryHighlight()));
1029 }
1030 
removeTemporaryHighlight()1031 void LatexEditorView::removeTemporaryHighlight()
1032 {
1033 	if (!tempHighlightQueue.isEmpty()) {
1034 		QDocumentLine docLine(tempHighlightQueue.first().first);
1035 		docLine.removeOverlay(tempHighlightQueue.first().second);
1036 		tempHighlightQueue.removeFirst();
1037 	}
1038 }
1039 
displayLineGrammarErrorsInternal(int lineNr,const QList<GrammarError> & errors)1040 void LatexEditorView::displayLineGrammarErrorsInternal(int lineNr, const QList<GrammarError> &errors)
1041 {
1042 	QDocumentLine line = document->line(lineNr);
1043 	foreach (const int f, grammarFormats)
1044 		line.clearOverlays(f);
1045 	foreach (const GrammarError &error, errors) {
1046 		int f;
1047 		if (error.error == GET_UNKNOWN) f = grammarMistakeFormat;
1048 		else {
1049             int index = static_cast<int>(error.error) - 1;
1050 			REQUIRE(index < grammarFormats.size());
1051 			if (grammarFormatsDisabled[index]) continue;
1052 			f = grammarFormats[index];
1053 		}
1054 		if (config->hideNonTextGrammarErrors && (isNonTextFormat(line.getFormatAt(error.offset)) || isNonTextFormat(line.getFormatAt(error.offset + error.length - 1))))
1055 			continue;
1056 		line.addOverlay(QFormatRange(error.offset, error.length, f));
1057 	}
1058 	//todo: check for width changing like if (changed && ff->format(wordRepetitionFormat).widthChanging()) line.handle()->updateWrapAndNotifyDocument(i);
1059 }
1060 
lineGrammarChecked(LatexDocument * doc,QDocumentLineHandle * lineHandle,int lineNr,const QList<GrammarError> & errors)1061 void LatexEditorView::lineGrammarChecked(LatexDocument *doc, QDocumentLineHandle *lineHandle, int lineNr, const QList<GrammarError> &errors)
1062 {
1063 	if (doc != this->document) return;
1064     lineNr = document->indexOf(lineHandle, lineNr);
1065 	if (lineNr < 0) return; //line already deleted
1066 	displayLineGrammarErrorsInternal(lineNr, errors);
1067 	document->line(lineNr).setCookie(QDocumentLine::GRAMMAR_ERROR_COOKIE, QVariant::fromValue<QList<GrammarError> >(errors));
1068 }
1069 
setGrammarOverlayDisabled(int type,bool newValue)1070 void LatexEditorView::setGrammarOverlayDisabled(int type, bool newValue)
1071 {
1072 	REQUIRE(type >= 0 && type < grammarFormatsDisabled.size());
1073 	if (newValue == grammarFormatsDisabled[type]) return;
1074 	grammarFormatsDisabled[type] = newValue;
1075 }
1076 
updateGrammarOverlays()1077 void LatexEditorView::updateGrammarOverlays()
1078 {
1079 	for (int i = 0; i < document->lineCount(); i++)
1080 		displayLineGrammarErrorsInternal(i, document->line(i).getCookie(QDocumentLine::GRAMMAR_ERROR_COOKIE).value<QList<GrammarError> >());
1081 	editor->viewport()->update();
1082 }
1083 
viewActivated()1084 void LatexEditorView::viewActivated()
1085 {
1086 	if (!LatexEditorView::completer) return;
1087 }
1088 
1089 /*!
1090  * Returns the name to be displayed when a short textual reference to the editor is required
1091  * such as in the tab or in a list of open documents.
1092  * This name is not necessarily unique.
1093  */
displayName() const1094 QString LatexEditorView::displayName() const
1095 {
1096 	return (!editor || editor->fileName().isEmpty() ? tr("untitled") : editor->name());
1097 }
1098 
1099 /*!
1100  * Returns the displayName() with properly escaped ampersands for UI elements
1101  * such as tabs and actions.
1102  */
displayNameForUI() const1103 QString LatexEditorView::displayNameForUI() const
1104 {
1105 	return displayName().replace('&', "&&");
1106 }
1107 
complete(int flags)1108 void LatexEditorView::complete(int flags)
1109 {
1110 	if (!LatexEditorView::completer) return;
1111 	setFocus();
1112 	LatexEditorView::completer->complete(editor, LatexCompleter::CompletionFlags(flags));
1113 }
1114 
jumpChangePositionBackward()1115 void LatexEditorView::jumpChangePositionBackward()
1116 {
1117 	if (changePositions.size() == 0) return;
1118 	for (int i = changePositions.size() - 1; i >= 0; i--)
1119 		if (!changePositions[i].isValid()) {
1120 			changePositions.removeAt(i);
1121 			if (i <= curChangePos) curChangePos--;
1122 		}
1123 	if (curChangePos >= changePositions.size() - 1) curChangePos = changePositions.size() - 1;
1124 	else if (curChangePos >= 0 && curChangePos < changePositions.size() - 1) curChangePos++;
1125 	else if (editor->cursor().line().handle() == changePositions.first().dlh()) curChangePos = 1;
1126 	else curChangePos = 0;
1127 	if (curChangePos >= 0 && curChangePos < changePositions.size())
1128 		editor->setCursorPosition(changePositions[curChangePos].lineNumber(), changePositions[curChangePos].columnNumber());
1129 }
1130 
jumpChangePositionForward()1131 void LatexEditorView::jumpChangePositionForward()
1132 {
1133 	for (int i = changePositions.size() - 1; i >= 0; i--)
1134 		if (!changePositions[i].isValid()) {
1135 			changePositions.removeAt(i);
1136 			if (i <= curChangePos) curChangePos--;
1137 		}
1138 	if (curChangePos > 0) {
1139 		curChangePos--;
1140 		editor->setCursorPosition(changePositions[curChangePos].lineNumber(), changePositions[curChangePos].columnNumber());
1141 	}
1142 }
1143 
jumpToBookmark(int bookmarkNumber)1144 void LatexEditorView::jumpToBookmark(int bookmarkNumber)
1145 {
1146 	int markLine = editor->document()->findNextMark(bookMarkId(bookmarkNumber), editor->cursor().lineNumber(), editor->cursor().lineNumber() - 1);
1147 	if (markLine >= 0) {
1148 		emit saveCurrentCursorToHistoryRequested();
1149 		editor->setCursorPosition(markLine, 0, false);
1150 		editor->ensureCursorVisible(QEditor::NavigationToHeader);
1151 		editor->setFocus();
1152 	}
1153 }
1154 
removeBookmark(QDocumentLineHandle * dlh,int bookmarkNumber)1155 void LatexEditorView::removeBookmark(QDocumentLineHandle *dlh, int bookmarkNumber)
1156 {
1157 	if (!dlh)
1158 		return;
1159 	int rmid = bookMarkId(bookmarkNumber);
1160 	if (hasBookmark(dlh, bookmarkNumber)) {
1161 		document->removeMark(dlh, rmid);
1162         editor->removeMark(dlh,"bookmark");
1163 		emit bookmarkRemoved(dlh);
1164 	}
1165 }
1166 
removeBookmark(int lineNr,int bookmarkNumber)1167 void LatexEditorView::removeBookmark(int lineNr, int bookmarkNumber)
1168 {
1169 	removeBookmark(document->line(lineNr).handle(), bookmarkNumber);
1170 }
1171 
addBookmark(int lineNr,int bookmarkNumber)1172 void LatexEditorView::addBookmark(int lineNr, int bookmarkNumber)
1173 {
1174 	int rmid = bookMarkId(bookmarkNumber);
1175     if (bookmarkNumber >= 0){
1176         int ln=document->findNextMark(rmid);
1177 		document->line(ln).removeMark(rmid);
1178         editor->removeMark(document->line(ln).handle(),"bookmark");
1179     }
1180     if (!document->line(lineNr).hasMark(rmid)){
1181 		document->line(lineNr).addMark(rmid);
1182         editor->addMark(document->line(lineNr).handle(),Qt::darkMagenta,"bookmark");
1183     }
1184 }
1185 
hasBookmark(int lineNr,int bookmarkNumber)1186 bool LatexEditorView::hasBookmark(int lineNr, int bookmarkNumber)
1187 {
1188 	int rmid = bookMarkId(bookmarkNumber);
1189 	return document->line(lineNr).hasMark(rmid);
1190 }
1191 
hasBookmark(QDocumentLineHandle * dlh,int bookmarkNumber)1192 bool LatexEditorView::hasBookmark(QDocumentLineHandle *dlh, int bookmarkNumber)
1193 {
1194 	if (!dlh)
1195 		return false;
1196 	int rmid = bookMarkId(bookmarkNumber);
1197 	QList<int> m_marks = document->marks(dlh);
1198 	return m_marks.contains(rmid);
1199 }
1200 /*!
1201  * \brief check if line has any bookmark (bookmarkid -1 .. 9)
1202  * \param dlh
1203  * \return bookmark number [-1:9] , -2 -> not found
1204  */
hasBookmark(QDocumentLineHandle * dlh)1205 int LatexEditorView::hasBookmark(QDocumentLineHandle *dlh)
1206 {
1207     if (!dlh)
1208         return false;
1209     int rmidLow = bookMarkId(-1);
1210     int rmidUp = bookMarkId(9);
1211     QList<int> m_marks = document->marks(dlh);
1212     for(int id:m_marks){
1213         if(id>=rmidLow && id<=rmidUp){
1214             return id;
1215         }
1216     }
1217     return -2; // none found
1218 }
1219 
1220 
toggleBookmark(int bookmarkNumber,QDocumentLine line)1221 bool LatexEditorView::toggleBookmark(int bookmarkNumber, QDocumentLine line)
1222 {
1223 	if (!line.isValid()) line = editor->cursor().line();
1224 	int rmid = bookMarkId(bookmarkNumber);
1225 	if (line.hasMark(rmid)) {
1226 		line.removeMark(rmid);
1227         editor->removeMark(line.handle(),"bookmark");
1228 		emit bookmarkRemoved(line.handle());
1229 		return false;
1230 	}
1231 	if (bookmarkNumber >= 0) {
1232 		int ln = editor->document()->findNextMark(rmid);
1233 		if (ln >= 0) {
1234 			editor->document()->line(ln).removeMark(rmid);
1235             editor->removeMark(editor->document()->line(ln).handle(),"bookmark");
1236 			emit bookmarkRemoved(editor->document()->line(ln).handle());
1237 		}
1238 	}
1239 	for (int i = -1; i < 10; i++) {
1240 		int rmid = bookMarkId(i);
1241 		if (line.hasMark(rmid)) {
1242 			line.removeMark(rmid);
1243             editor->removeMark(line.handle(),"bookmark");
1244 			emit bookmarkRemoved(line.handle());
1245 		}
1246 	}
1247 	line.addMark(rmid);
1248     editor->addMark(line.handle(),Qt::darkMagenta,"bookmark");
1249 	emit bookmarkAdded(line.handle(), bookmarkNumber);
1250 	return true;
1251 }
1252 
gotoLineHandleAndSearchCommand(const QDocumentLineHandle * dlh,const QSet<QString> & searchFor,const QString & id)1253 bool LatexEditorView::gotoLineHandleAndSearchCommand(const QDocumentLineHandle *dlh, const QSet<QString> &searchFor, const QString &id)
1254 {
1255 	if (!dlh) return false;
1256 	int ln = dlh->document()->indexOf(dlh);
1257 	if (ln < 0) return false;
1258 	QString lineText = dlh->document()->line(ln).text();
1259 	int col = 0;
1260 	foreach (const QString &cmd, searchFor) {
1261 		col = lineText.indexOf(cmd + "{" + id);
1262         if (col < 0) col = lineText.indexOf(QRegularExpression(QRegularExpression::escape(cmd) + "\\[[^\\]{}()\\\\]+\\]\\{" + QRegularExpression::escape(id))); //for \command[options]{id}
1263 		if (col >= 0) {
1264 			col += cmd.length() + 1;
1265 			break;
1266 		}
1267 	}
1268 	//Q_ASSERT(col >= 0);
1269 	bool colFound = (col >= 0);
1270 	if (col < 0) col = 0;
1271 	editor->setCursorPosition(ln, col, false);
1272 	editor->ensureCursorVisible(QEditor::Navigation);
1273 	if (colFound) {
1274 		QDocumentCursor highlightCursor(editor->cursor());
1275 		highlightCursor.movePosition(id.length(), QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
1276 		temporaryHighlight(highlightCursor);
1277 	}
1278 	return true;
1279 }
1280 
gotoLineHandleAndSearchString(const QDocumentLineHandle * dlh,const QString & str)1281 bool LatexEditorView::gotoLineHandleAndSearchString(const QDocumentLineHandle *dlh, const QString &str)
1282 {
1283 	if (!dlh) return false;
1284 	int ln = dlh->document()->indexOf(dlh);
1285 	if (ln < 0) return false;
1286 	QString lineText = dlh->document()->line(ln).text();
1287 	int col = lineText.indexOf(str);
1288 	bool colFound = (col >= 0);
1289 	if (col < 0) col = 0;
1290 	editor->setCursorPosition(ln, col, false);
1291 	editor->ensureCursorVisible(QEditor::Navigation);
1292 	if (colFound) {
1293 		QDocumentCursor highlightCursor(editor->cursor());
1294 		highlightCursor.movePosition(str.length(), QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
1295 		temporaryHighlight(highlightCursor);
1296 	}
1297 	return true;
1298 }
1299 
gotoLineHandleAndSearchLabel(const QDocumentLineHandle * dlh,const QString & label)1300 bool LatexEditorView::gotoLineHandleAndSearchLabel(const QDocumentLineHandle *dlh, const QString &label)
1301 {
1302 	return gotoLineHandleAndSearchCommand(dlh, LatexParser::getInstance().possibleCommands["%label"], label);
1303 }
1304 
gotoLineHandleAndSearchBibItem(const QDocumentLineHandle * dlh,const QString & bibId)1305 bool LatexEditorView::gotoLineHandleAndSearchBibItem(const QDocumentLineHandle *dlh, const QString &bibId)
1306 {
1307 	return gotoLineHandleAndSearchCommand(dlh, LatexParser::getInstance().possibleCommands["%bibitem"], bibId);
1308 }
1309 
1310 //collapse/expand every possible line
foldEverything(bool unFold)1311 void LatexEditorView::foldEverything(bool unFold)
1312 {
1313 	QDocument *doc = editor->document();
1314 	QLanguageDefinition *ld = doc->languageDefinition();
1315 	if (!ld) return;
1316 	QFoldedLineIterator fli = ld->foldedLineIterator(doc, 0);
1317 	for (int i = 0; i < doc->lines(); i++, ++fli)
1318 		if (fli.open) {
1319 			if (unFold) ld->expand(doc, i);
1320 			else ld->collapse(doc, i);
1321 		}
1322 }
1323 
1324 //collapse/expand lines at the top level
foldLevel(bool unFold,int level)1325 void LatexEditorView::foldLevel(bool unFold, int level)
1326 {
1327 	QDocument *doc = editor->document();
1328 	QLanguageDefinition *ld = doc->languageDefinition();
1329 	if (!ld) return;
1330 	for (QFoldedLineIterator fli = ld->foldedLineIterator(doc);
1331 	        fli.line.isValid(); ++fli) {
1332 		if (fli.openParentheses.size() == level && fli.open) {
1333 			if (unFold) ld->expand(doc, fli.lineNr);
1334 			else ld->collapse(doc, fli.lineNr);
1335 		}
1336 	}/*
1337  QDocument* doc = editor->document();
1338  QLanguageDefinition* ld = doc->languageDefinition();
1339  int depth=0;
1340  for (int n = 0; n < doc->lines(); ++n) {
1341   QDocumentLine b=doc->line(n);
1342   if (b.isHidden()) continue;
1343 
1344   int flags = ld->blockFlags(doc, n, depth);
1345   short open = QCE_FOLD_OPEN_COUNT(flags);
1346   short close = QCE_FOLD_CLOSE_COUNT(flags);
1347 
1348   depth -= close;
1349 
1350   if (depth < 0)
1351    depth = 0;
1352 
1353   depth += open;
1354 
1355   if (depth==level) {
1356    if (unFold && (flags & QLanguageDefinition::Collapsed))
1357     ld->expand(doc,n);
1358    else if (!unFold && (flags & QLanguageDefinition::Collapsible))
1359     ld->collapse(doc,n);
1360   }
1361   if (ld->blockFlags(doc, n, depth) & QLanguageDefinition::Collapsed)
1362    depth -= open; // outermost block folded : none of the opening is actually opened
1363  }*/
1364 }
1365 
1366 //Collapse at the first possible point before/at line
foldBlockAt(bool unFold,int line)1367 void LatexEditorView::foldBlockAt(bool unFold, int line)
1368 {
1369 	editor->document()->foldBlockAt(unFold, line);
1370 }
1371 
zoomIn()1372 void LatexEditorView::zoomIn()
1373 {
1374 	editor->zoom(1);
1375 }
1376 
zoomOut()1377 void LatexEditorView::zoomOut()
1378 {
1379 	editor->zoom(-1);
1380 }
1381 
resetZoom()1382 void LatexEditorView::resetZoom()
1383 {
1384 	editor->resetZoom();
1385 }
1386 
cleanBib()1387 void LatexEditorView::cleanBib()
1388 {
1389 	QDocument *doc = editor->document();
1390 	for (int i = doc->lines() - 1; i >= 0; i--) {
1391 		QString trimLine = doc->line(i).text().trimmed();
1392 		if (trimLine.startsWith("OPT") || trimLine.startsWith("ALT"))
1393 			doc->execute(new QDocumentEraseCommand(i, 0, i + 1, 0, doc));
1394 	}
1395 	setFocus();
1396 }
1397 
getBaseActions()1398 QList<QAction *> LatexEditorView::getBaseActions()
1399 {
1400 	if (!defaultInputBinding) return QList<QAction *>();
1401 	return defaultInputBinding->baseActions;
1402 }
1403 
setBaseActions(QList<QAction * > baseActions)1404 void LatexEditorView::setBaseActions(QList<QAction *> baseActions)
1405 {
1406 	if (!defaultInputBinding) return;
1407 	defaultInputBinding->baseActions = baseActions;
1408 }
1409 
setSpellerManager(SpellerManager * manager)1410 void LatexEditorView::setSpellerManager(SpellerManager *manager)
1411 {
1412 	spellerManager = manager;
1413 	connect(spellerManager, SIGNAL(defaultSpellerChanged()), this, SLOT(reloadSpeller()));
1414 }
1415 /*!
1416  * \brief Set new spelling language
1417  * No change if old and new are identical
1418  * The speller engine is forwarded to the syntax checker where the actual online checking is done.
1419  * \param name of the desired language
1420  * \return success of operation
1421  */
setSpeller(const QString & name,bool updateComment)1422 bool LatexEditorView::setSpeller(const QString &name, bool updateComment)
1423 {
1424 	if (!spellerManager) return false;
1425 
1426 	useDefaultSpeller = (name == "<default>");
1427 
1428 	SpellerUtility *su;
1429 	if (spellerManager->hasSpeller(name)) {
1430 		su = spellerManager->getSpeller(name);
1431 		if (!su) return false;
1432 	} else {
1433 		su = spellerManager->getSpeller(spellerManager->defaultSpellerName());
1434 		REQUIRE_RET(su, false);
1435 		useDefaultSpeller = true;
1436 	}
1437 	if (su == speller) return true; // nothing to do
1438 
1439     bool dontRecheck=false;
1440 
1441 	if (speller) {
1442 		disconnect(speller, SIGNAL(aboutToDelete()), this, SLOT(reloadSpeller()));
1443 		disconnect(speller, SIGNAL(ignoredWordAdded(QString)), this, SLOT(spellRemoveMarkers(QString)));
1444     }else {
1445         dontRecheck=true;
1446     }
1447 	speller = su;
1448     if(document){
1449         SpellerUtility::inlineSpellChecking=config->inlineSpellChecking && config->realtimeChecking;
1450         SpellerUtility::hideNonTextSpellingErrors=config->hideNonTextSpellingErrors;
1451         document->setSpeller(speller);
1452     }
1453 	connect(speller, SIGNAL(aboutToDelete()), this, SLOT(reloadSpeller()));
1454 	connect(speller, SIGNAL(ignoredWordAdded(QString)), this, SLOT(spellRemoveMarkers(QString)));
1455 	emit spellerChanged(name);
1456 
1457     if (document && updateComment) {
1458         document->updateMagicComment("spellcheck", speller ? speller->name() : "<none>");
1459     }
1460 
1461 	// force new highlighting
1462     if(!dontRecheck){
1463         document->reCheckSyntax(0, document->lineCount());
1464     }
1465 
1466 	return true;
1467 }
1468 /*!
1469  * \brief reload speller engine with new engine (sender())
1470  */
reloadSpeller()1471 void LatexEditorView::reloadSpeller()
1472 {
1473 	if (useDefaultSpeller) {
1474 		setSpeller("<default>");
1475 		return;
1476 	}
1477 
1478 	SpellerUtility *su = qobject_cast<SpellerUtility *>(sender());
1479 	if (!su) return;
1480 	setSpeller(su->name());
1481 }
1482 
getSpeller()1483 QString LatexEditorView::getSpeller()
1484 {
1485 	if (useDefaultSpeller) return QString("<default>");
1486     if(speller==nullptr) return QString("<none>");
1487 	return speller->name();
1488 }
1489 
setCompleter(LatexCompleter * newCompleter)1490 void LatexEditorView::setCompleter(LatexCompleter *newCompleter)
1491 {
1492 	LatexEditorView::completer = newCompleter;
1493 }
1494 
getCompleter()1495 LatexCompleter *LatexEditorView::getCompleter()
1496 {
1497 	return LatexEditorView::completer;
1498 }
1499 
updatePackageFormats()1500 void LatexEditorView::updatePackageFormats()
1501 {
1502 	for (int i = 0; i < editor->document()->lines(); i++) {
1503 		QList<QFormatRange> li = editor->document()->line(i).getOverlays();
1504 		QString curLineText = editor->document()->line(i).text();
1505 		for (int j = 0; j < li.size(); j++)
1506 			if (li[j].format == packagePresentFormat || li[j].format == packageMissingFormat || li[j].format == packageUndefinedFormat) {
1507 				int newFormat = packageUndefinedFormat;
1508                 if (!latexPackageList->empty()) {
1509                     newFormat = latexPackageList->find(curLineText.mid(li[j].offset, li[j].length)) != latexPackageList->end() ? packagePresentFormat : packageMissingFormat;
1510 				}
1511 				if (newFormat != li[j].format) {
1512 					editor->document()->line(i).removeOverlay(li[j]);
1513 					li[j].format = newFormat;
1514 					editor->document()->line(i).addOverlay(li[j]);
1515 				}
1516 			}
1517 	}
1518 }
1519 
clearLogMarks()1520 void LatexEditorView::clearLogMarks()
1521 {
1522 	setLogMarksVisible(false);
1523 	logEntryToLine.clear();
1524 	logEntryToMarkID.clear();
1525 	lineToLogEntries.clear();
1526 }
1527 
addLogEntry(int logEntryNumber,int lineNumber,int markID)1528 void LatexEditorView::addLogEntry(int logEntryNumber, int lineNumber, int markID)
1529 {
1530 	QDocumentLine l = editor->document()->line(lineNumber);
1531 	lineToLogEntries.insert(l.handle(), logEntryNumber);
1532 	logEntryToLine[logEntryNumber] = l.handle();
1533 	logEntryToMarkID[logEntryNumber] = markID;
1534 }
1535 
setLogMarksVisible(bool visible)1536 void LatexEditorView::setLogMarksVisible(bool visible)
1537 {
1538 	if (visible) {
1539 		foreach (int logEntryNumber, logEntryToMarkID.keys()) {
1540 			int markID = logEntryToMarkID[logEntryNumber];
1541 			if (markID >= 0) {
1542 				QDocumentLine(logEntryToLine[logEntryNumber]).addMark(markID);
1543 			}
1544 		}
1545 	} else {
1546 		int errorMarkID = QLineMarksInfoCenter::instance()->markTypeId("error");
1547 		int warningMarkID = QLineMarksInfoCenter::instance()->markTypeId("warning");
1548 		int badboxMarkID = QLineMarksInfoCenter::instance()->markTypeId("badbox");
1549 		editor->document()->removeMarks(errorMarkID);
1550 		editor->document()->removeMarks(warningMarkID);
1551 		editor->document()->removeMarks(badboxMarkID);
1552 	}
1553 }
1554 
updateCitationFormats()1555 void LatexEditorView::updateCitationFormats()
1556 {
1557 	for (int i = 0; i < editor->document()->lines(); i++) {
1558 		QList<QFormatRange> li = editor->document()->line(i).getOverlays();
1559 		QString curLineText = editor->document()->line(i).text();
1560 		for (int j = 0; j < li.size(); j++)
1561 			if (li[j].format == citationPresentFormat || li[j].format == citationMissingFormat) {
1562 				int newFormat = document->bibIdValid(curLineText.mid(li[j].offset, li[j].length)) ? citationPresentFormat : citationMissingFormat;
1563 				if (newFormat != li[j].format) {
1564 					editor->document()->line(i).removeOverlay(li[j]);
1565 					li[j].format = newFormat;
1566 					editor->document()->line(i).addOverlay(li[j]);
1567 				}
1568 			}
1569 	}
1570 }
1571 
containsBibTeXId(QString id)1572 bool LatexEditorView::containsBibTeXId(QString id)
1573 {
1574 	return document->bibIdValid(id);
1575 }
1576 
bookMarkId(int bookmarkNumber)1577 int LatexEditorView::bookMarkId(int bookmarkNumber)
1578 {
1579 	if (bookmarkNumber == -1) return  QLineMarksInfoCenter::instance()->markTypeId("bookmark"); //unnumbered mark
1580 	else return QLineMarksInfoCenter::instance()->markTypeId("bookmark" + QString::number(bookmarkNumber));
1581 	//return document->bookMarkId(bookmarkNumber);
1582 }
1583 
setLineMarkToolTip(const QString & tooltip)1584 void LatexEditorView::setLineMarkToolTip(const QString &tooltip)
1585 {
1586 	lineMarkPanel->setToolTipForTouchedMark(tooltip);
1587 }
1588 
1589 int LatexEditorView::environmentFormat, LatexEditorView::referencePresentFormat, LatexEditorView::referenceMissingFormat, LatexEditorView::referenceMultipleFormat, LatexEditorView::citationMissingFormat, LatexEditorView::citationPresentFormat, LatexEditorView::structureFormat, LatexEditorView::todoFormat, LatexEditorView::packageMissingFormat, LatexEditorView::packagePresentFormat, LatexEditorView::packageUndefinedFormat,
1590     LatexEditorView::wordRepetitionFormat, LatexEditorView::wordRepetitionLongRangeFormat, LatexEditorView::badWordFormat, LatexEditorView::grammarMistakeFormat, LatexEditorView::grammarMistakeSpecial1Format, LatexEditorView::grammarMistakeSpecial2Format, LatexEditorView::grammarMistakeSpecial3Format, LatexEditorView::grammarMistakeSpecial4Format,
1591     LatexEditorView::numbersFormat, LatexEditorView::verbatimFormat, LatexEditorView::commentFormat, LatexEditorView::pictureFormat, LatexEditorView::math_DelimiterFormat, LatexEditorView::math_KeywordFormat,
1592     LatexEditorView::pweaveDelimiterFormat, LatexEditorView::pweaveBlockFormat, LatexEditorView::sweaveDelimiterFormat, LatexEditorView::sweaveBlockFormat,
1593     LatexEditorView::asymptoteBlockFormat;
1594 int LatexEditorView::syntaxErrorFormat, LatexEditorView::preEditFormat;
1595 int LatexEditorView::deleteFormat, LatexEditorView::insertFormat, LatexEditorView::replaceFormat;
1596 
1597 QList<int> LatexEditorView::grammarFormats;
1598 QVector<bool> LatexEditorView::grammarFormatsDisabled;
1599 QList<int> LatexEditorView::formatsList;
1600 
updateSettings()1601 void LatexEditorView::updateSettings()
1602 {
1603 	lineNumberPanel->setVerboseMode(config->showlinemultiples != 10);
1604 	editor->setFont(QFont(config->fontFamily, config->fontSize));
1605 	editor->setLineWrapping(config->wordwrap > 0);
1606 	editor->setSoftLimitedLineWrapping(config->wordwrap == 2);
1607 	editor->setHardLineWrapping(config->wordwrap > 2);
1608 	if (config->wordwrap > 1) {
1609 		editor->setWrapAfterNumChars(config->lineWidth);
1610 	} else {
1611 		editor->setWrapAfterNumChars(0);
1612 	}
1613 	editor->setFlag(QEditor::AutoIndent, config->autoindent);
1614 	editor->setFlag(QEditor::WeakIndent, config->weakindent);
1615 	editor->setFlag(QEditor::ReplaceIndentTabs, config->replaceIndentTabs);
1616 	editor->setFlag(QEditor::ReplaceTextTabs, config->replaceTextTabs);
1617 	editor->setFlag(QEditor::RemoveTrailing, config->removeTrailingWsOnSave);
1618 	editor->setFlag(QEditor::AllowDragAndDrop, config->allowDragAndDrop);
1619 	editor->setFlag(QEditor::MouseWheelZoom, config->mouseWheelZoom);
1620 	editor->setFlag(QEditor::SmoothScrolling, config->smoothScrolling);
1621 	editor->setFlag(QEditor::VerticalOverScroll, config->verticalOverScroll);
1622 	editor->setFlag(QEditor::AutoInsertLRM, config->autoInsertLRM);
1623 	editor->setFlag(QEditor::BidiVisualColumnMode, config->visualColumnMode);
1624 	editor->setFlag(QEditor::OverwriteOpeningBracketFollowedByPlaceholder, config->overwriteOpeningBracketFollowedByPlaceholder);
1625 	editor->setFlag(QEditor::OverwriteClosingBracketFollowingPlaceholder, config->overwriteClosingBracketFollowingPlaceholder);
1626 	//TODO: parenmatch
1627 	editor->setFlag(QEditor::AutoCloseChars, config->parenComplete);
1628 	editor->setFlag(QEditor::ShowPlaceholders, config->showPlaceholders);
1629 	editor->setDoubleClickSelectionType(config->doubleClickSelectionIncludeLeadingBackslash ? QDocumentCursor::WordOrCommandUnderCursor : QDocumentCursor::WordUnderCursor);
1630 	editor->setTripleClickSelectionType((QList<QDocumentCursor::SelectionType>()
1631 										<< QDocumentCursor::WordUnderCursor
1632 										<< QDocumentCursor::WordOrCommandUnderCursor
1633 										<< QDocumentCursor::ParenthesesInner
1634 										<< QDocumentCursor::ParenthesesOuter
1635 										<< QDocumentCursor::LineUnderCursor).at(qMax(0, qMin(4, config->tripleClickSelectionIndex))));
1636 	editor->setIgnoreExternalChanges(!config->monitorFilesForExternalChanges);
1637 	editor->setSilentReloadOnExternalChanges(config->silentReload);
1638 	editor->setUseQSaveFile(config->useQSaveFile);
1639 	editor->setHidden(false);
1640 	editor->setCursorSurroundingLines(config->cursorSurroundLines);
1641 	editor->setCursorBold(config->boldCursor);
1642 	lineMarkPanelAction->setChecked((config->showlinemultiples != 0) || config->folding || config->showlinestate);
1643 	lineNumberPanelAction->setChecked(config->showlinemultiples != 0);
1644 	lineFoldPanelAction->setChecked(config->folding);
1645 	lineChangePanelAction->setChecked(config->showlinestate);
1646 	statusPanelAction->setChecked(config->showcursorstate);
1647 	editor->setDisplayModifyTime(false);
1648 	searchReplacePanel->setUseLineForSearch(config->useLineForSearch);
1649 	searchReplacePanel->setSearchOnlyInSelection(config->searchOnlyInSelection);
1650 	QDocument::setShowSpaces(config->showWhitespace ? (QDocument::ShowTrailing | QDocument::ShowLeading | QDocument::ShowTabs) : QDocument::ShowNone);
1651 	QDocument::setTabStop(config->tabStop);
1652 	QDocument::setLineSpacingFactor(config->lineSpacingPercent / 100.0);
1653 
1654 	editor->m_preEditFormat = preEditFormat;
1655 
1656 	QDocument::setWorkAround(QDocument::DisableFixedPitchMode, config->hackDisableFixedPitch);
1657 	QDocument::setWorkAround(QDocument::DisableWidthCache, config->hackDisableWidthCache);
1658 	QDocument::setWorkAround(QDocument::DisableLineCache, config->hackDisableLineCache);
1659 	QDocument::setWorkAround(QDocument::QImageCache, config->hackQImageCache);
1660 
1661 	QDocument::setWorkAround(QDocument::ForceQTextLayout, config->hackRenderingMode == 1);
1662 	QDocument::setWorkAround(QDocument::ForceSingleCharacterDrawing, config->hackRenderingMode == 2);
1663 	LatexDocument::syntaxErrorFormat = syntaxErrorFormat;
1664     if (document){
1665 		document->updateSettings();
1666         document->setCenterDocumentInEditor(config->centerDocumentInEditor);
1667     }
1668 }
1669 
updateFormatSettings()1670 void LatexEditorView::updateFormatSettings()
1671 {
1672 	static bool formatsLoaded = false;
1673 	if (!formatsLoaded) {
1674 		REQUIRE(QDocument::defaultFormatScheme());
1675 
1676         const void *formats[] = {
1677             & environmentFormat , "environment" ,
1678             & referenceMultipleFormat , "referenceMultiple" ,
1679             & referencePresentFormat , "referencePresent" ,
1680             & referenceMissingFormat , "referenceMissing" ,
1681             & citationPresentFormat , "citationPresent" ,
1682             & citationMissingFormat , "citationMissing" ,
1683             & packageMissingFormat , "packageMissing" ,
1684             & packagePresentFormat , "packagePresent" ,
1685             & packageUndefinedFormat, "normal",
1686             & syntaxErrorFormat, "latexSyntaxMistake", //TODO: rename all to xFormat, "x"
1687             & structureFormat , "structure" ,
1688             & todoFormat , "commentTodo" ,
1689             & deleteFormat, "diffDelete",
1690             & insertFormat, "diffAdd",
1691             & replaceFormat, "diffReplace",
1692             & wordRepetitionFormat , "wordRepetition" ,
1693             & wordRepetitionLongRangeFormat , "wordRepetitionLongRange" ,
1694             & badWordFormat , "badWord" ,
1695             & grammarMistakeFormat , "grammarMistake" ,
1696             & grammarMistakeSpecial1Format , "grammarMistakeSpecial1" ,
1697             & grammarMistakeSpecial2Format , "grammarMistakeSpecial2" ,
1698             & grammarMistakeSpecial3Format , "grammarMistakeSpecial3" ,
1699             & grammarMistakeSpecial4Format , "grammarMistakeSpecial4" ,
1700             & numbersFormat , "numbers" ,
1701             & verbatimFormat , "verbatim" ,
1702             & commentFormat , "comment" ,
1703             & pictureFormat ,"picture" ,
1704             & pweaveDelimiterFormat , "pweave-delimiter" ,
1705             & pweaveBlockFormat , "pweave-block" ,
1706             & sweaveDelimiterFormat , "sweave-delimiter" ,
1707             & sweaveBlockFormat , "sweave-block" ,
1708             & math_DelimiterFormat , "math-delimiter" ,
1709             & math_KeywordFormat , "math-keyword" ,
1710             & asymptoteBlockFormat , "asymptote:block" ,
1711             & preEditFormat , "preedit" ,
1712             nullptr, nullptr
1713         };
1714 
1715 		const void **temp = formats;
1716 		while (*temp) {
1717 			int *c = (static_cast<int *>(const_cast<void *>(*temp)));
1718             Q_ASSERT(c != nullptr);
1719 			*c = QDocument::defaultFormatScheme()->id(QString(static_cast<const char *>(*(temp + 1))));
1720 			temp += 2;
1721 		}
1722 		//int f=QDocument::formatFactory()->id("citationMissing");
1723 		formatsLoaded = true;
1724 		grammarFormats << wordRepetitionFormat << wordRepetitionLongRangeFormat << badWordFormat << grammarMistakeFormat << grammarMistakeSpecial1Format << grammarMistakeSpecial2Format << grammarMistakeSpecial3Format << grammarMistakeSpecial4Format; //don't change the order, it corresponds to GrammarErrorType
1725 		grammarFormatsDisabled.resize(9);
1726 		grammarFormatsDisabled.fill(false);
1727         formatsList << referencePresentFormat << citationPresentFormat << referenceMissingFormat;
1728 		formatsList << referenceMultipleFormat << citationMissingFormat << packageMissingFormat << packagePresentFormat << packageUndefinedFormat << environmentFormat;
1729 		formatsList << wordRepetitionFormat << structureFormat << todoFormat << insertFormat << deleteFormat << replaceFormat;
1730 		LatexDocument::syntaxErrorFormat = syntaxErrorFormat;
1731 	}
1732 }
1733 
requestCitation()1734 void LatexEditorView::requestCitation()
1735 {
1736 	QString id = editor->cursor().selectedText();
1737 	emit needCitation(id);
1738 }
1739 
openExternalFile()1740 void LatexEditorView::openExternalFile()
1741 {
1742 	QAction *act = qobject_cast<QAction *>(sender());
1743 	QString name = act->data().toString();
1744     name.replace("\\string~",QDir::homePath());
1745 	if (!name.isEmpty())
1746 		emit openFile(name);
1747 }
1748 
openPackageDocumentation(QString package)1749 void LatexEditorView::openPackageDocumentation(QString package)
1750 {
1751 	QString command;
1752 	if (package.isEmpty()) {
1753 		QAction *act = qobject_cast<QAction *>(sender());
1754 		if (!act) return;
1755 		package = act->data().toString();
1756 	}
1757 	if (package.contains("#")) {
1758 		int i = package.indexOf("#");
1759 		command = package.mid(i + 1);
1760 		package = package.left(i);
1761 	}
1762 	// replace some package denominations
1763 	if (package == "latex-document" || package == "latex-dev")
1764 		package = "latex2e";
1765 	if (package == "class-scrartcl,scrreprt,scrbook")
1766 		package = "scrartcl";
1767 	if (package.startsWith("class-"))
1768 		package = package.mid(6);
1769     if (!package.isEmpty() && help) {
1770 		if (config->texdocHelpInInternalViewer) {
1771             QString docfile = help->packageDocFile(package);
1772 			if (docfile.isEmpty())
1773 				return;
1774 			if (docfile.endsWith(".pdf"))
1775 				emit openInternalDocViewer(docfile, command);
1776 			else
1777                 help->viewTexdoc(package); // fallback e.g. for dvi
1778 
1779 		} else {
1780             help->viewTexdoc(package);
1781 		}
1782 	}
1783 }
1784 
emitChangeDiff()1785 void LatexEditorView::emitChangeDiff()
1786 {
1787 	QAction *act = qobject_cast<QAction *>(sender());
1788 	QPoint pt = act->data().toPoint();
1789 	emit changeDiff(pt);
1790 }
1791 
emitGotoDefinitionFromAction()1792 void LatexEditorView::emitGotoDefinitionFromAction()
1793 {
1794 	QDocumentCursor c;
1795 	QAction *act = qobject_cast<QAction *>(sender());
1796 	if (act) {
1797 		c = act->data().value<QDocumentCursor>();
1798 	}
1799 	emit gotoDefinition(c);
1800 }
1801 
emitFindLabelUsagesFromAction()1802 void LatexEditorView::emitFindLabelUsagesFromAction()
1803 {
1804 	QAction *action = qobject_cast<QAction *>(sender());
1805 	if (!action) return;
1806 	QString labelText = action->data().toString();
1807 	LatexDocument *doc = action->property("doc").value<LatexDocument *>();
1808 	emit findLabelUsages(doc, labelText);
1809 }
1810 
emitSyncPDFFromAction()1811 void LatexEditorView::emitSyncPDFFromAction()
1812 {
1813 	QDocumentCursor c;
1814 	QAction *act = qobject_cast<QAction *>(sender());
1815 	if (act) {
1816 		c = act->data().value<QDocumentCursor>();
1817 	}
1818 	emit syncPDFRequested(c);
1819 }
1820 
lineMarkClicked(int line)1821 void LatexEditorView::lineMarkClicked(int line)
1822 {
1823 	QDocumentLine l = editor->document()->line(line);
1824 	if (!l.isValid()) return;
1825 	//remove old mark (when possible)
1826 	for (int i = -1; i < 10; i++)
1827 		if (l.hasMark(bookMarkId(i))) {
1828 			l.removeMark(bookMarkId(i));
1829             editor->removeMark(l.handle(),"bookmark");
1830 			emit bookmarkRemoved(l.handle());
1831 			return;
1832 		}
1833 	// remove error marks
1834 	if (l.hasMark(QLineMarksInfoCenter::instance()->markTypeId("error"))) {
1835 		l.removeMark(QLineMarksInfoCenter::instance()->markTypeId("error"));
1836 		return;
1837 	}
1838 	if (l.hasMark(QLineMarksInfoCenter::instance()->markTypeId("warning"))) {
1839 		l.removeMark(QLineMarksInfoCenter::instance()->markTypeId("warning"));
1840 		return;
1841 	}
1842 	if (l.hasMark(QLineMarksInfoCenter::instance()->markTypeId("badbox"))) {
1843 		l.removeMark(QLineMarksInfoCenter::instance()->markTypeId("badbox"));
1844 		return;
1845 	}
1846 	//add unused mark (1,2 .. 9,0) (when possible)
1847 	for (int i = 1; i <= 10; i++) {
1848 		if (editor->document()->findNextMark(bookMarkId(i % 10)) < 0) {
1849 			l.addMark(bookMarkId(i % 10));
1850             editor->addMark(l.handle(),Qt::darkMagenta,"bookmark");
1851 			emit bookmarkAdded(l.handle(), i);
1852 			return;
1853 		}
1854 	}
1855 	l.addMark(bookMarkId(-1));
1856     editor->addMark(l.handle(),Qt::darkMagenta,"bookmark");
1857 	emit bookmarkAdded(l.handle(), -1);
1858 }
1859 
lineMarkToolTip(int line,int mark)1860 void LatexEditorView::lineMarkToolTip(int line, int mark)
1861 {
1862 	if (line < 0 || line >= editor->document()->lines()) return;
1863 	int errorMarkID = QLineMarksInfoCenter::instance()->markTypeId("error");
1864 	int warningMarkID = QLineMarksInfoCenter::instance()->markTypeId("warning");
1865 	int badboxMarkID = QLineMarksInfoCenter::instance()->markTypeId("badbox");
1866 	if (mark != errorMarkID && mark != warningMarkID && mark != badboxMarkID) return;
1867 	QList<int> errors = lineToLogEntries.values(editor->document()->line(line).handle());
1868 	if (!errors.isEmpty())
1869 		emit showMarkTooltipForLogMessage(errors);
1870 }
1871 
clearOverlays()1872 void LatexEditorView::clearOverlays()
1873 {
1874 	for (int i = 0; i < editor->document()->lineCount(); i++) {
1875 		QDocumentLine line = editor->document()->line(i);
1876 		if (!line.isValid()) continue;
1877 
1878 		//remove all overlays used for latex things, in descending frequency
1879 		line.clearOverlays(SpellerUtility::spellcheckErrorFormat);
1880 		line.clearOverlays(referencePresentFormat);
1881 		line.clearOverlays(citationPresentFormat);
1882 		line.clearOverlays(referenceMissingFormat);
1883 		line.clearOverlays(referenceMultipleFormat);
1884 		line.clearOverlays(citationMissingFormat);
1885 		line.clearOverlays(environmentFormat);
1886 		line.clearOverlays(syntaxErrorFormat);
1887 		line.clearOverlays(structureFormat);
1888 		line.clearOverlays(todoFormat);
1889 		foreach (const int f, grammarFormats)
1890 			line.clearOverlays(f);
1891 	}
1892 }
1893 
1894 /*!
1895 	Will be called from certain events, that should maybe result in opening the
1896 	completer. Since the completer depends on the context, the caller doesn't
1897 	have to be sure that a completer is really necassary. The context is checked
1898 	in this function and an appropriate completer is opened if necessary.
1899 	For example, typing a colon within a citation should start the completer.
1900 	Therefore, typing a colon will trigger this function. It's checked in here
1901 	if the context is a citation.
1902  */
mayNeedToOpenCompleter(bool fromSingleChar)1903 void LatexEditorView::mayNeedToOpenCompleter(bool fromSingleChar)
1904 {
1905 	QDocumentCursor c = editor->cursor();
1906 	QDocumentLineHandle *dlh = c.line().handle();
1907 	if (!dlh)
1908 		return;
1909 	TokenStack ts = Parsing::getContext(dlh, c.columnNumber());
1910 	Token tk;
1911 	if (!ts.isEmpty()) {
1912 		tk = ts.top();
1913         if(fromSingleChar && tk.length!=1){
1914             return; // only open completer on first char
1915         }
1916 		if (tk.type == Token::word && tk.subtype == Token::none && ts.size() > 1) {
1917 			// set brace type
1918 			ts.pop();
1919 			tk = ts.top();
1920 		}
1921     }else{
1922         return; // no context available
1923     }
1924 
1925 	Token::TokenType type = tk.type;
1926 	if (tk.subtype != Token::none)
1927 		type = tk.subtype;
1928 
1929 	QList<Token::TokenType> lst;
1930 	lst << Token::package << Token::keyValArg << Token::keyVal_val << Token::keyVal_key << Token::bibItem << Token::labelRefList;
1931     if(fromSingleChar){
1932         lst << Token::labelRef;
1933     }
1934 	if (lst.contains(type))
1935 		emit openCompleter();
1936     if (ts.isEmpty() || fromSingleChar)
1937 		return;
1938 	ts.pop();
1939 	if (!ts.isEmpty()) { // check next level if 1. check fails (e.g. key vals are set to real value)
1940 		tk = ts.top();
1941 		type = tk.type;
1942 		if (lst.contains(type))
1943 			emit openCompleter();
1944 	}
1945 }
1946 
documentContentChanged(int linenr,int count)1947 void LatexEditorView::documentContentChanged(int linenr, int count)
1948 {
1949 	Q_ASSERT(editor);
1950 	QDocumentLine startline = editor->document()->line(linenr);
1951 	if ((linenr >= 0 || count < editor->document()->lines()) && editor->cursor().isValid() &&
1952 	        !editor->cursor().atLineStart() && editor->cursor().line().text().trimmed().length() > 0 &&
1953 	        startline.isValid()) {
1954 		bool add = false;
1955 		if (changePositions.size() <= 0) add = true;
1956 		else if (curChangePos < 1) {
1957 			if (changePositions.first().dlh() != startline.handle()) add = true;
1958 			else changePositions.first().setColumnNumber(editor->cursor().columnNumber());
1959 		} else if (curChangePos >= changePositions.size() - 1) {
1960 			if (changePositions.last().dlh() != startline.handle()) add = true;
1961 			else changePositions.last().setColumnNumber(editor->cursor().columnNumber());
1962 		}  else if (changePositions[curChangePos].dlh() == startline.handle()) changePositions[curChangePos].setColumnNumber(editor->cursor().columnNumber());
1963 		else if (changePositions[curChangePos + 1].dlh() == startline.handle()) changePositions[curChangePos + 1].setColumnNumber(editor->cursor().columnNumber());
1964 		else add = true;
1965 		if (add) {
1966 			curChangePos = -1;
1967 			changePositions.insert(0, CursorPosition(editor->document()->cursor(linenr, editor->cursor().columnNumber())));
1968 			if (changePositions.size() > 20) changePositions.removeLast();
1969 		}
1970 	}
1971 
1972 	if (autoPreviewCursor.size() > 0) {
1973 		for (int i = 0; i < autoPreviewCursor.size(); i++) {
1974 			const QDocumentCursor &c = autoPreviewCursor[i];
1975 			if (c.lineNumber() >= linenr && c.anchorLineNumber() < linenr + count)
1976 				emit showPreview(c); //may modify autoPreviewCursor
1977 		}
1978 
1979 	}
1980 
1981 
1982 	// checking
1983 	if (!QDocument::defaultFormatScheme()) return;
1984 	const LatexDocument *ldoc = qobject_cast<const LatexDocument *>(editor->document());
1985 	bool latexLikeChecking = ldoc && ldoc->languageIsLatexLike();
1986 	if (latexLikeChecking && config->fullCompilePreview) emit showFullPreview();
1987 	if (!config->realtimeChecking) return; //disable all => implicit disable environment color correction (optimization)
1988 	if (!latexLikeChecking && !config->inlineCheckNonTeXFiles) return;
1989 
1990     if (config->inlineGrammarChecking) {
1991 		QList<LineInfo> changedLines;
1992 		int lookBehind = 0;
1993 		for (; linenr - lookBehind >= 0; lookBehind++)
1994 			if (editor->document()->line(linenr - lookBehind).firstChar() == -1) break;
1995 		if (lookBehind > 0) lookBehind--;
1996 		if (lookBehind > linenr) lookBehind = linenr;
1997 
1998         changedLines.reserve(linenr + count + lookBehind + 1);
1999 
2000 		int truefirst = linenr - lookBehind;
2001 		for (int i = linenr - lookBehind; i < editor->document()->lineCount(); i++) {
2002 			QDocumentLine line = editor->document()->line(i);
2003 			if (!line.isValid()) break;
2004 			LineInfo temp;
2005 			temp.line = line.handle();
2006 			temp.text = line.text();
2007             // blank irrelevant content, i.e. commands, non-text, comments, verbatim
2008             QDocumentLineHandle *dlh = line.handle();
2009             TokenList tl = dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>();
2010             if(tl.isEmpty()){
2011                 // special treatment of in verbatim env, as no tokens are generated
2012                 temp.text.fill(' ',temp.text.length());
2013             }
2014             foreach(const Token &tk,tl){
2015                 if(tk.type==Token::word && (tk.subtype==Token::none||tk.subtype==Token::text))
2016                     continue;
2017                 if(tk.type==Token::punctuation && (tk.subtype==Token::none||tk.subtype==Token::text))
2018                     continue;
2019                 if(tk.type==Token::symbol && (tk.subtype==Token::none||tk.subtype==Token::text))
2020                     continue; // don't blank symbol like '~'
2021                 if(tk.type==Token::braces && tk.subtype==Token::text){
2022                     //remove braces around text argument
2023                     temp.text.replace(tk.start,1,QString(' '));
2024                     temp.text.replace(tk.start+tk.length-1,1,QString(' '));
2025                     continue;
2026                 }
2027                 temp.text.replace(tk.start,tk.length,QString(tk.length,' '));
2028             }
2029 
2030 			changedLines << temp;
2031 			if (line.firstChar() == -1) {
2032                 emit linesChanged(speller ? speller->name() : "<none>", document, changedLines, truefirst);
2033 				truefirst += changedLines.size();
2034 				changedLines.clear();
2035 				if (i >= linenr + count) break;
2036 			}
2037 		}
2038 		if (!changedLines.isEmpty())
2039             emit linesChanged(speller ? speller->name() : "<none>", document, changedLines, truefirst);
2040 
2041     }
2042 
2043     for (int i = linenr; i < linenr + count; i++) {
2044 		QDocumentLine line = editor->document()->line(i);
2045         if (!line.isValid()) continue;
2046 
2047         //remove all overlays used for latex things, in descending frequency
2048         line.clearOverlays(formatsList); //faster as it avoids multiple lock/unlock operations
2049 
2050 		bool addedOverlaySpellCheckError = false;
2051 		bool addedOverlayReference = false;
2052 		bool addedOverlayCitation = false;
2053 		bool addedOverlayEnvironment = false;
2054 		bool addedOverlayStructure = false;
2055 		bool addedOverlayTodo = false;
2056 		bool addedOverlayPackage = false;
2057 
2058 		// diff presentation
2059 		QVariant cookie = line.getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
2060 		if (!cookie.isNull()) {
2061 			DiffList diffList = cookie.value<DiffList>();
2062 			for (int i = 0; i < diffList.size(); i++) {
2063 				DiffOp op = diffList.at(i);
2064 				switch (op.type) {
2065 				case DiffOp::Delete:
2066 					line.addOverlay(QFormatRange(op.start, op.length, deleteFormat));
2067 					break;
2068 				case DiffOp::Insert:
2069 					line.addOverlay(QFormatRange(op.start, op.length, insertFormat));
2070 					break;
2071 				case DiffOp::Replace:
2072 					line.addOverlay(QFormatRange(op.start, op.length, replaceFormat));
2073 					break;
2074 				default:
2075 					;
2076 				}
2077 
2078 			}
2079 		}
2080 
2081         QDocumentLineHandle *dlh = line.handle();
2082         // handle % TODO
2083         QPair<int,int> commentStart = dlh->getCookieLocked(QDocumentLine::LEXER_COMMENTSTART_COOKIE).value<QPair<int,int> >();
2084         if(commentStart.second==Token::todoComment){
2085             int col=commentStart.first;
2086             QString curLine=line.text();
2087             QString text = curLine.mid(col);
2088             QString regularExpression=ConfigManagerInterface::getInstance()->getOption("Editor/todo comment regExp").toString();
2089             QRegExp rx(regularExpression);
2090             if (rx.indexIn(text)==0) {
2091                 line.addOverlay(QFormatRange(col, text.length(), todoFormat));
2092                 addedOverlayTodo = true;
2093             }
2094         }
2095 
2096 		// alternative context detection
2097         TokenList tl = dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>();
2098 		for (int tkNr = 0; tkNr < tl.length(); tkNr++) {
2099 			Token tk = tl.at(tkNr);
2100 			if (tk.subtype == Token::verbatim)
2101 				continue;
2102 			if (tk.type == Token::comment)
2103 				break;
2104 			if (latexLikeChecking) {
2105                 if ((tk.subtype == Token::title || tk.subtype == Token::shorttitle) && (tk.type == Token::braces || tk.type == Token::openBrace)) {
2106 					line.addOverlay(QFormatRange(tk.innerStart(), tk.innerLength(), structureFormat));
2107 					addedOverlayStructure = true;
2108 				}
2109 				if (tk.subtype == Token::todo && (tk.type == Token::braces || tk.type == Token::openBrace)) {
2110 					line.addOverlay(QFormatRange(tk.innerStart(), tk.innerLength(), todoFormat));
2111 					addedOverlayTodo = true;
2112 				}
2113 				if (tk.type == Token::env || tk.type == Token::beginEnv) {
2114 					line.addOverlay(QFormatRange(tk.start, tk.length, environmentFormat));
2115 					addedOverlayEnvironment = true;
2116 				}
2117 				if ((tk.type == Token::package || tk.type == Token::beamertheme || tk.type == Token::documentclass) && config->inlinePackageChecking) {
2118 					// package
2119 					QString preambel;
2120 					if (tk.type == Token::beamertheme) { // special treatment for  \usetheme
2121 						preambel = "beamertheme";
2122 					}
2123 					QString text = dlh->text();
2124 					QString rpck =  trimLeft(text.mid(tk.start, tk.length)); // left spaces are ignored by \cite, right space not
2125 					//check and highlight
2126                     if (latexPackageList->empty())
2127 						dlh->addOverlay(QFormatRange(tk.start, tk.length, packageUndefinedFormat));
2128                     else if (latexPackageList->find(preambel + rpck) != latexPackageList->end())
2129 						dlh->addOverlay(QFormatRange(tk.start, tk.length, packagePresentFormat));
2130 					else
2131 						dlh->addOverlay(QFormatRange(tk.start, tk.length, packageMissingFormat));
2132 
2133 					addedOverlayPackage = true;
2134 				}
2135 				if ((tk.type == Token::labelRef || tk.type == Token::labelRefList) && config->inlineReferenceChecking) {
2136 					QDocumentLineHandle *dlh = tk.dlh;
2137 					QString ref = dlh->text().mid(tk.start, tk.length);
2138 					if (ref.contains('#')) continue;  // don't highlight refs in definitions e.g. in \newcommand*{\FigRef}[1]{figure~\ref{#1}}
2139 					int cnt = document->countLabels(ref);
2140 					if (cnt > 1) {
2141 						dlh->addOverlay(QFormatRange(tk.start, tk.length, referenceMultipleFormat));
2142 					} else if (cnt == 1) dlh->addOverlay(QFormatRange(tk.start, tk.length, referencePresentFormat));
2143 					else dlh->addOverlay(QFormatRange(tk.start, tk.length, referenceMissingFormat));
2144 					addedOverlayReference = true;
2145 				}
2146 				if (tk.type == Token::label && config->inlineReferenceChecking) {
2147 					QDocumentLineHandle *dlh = tk.dlh;
2148 					QString ref = dlh->text().mid(tk.start, tk.length);
2149 					int cnt = document->countLabels(ref);
2150 					if (cnt > 1) {
2151 						dlh->addOverlay(QFormatRange(tk.start, tk.length, referenceMultipleFormat));
2152 					} else dlh->addOverlay(QFormatRange(tk.start, tk.length, referencePresentFormat));
2153 					// look for corresponding reeferences and adapt format respectively
2154 					addedOverlayReference = true;
2155 				}
2156 				if (tk.type == Token::bibItem && config->inlineCitationChecking) {
2157 					QDocumentLineHandle *dlh = tk.dlh;
2158 					QString text = dlh->text().mid(tk.start, tk.length);
2159 					if (text.contains('#')) continue; // don't highlight cite in definitions e.g. in \newcommand*{\MyCite}[1]{see~\cite{#1}}
2160 					QString rcit =  trimLeft(text); // left spaces are ignored by \cite, right space not
2161 					//check and highlight
2162 					if (document->bibIdValid(rcit))
2163 						dlh->addOverlay(QFormatRange(tk.start, tk.length, citationPresentFormat));
2164 					else
2165 						dlh->addOverlay(QFormatRange(tk.start, tk.length, citationMissingFormat));
2166 					addedOverlayCitation = true;
2167 				}
2168 			}// if latexLineCheking
2169 		} // for Tokenslist
2170 
2171 		//update wrapping if the an overlay changed the width of the text
2172 		//TODO: should be handled by qce to be consistent (with syntax check and search)
2173 		if (!editor->document()->getFixedPitch() && editor->flag(QEditor::LineWrap)) {
2174 			bool updateWrapping = false;
2175 			QFormatScheme *ff = QDocument::defaultFormatScheme();
2176 			REQUIRE(ff);
2177 			updateWrapping |= addedOverlaySpellCheckError && ff->format(SpellerUtility::spellcheckErrorFormat).widthChanging();
2178 			updateWrapping |= addedOverlayReference && (ff->format(referenceMissingFormat).widthChanging() || ff->format(referencePresentFormat).widthChanging() || ff->format(referenceMultipleFormat).widthChanging());
2179 			updateWrapping |= addedOverlayCitation && (ff->format(citationPresentFormat).widthChanging() || ff->format(citationMissingFormat).widthChanging());
2180 			updateWrapping |= addedOverlayPackage && (ff->format(packagePresentFormat).widthChanging() || ff->format(packageMissingFormat).widthChanging());
2181 			updateWrapping |= addedOverlayEnvironment && ff->format(environmentFormat).widthChanging();
2182 			updateWrapping |= addedOverlayStructure && ff->format(structureFormat).widthChanging();
2183 			updateWrapping |= addedOverlayTodo && ff->format(todoFormat).widthChanging();
2184 			if (updateWrapping)
2185 				line.handle()->updateWrapAndNotifyDocument(i);
2186 
2187 		}
2188 	}
2189     editor->document()->markViewDirty();
2190 }
2191 /*!
2192  * \brief Force rechecking of syntax/spelling
2193  * \param linenr Starting line
2194  * \param count Number of lines, -1 -> until end
2195  */
reCheckSyntax(int linenr,int count)2196 void LatexEditorView::reCheckSyntax(int linenr, int count)
2197 {
2198     document->reCheckSyntax(linenr,count);
2199 }
2200 
lineDeleted(QDocumentLineHandle * l,int)2201 void LatexEditorView::lineDeleted(QDocumentLineHandle *l,int)
2202 {
2203     QMultiHash<QDocumentLineHandle *, int>::iterator it;
2204 	while ((it = lineToLogEntries.find(l)) != lineToLogEntries.end()) {
2205 		logEntryToLine.remove(it.value());
2206 		lineToLogEntries.erase(it);
2207 	}
2208 
2209 	for (int i = changePositions.size() - 1; i >= 0; i--)
2210 		if (changePositions[i].dlh() == l)
2211 			changePositions.removeAt(i);
2212 
2213 	emit lineHandleDeleted(l);
2214 	editor->document()->markViewDirty();
2215 }
textReplaceFromAction()2216 void LatexEditorView::textReplaceFromAction()
2217 {
2218 	QAction *action = qobject_cast<QAction *>(QObject::sender());
2219 	if (editor && action) {
2220 		QString replacementText = action->data().toString();
2221         editor->setCursor(wordSelection);
2222 		if (replacementText.isEmpty()) editor->cursor().removeSelectedText();
2223 		else editor->write(replacementText);
2224 		editor->setCursor(editor->cursor()); //to remove selection range
2225         wordSelection=QDocumentCursor();
2226 	}
2227 }
2228 
spellCheckingAlwaysIgnore()2229 void LatexEditorView::spellCheckingAlwaysIgnore()
2230 {
2231     if (speller && editor && wordSelection.selectedText() == defaultInputBinding->lastSpellCheckedWord) {
2232         QString newToIgnore = wordSelection.selectedText();
2233         speller->addToIgnoreList(newToIgnore);
2234     }
2235 }
2236 
addReplaceActions(QMenu * menu,const QStringList & replacements,bool italic)2237 void LatexEditorView::addReplaceActions(QMenu *menu, const QStringList &replacements, bool italic)
2238 {
2239 	if (!menu) return;
2240     QAction *before = nullptr;
2241     if (!menu->actions().isEmpty()) before = menu->actions().constFirst();
2242 
2243 	foreach (const QString &text, replacements) {
2244 		QAction *replaceAction = new QAction(this);
2245 		if (text.isEmpty()) {
2246 			replaceAction->setText(tr("Delete"));
2247 			QFont deleteFont;
2248 			deleteFont.setItalic(italic);
2249 			replaceAction->setFont(deleteFont);
2250 		} else {
2251 			replaceAction->setText(text);
2252 			QFont correctionFont;
2253 			correctionFont.setBold(true);
2254 			correctionFont.setItalic(italic);
2255 			replaceAction->setFont(correctionFont);
2256 		}
2257 		replaceAction->setData(text);
2258 		connect(replaceAction, SIGNAL(triggered()), this, SLOT(textReplaceFromAction()));
2259 		menu->insertAction(before, replaceAction);
2260 	}
2261 }
2262 
populateSpellingMenu()2263 void LatexEditorView::populateSpellingMenu()
2264 {
2265 	QMenu *menu = qobject_cast<QMenu *>(sender());
2266 	if (!menu) return;
2267 	QString word = menu->property("word").toString();
2268 	if (word.isEmpty()) return;
2269 	addSpellingActions(menu, word, true);
2270 }
2271 
addSpellingActions(QMenu * menu,QString word,bool dedicatedMenu)2272 void LatexEditorView::addSpellingActions(QMenu *menu, QString word, bool dedicatedMenu)
2273 {
2274 	if (menu->property("isSpellingPopulated").toBool()) return;
2275 
2276 	QStringList suggestions = speller->suggest(word);
2277 	addReplaceActions(menu, suggestions, false);
2278 
2279 	QAction *act = new QAction(LatexEditorView::tr("Add to Dictionary"), menu);
2280 	connect(act, SIGNAL(triggered()), this, SLOT(spellCheckingAlwaysIgnore()));
2281 	if (dedicatedMenu) {
2282 		menu->addSeparator();
2283 	} else {
2284 		QFont ignoreFont;
2285 		ignoreFont.setItalic(true);
2286 		act->setFont(ignoreFont);
2287 	}
2288 	menu->addAction(act);
2289 	menu->setProperty("isSpellingPopulated", true);
2290 }
2291 
spellRemoveMarkers(const QString & newIgnoredWord)2292 void LatexEditorView::spellRemoveMarkers(const QString &newIgnoredWord)
2293 {
2294 	REQUIRE(editor);
2295 	QDocument* doc = editor->document();
2296 	if (!doc) return;
2297     QString newUpperIgnoredWord=newIgnoredWord; //remove upper letter start as well
2298     if(!newUpperIgnoredWord.isEmpty()){
2299         newUpperIgnoredWord[0]=newUpperIgnoredWord[0].toUpper();
2300     }
2301 	//documentContentChanged(editor->cursor().lineNumber(),1);
2302 	for (int i = 0; i < doc->lines(); i++) {
2303 		QList<QFormatRange> li = doc->line(i).getOverlays(SpellerUtility::spellcheckErrorFormat);
2304 		QString curLineText = doc->line(i).text();
2305 		for (int j = 0; j < li.size(); j++)
2306             if (latexToPlainWord(curLineText.mid(li[j].offset, li[j].length)) == newIgnoredWord || latexToPlainWord(curLineText.mid(li[j].offset, li[j].length)) == newUpperIgnoredWord) {
2307 				doc->line(i).removeOverlay(li[j]);
2308 				doc->line(i).setFlag(QDocumentLine::LayoutDirty, true);
2309 			}
2310 	}
2311 	editor->viewport()->update();
2312 }
2313 
closeCompleter()2314 void LatexEditorView::closeCompleter()
2315 {
2316 	completer->close();
2317 }
2318 
2319 /*
2320  * Extracts the math formula at the given cursor position including math delimiters.
2321  * Current limitations: the cursor needs to be on one of the delimiters. This does
2322  * not work for math environments
2323  * Returns an empty string if there is no math formula.
2324  */
extractMath(QDocumentCursor cursor)2325 QString LatexEditorView::extractMath(QDocumentCursor cursor)
2326 {
2327 	if (cursor.line().getFormatAt(cursor.columnNumber()) != math_DelimiterFormat)
2328 		return QString();
2329 	int col = cursor.columnNumber();
2330 	while (col > 0 && cursor.line().getFormatAt(col - 1) == math_DelimiterFormat) col--;
2331 	cursor.setColumnNumber(col);
2332 	return parenthizedTextSelection(cursor).selectedText();
2333 }
2334 
moveToCommandStart(QDocumentCursor & cursor,QString commandPrefix)2335 bool LatexEditorView::moveToCommandStart (QDocumentCursor &cursor, QString commandPrefix)
2336 {
2337 	QString line = cursor.line().text();
2338 	int lastOffset = cursor.columnNumber();
2339 	if (lastOffset >= line.length()) {
2340 		lastOffset = -1;
2341 	}
2342 	int foundOffset = line.lastIndexOf(commandPrefix, lastOffset);
2343 	if (foundOffset == -1) {
2344 		return false;
2345 	}
2346 	cursor.moveTo(cursor.lineNumber(), foundOffset);
2347 	return true;
2348 }
2349 
findEnclosedMathText(QDocumentCursor cursor,QString command)2350 QString LatexEditorView::findEnclosedMathText(QDocumentCursor cursor, QString command){
2351     QString text;
2352     QFormatRange fr = cursor.line().getOverlayAt(cursor.columnNumber(), numbersFormat);
2353     if(fr.isValid()){
2354         // end found
2355         // test if start is in the same line
2356         if(fr.offset>0){
2357             // yes
2358             text=cursor.line().text().mid(fr.offset,fr.length);
2359         }else{
2360             //start in previous lines
2361             StackEnvironment env;
2362             document->getEnv(cursor.lineNumber(), env);
2363             if(!env.isEmpty() && env.top().name=="math"){
2364                 cursor.moveTo(document->indexOf(env.top().dlh,cursor.lineNumber()),env.top().startingColumn,QDocumentCursor::KeepAnchor);
2365                 text=cursor.selectedText();
2366             }
2367         }
2368     }else{
2369         // try again at end of command ($/$$)
2370         fr = cursor.line().getOverlayAt(cursor.columnNumber()+command.length(), numbersFormat);
2371         if(fr.isValid()){
2372             if(fr.offset+fr.length<cursor.line().length()){
2373                 // within current line
2374                 text=cursor.line().text().mid(fr.offset,fr.length);
2375             }else{
2376                 // exceeds current line
2377                 cursor.movePosition(command.length());
2378                 int ln=cursor.lineNumber();
2379                 StackEnvironment env;
2380                 document->getEnv(ln+1, env);
2381                 while(true){
2382                     ++ln;
2383                     QDocumentLine dln=document->line(ln);
2384                     if(dln.isValid()){
2385                         StackEnvironment envNext;
2386                         document->getEnv(ln+1, envNext);
2387                         if(env==envNext)
2388                             continue;
2389                         QFormatRange fr2 = dln.getOverlayAt(0, numbersFormat);
2390                         cursor.moveTo(ln,fr2.length,QDocumentCursor::KeepAnchor);
2391                         text=cursor.selectedText();
2392                         break;
2393                     }else{
2394                         QDocumentLine dln=document->line(ln-1);
2395                         cursor.moveTo(ln-1,dln.length(),QDocumentCursor::KeepAnchor);
2396                         text=cursor.selectedText();
2397                         break;
2398                     }
2399                 }
2400             }
2401         }
2402     }
2403     return text;
2404 }
2405 
showMathEnvPreview(QDocumentCursor cursor,QString command,QString environment,QPoint pos)2406 bool LatexEditorView::showMathEnvPreview(QDocumentCursor cursor, QString command, QString environment, QPoint pos)
2407 {
2408 	QStringList envAliases = document->lp.environmentAliases.values(environment);
2409 	bool found;
2410     QString text;
2411     if (((command == "\\begin" || command == "\\end") && envAliases.contains("math")) || command == "\\[" || command == "\\]" || command == "\\(" || command == "\\)") {
2412 		found = moveToCommandStart(cursor, "\\");
2413 	} else if (command == "$" || command == "$$") {
2414 		found = moveToCommandStart(cursor, command);
2415         // special treatment for $/$$ as it is handled in syntax checker
2416         text="$"+findEnclosedMathText(cursor,command)+"$";
2417 	} else {
2418 		found = false;
2419 	}
2420 	if (!found) {
2421 		QToolTip::hideText();
2422 		return false;
2423 	}
2424     text = text.isEmpty() ? parenthizedTextSelection(cursor).selectedText() : text;
2425 	if (text.isEmpty()) {
2426 		QToolTip::hideText();
2427 		return false;
2428 	}
2429 	m_point = editor->mapToGlobal(editor->mapFromFrame(pos));
2430 	emit showPreview(text);
2431 	return true;
2432 }
2433 
mouseHovered(QPoint pos)2434 void LatexEditorView::mouseHovered(QPoint pos)
2435 {
2436 	// reimplement to what is necessary
2437 
2438 	if (pos.x() < 0) return; // hover event on panel
2439 	QDocumentCursor cursor;
2440     cursor = editor->cursorForPosition(editor->mapToContents(pos),true);
2441 	QString line = cursor.line().text();
2442 	QDocumentLine l = cursor.line();
2443 
2444 	QFormatRange fr = cursor.line().getOverlayAt(cursor.columnNumber(), replaceFormat);
2445 	if (fr.length > 0 && fr.format == replaceFormat) {
2446 		QVariant var = l.getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
2447 		if (var.isValid()) {
2448 			DiffList diffList = var.value<DiffList>();
2449 			DiffOp op;
2450 			op.start = -1;
2451 			foreach (op, diffList) {
2452 				if (op.start <= cursor.columnNumber() && op.start + op.length >= cursor.columnNumber()) {
2453 					break;
2454 				}
2455 				op.start = -1;
2456 			}
2457 
2458 			if (op.start >= 0 && !op.text.isEmpty()) {
2459 				QString message = op.text;
2460 				QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), message);
2461 				return;
2462 			}
2463 		}
2464 	}
2465 	foreach (const int f, grammarFormats) {
2466 		fr = cursor.line().getOverlayAt(cursor.columnNumber(), f);
2467 		if (fr.length > 0 && fr.format == f) {
2468 			QVariant var = l.getCookie(QDocumentLine::GRAMMAR_ERROR_COOKIE);
2469 			if (var.isValid()) {
2470 				const QList<GrammarError> &errors = var.value<QList<GrammarError> >();
2471 				for (int i = 0; i < errors.size(); i++)
2472 					if (errors[i].offset <= cursor.columnNumber() && errors[i].offset + errors[i].length >= cursor.columnNumber()) {
2473 						QString message = errors[i].message;
2474 						QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), message);
2475 						return;
2476 					}
2477 			}
2478 		}
2479 	}
2480 	// check for latex error
2481 	//syntax checking
2482 	fr = cursor.line().getOverlayAt(cursor.columnNumber(), syntaxErrorFormat);
2483 	if (fr.length > 0 && fr.format == syntaxErrorFormat) {
2484 		StackEnvironment env;
2485 		document->getEnv(cursor.lineNumber(), env);
2486 		TokenStack remainder;
2487 		int i = cursor.lineNumber();
2488 		if (document->line(i - 1).handle())
2489 			remainder = document->line(i - 1).handle()->getCookieLocked(QDocumentLine::LEXER_REMAINDER_COOKIE).value<TokenStack >();
2490 		QString text = l.text();
2491 		if (!text.isEmpty()) {
2492 			QString message = document->getErrorAt(l.handle(), cursor.columnNumber(), env, remainder);
2493 			QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), message);
2494 			return;
2495 		}
2496 	}
2497 	// new way
2498 	QDocumentLineHandle *dlh = cursor.line().handle();
2499 
2500 	TokenList tl = dlh ? dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>() : TokenList();
2501 
2502 	//Tokens tk=getTokenAtCol(dlh,cursor.columnNumber());
2503 	TokenStack ts = Parsing::getContext(dlh, cursor.columnNumber());
2504 	Token tk;
2505 	if (!ts.isEmpty()) {
2506 		tk = ts.top();
2507 	}
2508 
2509 	LatexParser &lp = LatexParser::getInstance();
2510 	QString command, value;
2511 	bool handled = false;
2512 	if (tk.type != Token::none) {
2513 		int tkPos = tl.indexOf(tk);
2514 		if (tk.type == Token::command || tk.type == Token::commandUnknown) {
2515 			handled = true;
2516 			command = line.mid(tk.start, tk.length);
2517 			CommandDescription cd = lp.commandDefs.value(command);
2518 			if (cd.args > 0)
2519 				value = Parsing::getArg(tl.mid(tkPos + 1), dlh, 0, ArgumentList::Mandatory);
2520 			if (config->toolTipPreview && showMathEnvPreview(cursor, command, value, pos)) {
2521                 // action is already performed as a side effect
2522 			} else if (config->toolTipHelp && completer->getLatexReference()) {
2523 				QString topic = completer->getLatexReference()->getTextForTooltip(command);
2524 				if (!topic.isEmpty()) QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), topic);
2525 			}
2526 		}
2527 		value = tk.getText();
2528 		if (tk.type == Token::env || tk.type == Token::beginEnv) {
2529 			handled = true;
2530 			if (config->toolTipPreview && showMathEnvPreview(cursor, "\\begin", value, pos)) {
2531                 // action is already performed as a side effect
2532 			} else if (config->toolTipHelp && completer->getLatexReference()) {
2533 				QString topic = completer->getLatexReference()->getTextForTooltip("\\begin{" + value);
2534 				if (!topic.isEmpty()) QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), topic);
2535 			}
2536 		}
2537 		if (tk.type == Token::labelRef || tk.type == Token::labelRefList) {
2538 			handled = true;
2539 			int cnt = document->countLabels(value);
2540 			QString mText = "";
2541 			if (cnt == 0) {
2542 				mText = tr("label missing!");
2543 			} else if (cnt > 1) {
2544 				mText = tr("label defined multiple times!");
2545 			} else {
2546 				QMultiHash<QDocumentLineHandle *, int> result = document->getLabels(value);
2547                 QDocumentLineHandle *mLine = result.keys().constFirst();
2548 				int l = mLine->document()->indexOf(mLine);
2549 				LatexDocument *doc = qobject_cast<LatexDocument *> (editor->document());
2550 				if (mLine->document() != editor->document()) {
2551 					doc = document->parent->findDocument(mLine->document());
2552 					if (doc) mText = tr("<p style='white-space:pre'><b>Filename: %1</b>\n").arg(doc->getFileName());
2553 				}
2554 				if (doc)
2555 					mText += doc->exportAsHtml(doc->cursor(qMax(0, l - 2), 0, l + 2), true, true, 60);
2556 			}
2557 			QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), mText);
2558 		}
2559 		if (tk.type == Token::label) {
2560 			handled = true;
2561 			if (document->countLabels(value) > 1) {
2562 				QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), tr("label defined multiple times!"));
2563 			} else {
2564 				int cnt = document->countRefs(value);
2565 				QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), tr("%n reference(s) to this label", "", cnt));
2566 			}
2567 		}
2568 		if (tk.type == Token::package || tk.type == Token::beamertheme || tk.type == Token::documentclass) {
2569 			handled = true;
2570 			QString type = (tk.type == Token::documentclass) ? tr("Class") : tr("Package");
2571 			QString preambel;
2572 			if (tk.type == Token::beamertheme) { // special treatment for  \usetheme
2573 				preambel = "beamertheme";
2574 				type = tr("Beamer Theme");
2575 				type.replace(' ', "&nbsp;");
2576 			}
2577             QString text = QString("%1:&nbsp;<b>%2</b>").arg(type,value);
2578             if (latexPackageList->find(preambel + value) != latexPackageList->end()) {
2579 				QString description = LatexRepository::instance()->shortDescription(value);
2580 				if (!description.isEmpty()) text += "<br>" + description;
2581 				QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), text);
2582 			} else {
2583 				text += "<br><b>(" + tr("not found") + ")";
2584 				QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), text);
2585 			}
2586 		}
2587 		if (tk.subtype == Token::color) {
2588 			QString text;
2589 			if (ts.size() > 1) {
2590 				ts.pop();
2591 				tk = ts.top();
2592 			}
2593 			text = QString("\\noident{\\color%1 \\rule{1cm}{1cm} }").arg(tk.getText());
2594 			m_point = editor->mapToGlobal(editor->mapFromFrame(pos));
2595 			emit showPreview(text);
2596 		}
2597 		if (tk.type == Token::bibItem) {
2598 			handled = true;
2599 			QString tooltip(tr("Citation correct (reading ...)"));
2600 			QString bibID;
2601 
2602 			bibID = value;
2603 
2604 			if (!document->bibIdValid(bibID)) {
2605 				tooltip = "<b>" + tr("Citation missing") + ":</b> " + bibID;
2606 
2607 				if (!bibID.isEmpty() && bibID[bibID.length() - 1].isSpace()) {
2608 					tooltip.append("<br><br><i>" + tr("Warning:") + "</i> " + tr("BibTeX ID ends with space. Trailing spaces are not ignored by BibTeX."));
2609 				}
2610 			} else {
2611 				if (document->isBibItem(bibID)) {
2612 					// by bibitem defined citation
2613 					tooltip.clear();
2614 					QMultiHash<QDocumentLineHandle *, int> result = document->getBibItems(bibID);
2615                     if (result.isEmpty())
2616 						return;
2617                     QDocumentLineHandle *mLine = result.keys().constFirst();
2618 					if (!mLine)
2619 						return;
2620 					int l = mLine->document()->indexOf(mLine);
2621 					LatexDocument *doc = qobject_cast<LatexDocument *> (editor->document());
2622 					if (mLine->document() != editor->document()) {
2623 						doc = document->parent->findDocument(mLine->document());
2624 						if (doc) tooltip = tr("<p style='white-space:pre'><b>Filename: %1</b>\n").arg(doc->getFileName());
2625 					}
2626 					if (doc)
2627 						tooltip += doc->exportAsHtml(doc->cursor(l, 0, l + 4), true, true, 60);
2628 				} else {
2629 					// read entry in bibtex file
2630 					if (!bibReader) {
2631 						bibReader = new bibtexReader(this);
2632 						connect(bibReader, SIGNAL(sectionFound(QString)), this, SLOT(bibtexSectionFound(QString)));
2633                         connect(this, SIGNAL(searchBibtexSection(QString,QString)), bibReader, SLOT(searchSection(QString,QString)));
2634 						bibReader->start(); //The thread is started, but it is doing absolutely nothing! Signals/slots called in the thread object are execute in the emitting thread, not the thread itself.  TODO: fix
2635 					}
2636 					QString file = document->findFileFromBibId(bibID);
2637 					lastPos = pos;
2638 					if (!file.isEmpty())
2639 						emit searchBibtexSection(file, bibID);
2640 					return;
2641 				}
2642 			}
2643 			QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), tooltip);
2644 		}
2645 		if (tk.type == Token::imagefile && config->imageToolTip) {
2646 			handled = true;
2647 			QStringList imageExtensions = QStringList() << "" << "png" << "pdf" << "jpg" << "jpeg";
2648 			QString fname;
2649 			QFileInfo fi;
2650 			QStringList imagePaths = ConfigManagerInterface::getInstance()->getOption("Files/Image Paths").toString().split(getPathListSeparator());
2651 			foreach (const QString &ext, imageExtensions) {
2652 				fname = getDocument()->getAbsoluteFilePath(value, ext, imagePaths);
2653 				fi.setFile(fname);
2654 				if (fi.exists()) break;
2655 			}
2656 			if (!fi.exists()) return;
2657 			m_point = editor->mapToGlobal(editor->mapFromFrame(pos));
2658 			emit showImgPreview(fname);
2659 		}
2660 
2661 	}//if tk
2662 	if (handled)
2663 		return;
2664 
2665 	QToolTip::hideText();
2666 
2667 	/*
2668 		switch (LatexParser::getInstance().findContext(line, cursor.columnNumber(), command, value)){
2669 	    case LatexParser::Unknown: // when does this happen ????
2670 			if (config->toolTipPreview) {
2671 				QString command = extractMath(cursor);
2672 				if (!command.isEmpty()) {
2673 					m_point = editor->mapToGlobal(editor->mapFromFrame(pos));
2674 					emit showPreview(command);
2675 				} else {
2676 					QToolTip::hideText();
2677 				}
2678 			}
2679 			break;
2680 
2681 
2682 	     }*/
2683 	//QToolTip::showText(editor->mapToGlobal(pos), line);
2684 }
2685 
closeElement()2686 bool LatexEditorView::closeElement()
2687 {
2688 	if (completer->close()) return true;
2689 	if (gotoLinePanel->isVisible()) {
2690 		gotoLinePanel->hide();
2691 		editor->setFocus();
2692 		return true;
2693 	}
2694 	if (searchReplacePanel->isVisible()) {
2695 		searchReplacePanel->closeElement(config->closeSearchAndReplace);
2696 		return true;
2697 	}
2698 	return false;
2699 }
2700 
insertHardLineBreaks(int newLength,bool smartScopeSelection,bool joinLines)2701 void LatexEditorView::insertHardLineBreaks(int newLength, bool smartScopeSelection, bool joinLines)
2702 {
2703     QRegularExpression breakChars("[ \t\n\r]");
2704 	QDocumentCursor cur = editor->cursor();
2705 	QDocument *doc = editor->document();
2706 	int startLine = 0;
2707 	int endLine = doc->lines() - 1;
2708 
2709 	if (cur.isValid()) {
2710 		if (cur.hasSelection()) {
2711 			startLine = cur.selectionStart().lineNumber();
2712 			endLine = cur.selectionEnd().lineNumber();
2713 			if (cur.selectionEnd().columnNumber() == 0 && startLine < endLine) endLine--;
2714 		} else if (smartScopeSelection) {
2715 			QDocumentCursor currentCur = cur;
2716 			QDocumentCursor lineCursor = currentCur;
2717 			do {
2718 				QString lineString  = lineCursor.line().text().trimmed();
2719 				if ((lineString == QLatin1String("")) ||
2720 				        (lineString.contains("\\begin")) ||
2721 				        (lineString.contains("\\end")) ||
2722 				        (lineString.contains("$$")) ||
2723 				        (lineString.contains("\\[")) ||
2724 				        (lineString.contains("\\]"))) {
2725 					//qDebug() << lineString;
2726 					break;
2727 				}
2728 			} while (lineCursor.movePosition(1, QDocumentCursor::Up, QDocumentCursor::MoveAnchor));
2729 			startLine = lineCursor.lineNumber();
2730 			if (lineCursor.atStart()) startLine--;
2731 
2732 			lineCursor = currentCur;
2733 			do {
2734 				QString lineString  = lineCursor.line().text().trimmed();
2735 				if ((lineString == QLatin1String("")) ||
2736 				        (lineString.contains("\\begin")) ||
2737 				        (lineString.contains("\\end")) ||
2738 				        (lineString.contains("$$")) ||
2739 				        (lineString.contains("\\[")) ||
2740 				        (lineString.contains("\\]"))) {
2741 					//qDebug() << lineString;
2742 					break;
2743 				}
2744 			} while (lineCursor.movePosition(1, QDocumentCursor::Down, QDocumentCursor::MoveAnchor));
2745 			endLine = lineCursor.lineNumber();
2746 			if (lineCursor.atEnd()) endLine++	;
2747 
2748 			if ((endLine - startLine) < 2) { // lines near, therefore no need to line break
2749 				return ;
2750 			}
2751 
2752 			startLine++;
2753 			endLine--;
2754 		}
2755 	}
2756 	if (joinLines) { // start of smart formatting, similar to what emacs (AucTeX) can do, but much simple
2757 		QStringList lines;
2758 		for (int i = startLine; i <= endLine; i++)
2759 			lines << doc->line(i).text();
2760 		lines = joinLinesExceptCommentsAndEmptyLines(lines);
2761 		lines = splitLines(lines, newLength, breakChars);
2762 
2763 		QDocumentCursor vCur = doc->cursor(startLine, 0, endLine, doc->line(endLine).length());
2764 		editor->insertText(vCur, lines.join("\n"));
2765 		editor->setCursor(cur);
2766 		return;
2767 	}
2768 
2769 	bool areThereLinesToBreak = false;
2770 	for (int i = startLine; i <= endLine; i++)
2771 		if (doc->line(i).length() > newLength) {
2772 			areThereLinesToBreak = true;
2773 			break;
2774 		}
2775 	if (!areThereLinesToBreak) return;
2776 	//remove all lines and reinsert them wrapped
2777 	if (endLine + 1 < doc->lines())
2778 		cur = doc->cursor(startLine, 0, endLine + 1, 0); //+1,0);
2779 	else
2780 		cur = doc->cursor(startLine, 0, endLine, doc->line(endLine).length()); //+1,0);
2781 	QStringList lines;
2782 	for (int i = startLine; i <= endLine; i++)
2783 		lines << doc->line(i).text();
2784 	QString insertBlock;
2785 	for (int i = 0; i < lines.count(); i++) {
2786 		QString line = lines[i];
2787 		int commentStart = LatexParser::commentStart(line);
2788 		if (commentStart == -1) commentStart = line.length();
2789 		while (line.length() > newLength) {
2790 			int breakAt = line.lastIndexOf(breakChars, newLength);
2791 			if (breakAt < 0) breakAt = line.indexOf(breakChars, newLength);
2792 			if (breakAt < 0) break;
2793 			if (breakAt >= commentStart && breakAt + 1 > newLength) {
2794 				int newBreakAt = line.indexOf(breakChars, breakAt - 1);
2795 				if (newBreakAt > -1) breakAt = newBreakAt;
2796 			}
2797 			insertBlock += line.left(breakAt) + "\n";
2798 			if (breakAt < commentStart) {
2799 				line = line.mid(breakAt + 1);
2800 				commentStart -= breakAt + 1;
2801 			} else {
2802 				line = "%" + line.mid(breakAt + 1);
2803 				commentStart = 0;
2804 			}
2805 		}
2806 		insertBlock += line + "\n";
2807 	}
2808 	editor->insertText(cur, insertBlock);
2809 
2810 	editor->setCursor(cur);
2811 }
2812 
sortSelectedLines(LineSorting sorting,Qt::CaseSensitivity caseSensitivity,bool completeLines,bool removeDuplicates)2813 void LatexEditorView::sortSelectedLines(LineSorting sorting, Qt::CaseSensitivity caseSensitivity, bool completeLines, bool removeDuplicates){
2814 	if (completeLines){
2815 		editor->selectExpand(QDocumentCursor::LineUnderCursor);
2816 	}
2817 	QList<QDocumentCursor> cursors = editor->cursors();
2818 	std::sort(cursors.begin(), cursors.end());
2819 	for (int i=0; i < cursors.length(); i++)
2820 		cursors[i].setAutoUpdated(true); //auto updating is not enabled by default (but it is supposed to be, isn't it?)
2821 
2822 	QList<int> spannedLines;
2823 	QStringList text;
2824 	foreach (const QDocumentCursor& c, cursors) {
2825 		QStringList selectedTextLines = c.selectedText().split('\n');
2826 		spannedLines << selectedTextLines.length();
2827 		text << selectedTextLines;
2828 	}
2829 	if (text.isEmpty()) return;
2830 	bool additionalEmptyLine = text.last().isEmpty();
2831 	if (additionalEmptyLine) text.removeLast();
2832 
2833 	if (sorting == SortAscending || sorting == SortDescending)
2834 		text.sort(caseSensitivity);
2835     else if (sorting == SortRandomShuffle){
2836         std::random_device rd;
2837         std::mt19937 g(rd());
2838         std::shuffle(text.begin(), text.end(),g);
2839     }
2840 	if (sorting == SortDescending)
2841 		std::reverse(text.begin(), text.end());
2842 	if (removeDuplicates)
2843 		text.removeDuplicates();
2844 
2845 	if (additionalEmptyLine) text.append("");
2846 
2847 	editor->document()->beginMacro();
2848 	for (int i=0; i < cursors.length() - 1; i++){
2849 		QStringList lines;
2850 		for (int j=0; j < qMin(spannedLines[i], text.length()); j++)
2851 			lines << text.takeFirst();
2852 		cursors[i].replaceSelectedText(lines.join('\n'));
2853 	}
2854 	cursors.last().replaceSelectedText(text.join('\n'));
2855 	editor->document()->endMacro();
2856 }
2857 
translateEditOperation(int key)2858 QString LatexEditorViewConfig::translateEditOperation(int key)
2859 {
2860     return QEditor::translateEditOperation(static_cast<QEditor::EditOperation>(key));
2861 }
2862 
possibleEditOperations()2863 QList<int> LatexEditorViewConfig::possibleEditOperations()
2864 {
2865 	int  temp[] = {
2866 		QEditor::NoOperation,
2867 		QEditor::Invalid,
2868 
2869 		QEditor::CursorUp,
2870 		QEditor::CursorDown,
2871 		QEditor::CursorLeft,
2872 		QEditor::CursorRight,
2873 		QEditor::CursorWordLeft,
2874 		QEditor::CursorWordRight,
2875 		QEditor::CursorStartOfLine,
2876 		QEditor::CursorEndOfLine,
2877 		QEditor::CursorStartOfDocument,
2878 		QEditor::CursorEndOfDocument,
2879 
2880 		QEditor::CursorPageUp,
2881 		QEditor::CursorPageDown,
2882 
2883 		QEditor::SelectCursorUp,
2884 		QEditor::SelectCursorDown,
2885 		QEditor::SelectCursorLeft,
2886 		QEditor::SelectCursorRight,
2887 		QEditor::SelectCursorWordLeft,
2888 		QEditor::SelectCursorWordRight,
2889 		QEditor::SelectCursorStartOfLine,
2890 		QEditor::SelectCursorEndOfLine,
2891 		QEditor::SelectCursorStartOfDocument,
2892 		QEditor::SelectCursorEndOfDocument,
2893 
2894 		QEditor::SelectPageUp,
2895 		QEditor::SelectPageDown,
2896 
2897 		QEditor::EnumForCursorEnd,
2898 
2899 		QEditor::DeleteLeft,
2900 		QEditor::DeleteRight,
2901 		QEditor::DeleteLeftWord,
2902 		QEditor::DeleteRightWord,
2903 		QEditor::NewLine,
2904 
2905 		QEditor::ChangeOverwrite,
2906 		QEditor::Undo,
2907 		QEditor::Redo,
2908 		QEditor::Copy,
2909 		QEditor::Paste,
2910 		QEditor::Cut,
2911 		QEditor::Print,
2912 		QEditor::SelectAll,
2913 		QEditor::Find,
2914 		QEditor::FindNext,
2915 		QEditor::FindPrevious,
2916 		QEditor::Replace,
2917 
2918 		QEditor::CreateMirrorUp,
2919 		QEditor::CreateMirrorDown,
2920 		QEditor::NextPlaceHolder,
2921 		QEditor::PreviousPlaceHolder,
2922 		QEditor::NextPlaceHolderOrWord,
2923 		QEditor::PreviousPlaceHolderOrWord,
2924 		QEditor::NextPlaceHolderOrChar,
2925 		QEditor::PreviousPlaceHolderOrChar,
2926 		QEditor::TabOrIndentSelection,
2927 		QEditor::IndentSelection,
2928 		QEditor::UnindentSelection
2929 	};
2930 	QList<int> res;
2931     int operationCount = static_cast<int>(sizeof(temp) / sizeof(int)); //sizeof(array) is possible with c-arrays
2932 	for (int i = 0; i < operationCount; i++)
2933 		res << temp[i];
2934 	return res;
2935 }
2936 
2937 /*
2938  * If the cursor is at the border of a parenthesis, this returns a QDocumentCursor with a selection of the parenthized text.
2939  * Otherwise, a default QDocumentCursor is returned.
2940  */
parenthizedTextSelection(const QDocumentCursor & cursor,bool includeParentheses)2941 QDocumentCursor LatexEditorView::parenthizedTextSelection(const QDocumentCursor &cursor, bool includeParentheses)
2942 {
2943 	QDocumentCursor from, to;
2944 	cursor.getMatchingPair(from, to, includeParentheses);
2945 	if (!from.hasSelection() || !to.hasSelection()) return QDocumentCursor();
2946 	QDocumentCursor::sort(from, to);
2947 	return QDocumentCursor(from.selectionStart(), to.selectionEnd());
2948 }
2949 
2950 /*
2951  * finds the beginning of the specified allowedFormats
2952  * additional formats can be allowed at the line end (e.g. comments)
2953  */
findFormatsBegin(const QDocumentCursor & cursor,QSet<int> allowedFormats,QSet<int>)2954 QDocumentCursor LatexEditorView::findFormatsBegin(const QDocumentCursor &cursor, QSet<int> allowedFormats, QSet<int> )
2955 {
2956 	QDocumentCursor c(cursor);
2957 
2958 	QVector<int> lineFormats = c.line().getFormats();
2959 	int col = c.columnNumber();
2960     QFormatRange rng=c.line().getOverlayAt(col);
2961     if (col >= 0 && allowedFormats.contains(rng.format) ) {
2962 		while (true) {
2963             while (col > 0) {
2964                 rng=c.line().getOverlayAt(col-1);
2965                 if(allowedFormats.contains(rng.format)){
2966                     col--;
2967                 }else{
2968                     break;
2969                 }
2970             }
2971 			if (col > 0) break;
2972 			// continue on previous line
2973 			c.movePosition(1, QDocumentCursor::PreviousLine);
2974 			c.movePosition(1, QDocumentCursor::EndOfLine);
2975 			lineFormats = c.line().getFormats();
2976 			col = c.columnNumber();
2977             while (col > 0) {
2978                 rng=c.line().getOverlayAt(col-1);
2979                 if(allowedFormats.contains(rng.format)){
2980                     col--;
2981                 }else{
2982                     break;
2983                 }
2984             }
2985 		}
2986 		c.setColumnNumber(col);
2987 		return c;
2988 	}
2989 	return QDocumentCursor();
2990 }
2991 
triggeredThesaurus()2992 void LatexEditorView::triggeredThesaurus()
2993 {
2994 	QAction *act = qobject_cast<QAction *>(sender());
2995 	QPoint pt = act->data().toPoint();
2996 	emit thesaurus(pt.x(), pt.y());
2997 }
2998 
changeSpellingDict(const QString & name)2999 void LatexEditorView::changeSpellingDict(const QString &name)
3000 {
3001 	QString similarName;
3002 	if (spellerManager->hasSpeller(name)) {
3003 		setSpeller(name);
3004 	} else if (spellerManager->hasSimilarSpeller(name, similarName)) {
3005 		setSpeller(similarName);
3006 	}
3007 }
3008 
copyImageFromAction()3009 void LatexEditorView::copyImageFromAction()
3010 {
3011 	QAction *act = qobject_cast<QAction *>(sender());
3012 	if (!act) return;
3013 
3014 	QPixmap pm = act->data().value<QPixmap>();
3015 	if (!pm.isNull()) {
3016 		QApplication::clipboard()->setImage(pm.toImage());
3017 	}
3018 }
3019 
saveImageFromAction()3020 void LatexEditorView::saveImageFromAction()
3021 {
3022 	static QString lastSaveDir;
3023 
3024 	QAction *act = qobject_cast<QAction *>(sender());
3025 	if (!act) return;
3026 
3027 	QPixmap pm = act->data().value<QPixmap>();
3028 
3029 	QString fname = FileDialog::getSaveFileName(this , tr("Save Preview Image"), lastSaveDir, tr("Images") + " (*.png *.jpg *.jpeg)");
3030 	if (fname.isEmpty()) return;
3031 
3032 	QFileInfo fi(fname);
3033 	lastSaveDir = fi.absolutePath();
3034 	pm.save(fname);
3035 }
3036 
3037 
settingsChanged()3038 void LatexEditorViewConfig::settingsChanged()
3039 {
3040 	if (!hackAutoChoose) {
3041 		lastFontFamily = "";
3042 		return;
3043 	}
3044 	if (lastFontFamily == fontFamily && lastFontSize == fontSize) return;
3045 
3046 	QFont f(fontFamily, fontSize);
3047 #if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
3048     f.setStyleHint(QFont::Courier);
3049 #else
3050 	f.setStyleHint(QFont::Courier, QFont::ForceIntegerMetrics);
3051 #endif
3052 
3053 	f.setKerning(false);
3054 
3055     QList<QFontMetrics> fms; // QFontMetric should be okay as it is just used to check for monospace font.
3056 	for (int b = 0; b < 2; b++) for (int i = 0; i < 2; i++) {
3057 			QFont ft(f);
3058 			ft.setBold(b);
3059 			ft.setItalic(i);
3060             fms << QFontMetrics(ft);
3061 		}
3062 
3063 	bool lettersHaveDifferentWidth = false, sameLettersHaveDifferentWidth = false;
3064 	int letterWidth = UtilsUi::getFmWidth(fms.first(), 'a');
3065 
3066 	const QString lettersToCheck("abcdefghijklmnoqrstuvwxyzABCDEFHIJKLMNOQRSTUVWXYZ_+ 123/()=.,;#");
3067 	QVector<QMap<QChar, int> > widths;
3068 	widths.resize(fms.size());
3069 
3070 	foreach (const QChar &c, lettersToCheck) {
3071 		for (int fmi = 0; fmi < fms.size(); fmi++) {
3072 			const QFontMetrics &fm = fms[fmi];
3073 			int currentWidth = UtilsUi::getFmWidth(fm, c);
3074 			widths[fmi].insert(c, currentWidth);
3075 			if (currentWidth != letterWidth) lettersHaveDifferentWidth = true;
3076 			QString testString;
3077 			for (int i = 1; i < 10; i++) {
3078 				testString += c;
3079 				int stringWidth = UtilsUi::getFmWidth(fm, testString);
3080 				if (stringWidth % i != 0) sameLettersHaveDifferentWidth = true;
3081 				if (currentWidth != stringWidth / i) sameLettersHaveDifferentWidth = true;
3082 			}
3083 			if (lettersHaveDifferentWidth && sameLettersHaveDifferentWidth) break;
3084 		}
3085 		if (lettersHaveDifferentWidth && sameLettersHaveDifferentWidth) break;
3086 	}
3087 	const QString ligatures[2] = {"aftt", "afit"};
3088 	for (int l = 0; l < 2 && !sameLettersHaveDifferentWidth; l++) {
3089 		for (int fmi = 0; fmi < fms.size(); fmi++) {
3090 			int expectedWidth = 0;
3091 			for (int i = 0; i < ligatures[l].size() && !sameLettersHaveDifferentWidth; i++) {
3092 				expectedWidth += widths[fmi].value(ligatures[l][i]);
3093 				if (expectedWidth != UtilsUi::getFmWidth(fms[fmi], ligatures[l].left(i + 1))) sameLettersHaveDifferentWidth = true;
3094 			}
3095 		}
3096 	}
3097 
3098 	if (!QFontInfo(f).fixedPitch()) hackDisableFixedPitch = false; //won't be enabled anyways
3099 	else hackDisableFixedPitch = lettersHaveDifferentWidth || sameLettersHaveDifferentWidth;
3100 	hackDisableWidthCache = sameLettersHaveDifferentWidth;
3101 
3102 #if defined( Q_OS_LINUX ) || defined( Q_OS_WIN )
3103 	hackDisableLineCache = true;
3104 #else
3105 	hackDisableLineCache = false;
3106 	//hackDisableLineCache = isRetinaMac();
3107 #endif
3108 	hackRenderingMode = 0; //always use qce, best tested
3109 
3110 	lastFontFamily = fontFamily;
3111 	lastFontSize = fontSize;
3112 }
3113 
3114 
affect(const QKeyEvent *,const QString & base,int,int) const3115 QString BracketInvertAffector::affect(const QKeyEvent *, const QString &base, int, int) const
3116 {
3117 	static const QString &brackets = "<>()[]{}";
3118 	QString after;
3119 	for (int i = 0; i < base.length(); i++)
3120 		if (brackets.indexOf(base[i]) >= 0)
3121 			after += brackets[brackets.indexOf(base[i]) + 1 - 2 * (brackets.indexOf(base[i]) & 1) ];
3122 		else if (base[i] == '\\') {
3123 			if (base.mid(i, 7) == "\\begin{") {
3124 				after += "\\end{" + base.mid(i + 7);
3125 				return after;
3126 			} else if (base.mid(i, 5) == "\\end{") {
3127 				after += "\\begin{" + base.mid(i + 5);
3128 				return after;
3129 			} else if (base.mid(i, 5) == "\\left") {
3130 				after += "\\right";
3131 				i += 4;
3132 			} else if (base.mid(i, 6) == "\\right") {
3133 				after += "\\left";
3134 				i += 5;
3135 			} else after += '\\';
3136 		} else after += base[i];
3137 	return after;
3138 }
3139 
3140 BracketInvertAffector *inverterSingleton = nullptr;
3141 
instance()3142 BracketInvertAffector *BracketInvertAffector::instance()
3143 {
3144 	if (!inverterSingleton) inverterSingleton = new BracketInvertAffector();
3145 	return inverterSingleton;
3146 }
3147 
bibtexSectionFound(QString content)3148 void LatexEditorView::bibtexSectionFound(QString content)
3149 {
3150 	QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(lastPos)), content);
3151 }
3152 
lineMarkContextMenuRequested(int lineNumber,QPoint globalPos)3153 void LatexEditorView::lineMarkContextMenuRequested(int lineNumber, QPoint globalPos)
3154 {
3155 	if (!document) return;
3156 
3157 	QDocumentLine line(document->line(lineNumber));
3158 	QMenu menu(this);
3159 
3160 	for (int i = -1; i < 10; i++) {
3161 		int rmid = bookMarkId(i);
3162 		if (line.hasMark(rmid)) {
3163 			QAction *act =  new QAction(tr("Remove Bookmark"), &menu);
3164 			act->setData(-2);
3165 			menu.addAction(act);
3166 			menu.addSeparator();
3167 			break;
3168 		}
3169 	}
3170 
3171 	QAction *act = new QAction(getRealIconCached("lbook"), tr("Unnamed Bookmark"), &menu);
3172 	act->setData(-1);
3173 	menu.addAction(act);
3174 
3175 	for (int i = 0; i < 10; i++) {
3176 		QAction *act = new QAction(getRealIconCached(QString("lbook%1").arg(i)), tr("Bookmark") + QString(" %1").arg(i), &menu);
3177 		act->setData(i);
3178 		menu.addAction(act);
3179 	}
3180 
3181 	act = menu.exec(globalPos);
3182 	if (act) {
3183 		int bookmarkNumber = act->data().toInt();
3184 		if (bookmarkNumber == -2) {
3185 			for (int i = -1; i < 10; i++) {
3186 				int rmid = bookMarkId(i);
3187 				if (line.hasMark(rmid)) {
3188 					removeBookmark(line.handle(), i);
3189 					return;
3190 				}
3191 			}
3192 		} else {
3193 			toggleBookmark(bookmarkNumber, line);
3194 		}
3195 	}
3196 }
3197 
foldContextMenuRequested(int lineNumber,QPoint globalPos)3198 void LatexEditorView::foldContextMenuRequested(int lineNumber, QPoint globalPos)
3199 {
3200 	Q_UNUSED(lineNumber)
3201 
3202 	QMenu menu;
3203 	QAction *act = new QAction(tr("Collapse All"), &menu);
3204 	act->setData(-5);
3205 	menu.addAction(act);
3206 	for (int i = 1; i <= 4; i++) {
3207 		act = new QAction(QString(tr("Collapse Level %1")).arg(i), &menu);
3208 		act->setData(-i);
3209 		menu.addAction(act);
3210 	}
3211 	menu.addSeparator();
3212 	act = new QAction(tr("Expand All"), &menu);
3213 	act->setData(5);
3214 	menu.addAction(act);
3215 	for (int i = 1; i <= 4; i++) {
3216 		act = new QAction(QString(tr("Expand Level %1")).arg(i), &menu);
3217 		act->setData(i);
3218 		menu.addAction(act);
3219 	}
3220 
3221 	act = menu.exec(globalPos);
3222 	if (act) {
3223 		int level = act->data().toInt();
3224 		if (qAbs(level) < 5) {
3225 			foldLevel(level > 0, qAbs(level));
3226 		} else {
3227 			foldEverything(level > 0);
3228 		}
3229 	}
3230 }
3231 
LinkOverlay(const LinkOverlay & o)3232 LinkOverlay::LinkOverlay(const LinkOverlay &o)
3233 {
3234 	type = o.type;
3235 	if (o.isValid()) {
3236 		docLine = o.docLine;
3237 		formatRange = o.formatRange;
3238 	}
3239 }
3240 
LinkOverlay(const Token & token,LinkOverlay::LinkOverlayType ltype)3241 LinkOverlay::LinkOverlay(const Token &token, LinkOverlay::LinkOverlayType ltype) :
3242 	type(ltype)
3243 {
3244 	if (type == Invalid) return;
3245 
3246 	int from = token.start;
3247 	int to = from + token.length - 1;
3248 	if (from < 0 || to < 0 || to <= from)
3249 		return;
3250 
3251 	REQUIRE(QDocument::defaultFormatScheme());
3252 	formatRange = QFormatRange(from, to - from + 1, QDocument::defaultFormatScheme()->id("link"));
3253 	docLine = QDocumentLine(token.dlh);
3254 }
3255 
text() const3256 QString LinkOverlay::text() const
3257 {
3258 	if (!isValid()) return QString();
3259 	return docLine.text().mid(formatRange.offset, formatRange.length);
3260 }
3261 
getSearchText()3262 QString LatexEditorView::getSearchText()
3263 {
3264 	return searchReplacePanel->getSearchText();
3265 }
3266 
getReplaceText()3267 QString LatexEditorView::getReplaceText()
3268 {
3269 	return searchReplacePanel->getReplaceText();
3270 }
3271 
getSearchIsWords()3272 bool LatexEditorView::getSearchIsWords()
3273 {
3274 	return searchReplacePanel->getSearchIsWords();
3275 }
3276 
getSearchIsCase()3277 bool LatexEditorView::getSearchIsCase()
3278 {
3279 	return searchReplacePanel->getSearchIsCase();
3280 }
3281 
getSearchIsRegExp()3282 bool LatexEditorView::getSearchIsRegExp()
3283 {
3284 	return searchReplacePanel->getSearchIsRegExp();
3285 }
3286 
isInMathHighlighting(const QDocumentCursor & cursor)3287 bool LatexEditorView::isInMathHighlighting(const QDocumentCursor &cursor )
3288 {
3289 	const QDocumentLine &line = cursor.line();
3290 	if (!line.handle()) return false;
3291 
3292     int col = cursor.columnNumber();
3293 
3294     const QFormatRange &format = line.handle()->getOverlayAt(col,numbersFormat);
3295 
3296     return format.isValid();
3297 }
3298 
checkRTLLTRLanguageSwitching()3299 void LatexEditorView::checkRTLLTRLanguageSwitching()
3300 {
3301 #if defined( Q_OS_WIN ) || defined( Q_OS_LINUX ) || ( defined( Q_OS_UNIX ) && !defined( Q_OS_MAC ) )
3302 	QDocumentCursor cursor = editor->cursor();
3303 	QDocumentLine line = cursor.line();
3304 	InputLanguage language = IL_UNCERTAIN;
3305 	if (line.firstChar() >= 0) { //whitespace lines have no language information
3306 		if (config->switchLanguagesMath) {
3307 			if (isInMathHighlighting(cursor)) language = IL_LTR;
3308 			else language = IL_RTL;
3309 		}
3310 
3311 		if (config->switchLanguagesDirection && language != IL_LTR) {
3312 			if (line.hasFlag(QDocumentLine::LayoutDirty))
3313 				if (line.isRTLByLayout() || line.isRTLByText() ) {
3314 					line.handle()->lockForWrite();
3315 					line.handle()->layout(cursor.lineNumber());
3316 					line.handle()->unlock();
3317 				}
3318 			if (!line.isRTLByLayout())
3319 				language = IL_LTR;
3320 			else {
3321 				int c = cursor.columnNumber();
3322 				int dir = line.rightCursorPosition(c) - c;
3323 				if (dir < 0) language = IL_RTL;
3324 				else if (dir > 0) language = IL_LTR;
3325 			}
3326 		}
3327 	}
3328 	setInputLanguage(language);
3329 #endif
3330 }
3331