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(' ', " ");
2576 }
2577 QString text = QString("%1: <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