LCOV - code coverage report
Current view: top level - gui/source - codeeditor.cpp (source / functions) Hit Total Coverage
Test: mcrl2_coverage.info.cleaned Lines: 0 226 0.0 %
Date: 2020-09-16 00:45:56 Functions: 0 26 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Author(s): Olav Bunte
       2             : // Copyright: see the accompanying file COPYING or copy at
       3             : // https://github.com/mCRL2org/mCRL2/blob/master/COPYING
       4             : //
       5             : // Distributed under the Boost Software License, Version 1.0.
       6             : // (See accompanying file LICENSE_1_0.txt or copy at
       7             : // http://www.boost.org/LICENSE_1_0.txt)
       8             : //
       9             : 
      10             : #include "mcrl2/gui/codeeditor.h"
      11             : 
      12             : #include "mcrl2/utilities/platform.h"
      13             : 
      14             : #include <QPainter>
      15             : #include <QTextBlock>
      16             : #include <QMenu>
      17             : 
      18             : using namespace mcrl2::gui::qt;
      19             : 
      20           0 : HighlightingRule::HighlightingRule(QRegExp pattern, QTextCharFormat format)
      21           0 :     : pattern(pattern), format(format)
      22             : {
      23           0 : }
      24             : 
      25           0 : CodeHighlighter::CodeHighlighter(bool spec, bool light, QTextDocument* parent)
      26           0 :     : QSyntaxHighlighter(parent)
      27             : {
      28             :   /* identifiers */
      29           0 :   identifierFormat.setForeground(light ? Qt::black : Qt::white);
      30           0 :   highlightingRules.push_back(HighlightingRule(
      31           0 :       QRegExp("\\b[a-zA-Z_][a-zA-Z0-9_']*\\b"), identifierFormat));
      32             : 
      33             :   /* in case of mcrl2 specification */
      34           0 :   if (spec)
      35             :   {
      36             :     /* specification keywords */
      37           0 :     specificationKeywordFormat.setForeground(light ? Qt::darkBlue
      38             :                                                    : QColor(38, 139, 210));
      39             :     QStringList specificationKeywordPatterns = {
      40             :         "\\bsort\\b", "\\bcons\\b", "\\bmap\\b",  "\\bvar\\b",   "\\beqn\\b",
      41           0 :         "\\bact\\b",  "\\bproc\\b", "\\binit\\b", "\\bstruct\\b"};
      42           0 :     for (const QString& pattern : specificationKeywordPatterns)
      43             :     {
      44           0 :       highlightingRules.push_back(
      45           0 :           HighlightingRule(QRegExp(pattern), specificationKeywordFormat));
      46             :     }
      47             : 
      48             :     /* process keywords */
      49           0 :     processKeywordFormat.setForeground(light ? Qt::darkCyan : Qt::cyan);
      50           0 :     QStringList processKeywordPatterns = {"\\bdelta\\b", "\\btau\\b"};
      51           0 :     for (const QString& pattern : processKeywordPatterns)
      52             :     {
      53           0 :       highlightingRules.push_back(
      54           0 :           HighlightingRule(QRegExp(pattern), processKeywordFormat));
      55             :     }
      56             : 
      57             :     /* process operator keywords */
      58           0 :     processOperatorKeywordFormat.setForeground(light ? Qt::blue
      59             :                                                      : QColor(108, 113, 196));
      60             :     QStringList processOperatorKeywordPatterns = {
      61             :         "\\bsum\\b",  "\\bdist\\b",   "\\bblock\\b", "\\ballow\\b",
      62           0 :         "\\bhide\\b", "\\brename\\b", "\\bcomm\\b"};
      63           0 :     for (const QString& pattern : processOperatorKeywordPatterns)
      64             :     {
      65           0 :       highlightingRules.push_back(
      66           0 :           HighlightingRule(QRegExp(pattern), processOperatorKeywordFormat));
      67             :     }
      68             :   }
      69             :   /* in case of mcf formula */
      70             :   else
      71             :   {
      72             :     /* state formula operator keywords */
      73           0 :     stateFormulaOpertorKeywordFormat.setForeground(
      74           0 :         light ? Qt::blue : QColor(128, 128, 255));
      75             :     QStringList processOperatorKeywordPatterns = {"\\bmu\\b", "\\bnu\\b",
      76           0 :                                                   "\\bdelay\\b", "\\byelad\\b"};
      77           0 :     for (const QString& pattern : processOperatorKeywordPatterns)
      78             :     {
      79           0 :       highlightingRules.push_back(
      80           0 :           HighlightingRule(QRegExp(pattern), stateFormulaOpertorKeywordFormat));
      81             :     }
      82             :   }
      83             : 
      84             :   /* shared syntax */
      85             : 
      86             :   /* primitive type keywords */
      87           0 :   primitiveTypeKeywordFormat.setForeground(light ? Qt::darkMagenta
      88             :                                                  : Qt::magenta);
      89             :   QStringList primitiveTypeKeywordPatterns = {
      90           0 :       "\\bBool\\b", "\\bPos\\b", "\\bNat\\b", "\\bInt\\b", "\\bReal\\b"};
      91           0 :   for (const QString& pattern : primitiveTypeKeywordPatterns)
      92             :   {
      93           0 :     highlightingRules.push_back(
      94           0 :         HighlightingRule(QRegExp(pattern), primitiveTypeKeywordFormat));
      95             :   }
      96             : 
      97             :   /* container type keywords */
      98           0 :   containerTypeKeywordFormat.setForeground(light ? Qt::darkGreen : Qt::green);
      99             :   QStringList containerTypeKeywordPatterns = {
     100           0 :       "\\bList\\b", "\\bSet\\b", "\\bBag\\b", "\\bFSet\\b", "\\bFBag\\b"};
     101           0 :   for (const QString& pattern : containerTypeKeywordPatterns)
     102             :   {
     103           0 :     highlightingRules.push_back(
     104           0 :         HighlightingRule(QRegExp(pattern), containerTypeKeywordFormat));
     105             :   }
     106             : 
     107             :   /* data keywords */
     108           0 :   dataKeywordFormat.setForeground(light ? Qt::darkYellow : Qt::yellow);
     109           0 :   QStringList dataKeywordPatterns = {"\\btrue\\b", "\\bfalse\\b"};
     110           0 :   for (const QString& pattern : dataKeywordPatterns)
     111             :   {
     112           0 :     highlightingRules.push_back(
     113           0 :         HighlightingRule(QRegExp(pattern), dataKeywordFormat));
     114             :   }
     115             : 
     116             :   /* data operator keywords */
     117           0 :   dataOperatorKeywordFormat.setForeground(light ? Qt::darkRed : Qt::red);
     118             :   QStringList dataOperatorKeywordPatterns = {
     119             :       "\\bwhr\\b",    "\\bend\\b", "\\blambda\\b", "\\bforall\\b",
     120           0 :       "\\bexists\\b", "\\bdiv\\b", "\\bmod\\b",    "\\bin\\b"};
     121           0 :   for (const QString& pattern : dataOperatorKeywordPatterns)
     122             :   {
     123           0 :     highlightingRules.push_back(
     124           0 :         HighlightingRule(QRegExp(pattern), dataOperatorKeywordFormat));
     125             :   }
     126             : 
     127             :   /* to do keywords */
     128           0 :   todoKeywordFormat.setForeground(light ? Qt::red : QColor(255, 128, 128));
     129             :   QStringList todoKeywordPatterns = {"\\bcontained\\b", "\\bTODO\\b",
     130           0 :                                      "\\bFIXME\\b", "\\bXXX\\b"};
     131           0 :   for (const QString& pattern : todoKeywordPatterns)
     132             :   {
     133           0 :     highlightingRules.push_back(
     134           0 :         HighlightingRule(QRegExp(pattern), todoKeywordFormat));
     135             :   }
     136             : 
     137             :   /* defined function keywords */
     138           0 :   functionKeywordFormat.setForeground(light ? Qt::darkCyan : Qt::cyan);
     139             :   QStringList functionKeywordPatterns = {
     140             :       "\\bmin\\b",     "\\bmax\\b",     "\\bsucc\\b",   "\\bpred\\b",
     141             :       "\\babs\\b",     "\\bfloor\\b",   "\\bceil\\b",   "\\bround\\b",
     142             :       "\\bexp\\b",     "\\bA2B\\b",     "\\bhead\\b",   "\\btail\\b",
     143             :       "\\brhead\\b",   "\\brtail\\b",   "\\bcount\\b",  "\\bPos2Nat\\b",
     144           0 :       "\\bNat2Pos\\b", "\\bSet2Bag\\b", "\\bBag2Set\\b"};
     145           0 :   for (const QString& pattern : functionKeywordPatterns)
     146             :   {
     147           0 :     highlightingRules.push_back(
     148           0 :         HighlightingRule(QRegExp(pattern), functionKeywordFormat));
     149             :   }
     150             : 
     151             :   /* operators */
     152           0 :   operatorFormat.setForeground(light ? Qt::darkGreen : Qt::green);
     153           0 :   highlightingRules.push_back(HighlightingRule(
     154           0 :       QRegExp("[\\.\\+|&<>:;=@(){}\\[\\],\\!\\*/\\\\-]"), operatorFormat));
     155           0 :   highlightingRules.push_back(
     156           0 :       HighlightingRule(QRegExp("\\|\\|_"), operatorFormat));
     157           0 :   highlightingRules.push_back(HighlightingRule(QRegExp("->"), operatorFormat));
     158             : 
     159             :   /* numbers */
     160           0 :   numberFormat.setForeground(light ? Qt::darkGreen : Qt::green);
     161           0 :   highlightingRules.push_back(
     162           0 :       HighlightingRule(QRegExp("\\b[0-9]+\\b"), numberFormat));
     163             : 
     164             :   /* single line comments */
     165           0 :   commentFormat.setForeground(light ? Qt::gray : Qt::lightGray);
     166           0 :   highlightingRules.push_back(
     167           0 :       HighlightingRule(QRegExp("%[^\n]*"), commentFormat));
     168           0 : }
     169             : 
     170           0 : void CodeHighlighter::highlightBlock(const QString& text)
     171             : {
     172           0 :   for (const HighlightingRule& rule : highlightingRules)
     173             :   {
     174           0 :     QRegExp expression(rule.pattern);
     175           0 :     int index = expression.indexIn(text);
     176           0 :     while (index >= 0)
     177             :     {
     178           0 :       int length = expression.matchedLength();
     179           0 :       setFormat(index, length, rule.format);
     180           0 :       index = expression.indexIn(text, index + length);
     181             :     }
     182             :   }
     183           0 : }
     184             : 
     185           0 : LineNumberArea::LineNumberArea(CodeEditor* editor)
     186           0 :     : QWidget(editor), codeEditor(editor)
     187             : {
     188           0 : }
     189             : 
     190           0 : QSize LineNumberArea::sizeHint() const
     191             : {
     192           0 :   return QSize(codeEditor->lineNumberAreaWidth(), 0);
     193             : }
     194             : 
     195           0 : void LineNumberArea::paintEvent(QPaintEvent* event)
     196             : {
     197           0 :   codeEditor->lineNumberAreaPaintEvent(event);
     198           0 : }
     199             : 
     200           0 : CodeEditor::CodeEditor(QWidget* parent) : QPlainTextEdit(parent)
     201             : {
     202           0 :   lightPalette = hasLightBackground(this);
     203             : 
     204             :   /* set the font used (monospaced) */
     205           0 :   codeFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
     206           0 :   codeFont.setWeight(QFont::Light);
     207           0 :   lineNumberFont = this->font();
     208             : 
     209             :   /* set the selection colour while inactive the same as when active */
     210           0 :   QPalette palette = this->palette();
     211           0 :   palette.setColor(QPalette::Inactive, QPalette::Highlight,
     212             :                    palette.color(QPalette::Active, QPalette::Highlight));
     213           0 :   palette.setColor(QPalette::Inactive, QPalette::HighlightedText,
     214             :                    palette.color(QPalette::Active, QPalette::HighlightedText));
     215           0 :   this->setPalette(palette);
     216             : 
     217           0 :   setFontSize(13);
     218             : 
     219           0 :   lineNumberArea = new LineNumberArea(this);
     220           0 :   updateLineNumberAreaWidth(0);
     221             : 
     222           0 :   this->setContextMenuPolicy(Qt::CustomContextMenu);
     223           0 :   connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this,
     224             :           SLOT(showContextMenu(const QPoint&)));
     225             : 
     226             :   /* change the line number area when needed */
     227           0 :   connect(this, SIGNAL(blockCountChanged(int)), this,
     228             :           SLOT(updateLineNumberAreaWidth(int)));
     229           0 :   connect(this, SIGNAL(updateRequest(QRect, int)), this,
     230             :           SLOT(updateLineNumberArea(QRect, int)));
     231           0 : }
     232             : 
     233           0 : CodeEditor::~CodeEditor()
     234             : {
     235           0 :   lineNumberArea->deleteLater();
     236           0 :   highlighter->deleteLater();
     237           0 : }
     238             : 
     239           0 : void CodeEditor::setPurpose(bool isSpecificationEditor)
     240             : {
     241           0 :   this->isSpecificationEditor = isSpecificationEditor;
     242           0 :   changeHighlightingRules();
     243           0 : }
     244             : 
     245           0 : void CodeEditor::setFontSize(int pixelSize)
     246             : {
     247           0 :   codeFont.setPixelSize(pixelSize);
     248           0 :   this->setFont(codeFont);
     249           0 :   lineNumberFont.setPixelSize(pixelSize);
     250             : 
     251             :   /* set the tab width to 4 characters */
     252           0 :   QFontMetrics codeFontMetrics = QFontMetrics(codeFont);
     253           0 :   this->setTabStopWidth(codeFontMetrics.width("1234"));
     254           0 : }
     255             : 
     256           0 : void CodeEditor::changeHighlightingRules()
     257             : {
     258           0 :   highlighter = new CodeHighlighter(isSpecificationEditor, lightPalette,
     259           0 :                                     this->document());
     260           0 : }
     261             : 
     262           0 : void CodeEditor::showContextMenu(const QPoint& position)
     263             : {
     264           0 :   QMenu* contextMenu = this->createStandardContextMenu();
     265           0 :   contextMenu->addSeparator();
     266           0 :   zoomInAction = contextMenu->addAction("Zoom in", this, SLOT(zoomIn()));
     267           0 :   zoomOutAction = contextMenu->addAction("Zoom out", this, SLOT(zoomOut()));
     268           0 :   contextMenu->exec(mapToGlobal(position));
     269           0 :   delete contextMenu;
     270           0 : }
     271             : 
     272           0 : void CodeEditor::highlightCurrentLine()
     273             : {
     274           0 :   QList<QTextEdit::ExtraSelection> extraSelections;
     275           0 :   QTextEdit::ExtraSelection selection;
     276             : 
     277             :   QColor lineColor =
     278           0 :       lightPalette ? QColor(Qt::lightGray) : QColor(64, 64, 64);
     279             : 
     280           0 :   selection.format.setBackground(lineColor);
     281           0 :   selection.format.setProperty(QTextFormat::FullWidthSelection, true);
     282           0 :   selection.cursor = textCursor();
     283           0 :   selection.cursor.clearSelection();
     284           0 :   extraSelections.append(selection);
     285             : 
     286           0 :   setExtraSelections(extraSelections);
     287           0 : }
     288             : 
     289           0 : void CodeEditor::paintEvent(QPaintEvent* event)
     290             : {
     291             :   /* highlight the line the cursor is on when in focus */
     292           0 :   if (this->hasFocus())
     293             :   {
     294           0 :     highlightCurrentLine();
     295             :   }
     296           0 :   QPlainTextEdit::paintEvent(event);
     297           0 : }
     298             : 
     299           0 : void CodeEditor::keyPressEvent(QKeyEvent* event)
     300             : {
     301             :   /* zoom in in case of Ctrl++ or Ctrl+=, zoom out in case of Ctrl +- */
     302           0 :   if (event->matches(QKeySequence::ZoomIn) ||
     303           0 :       (event->modifiers() == Qt::ControlModifier &&
     304           0 :        event->key() == Qt::Key_Equal))
     305             :   {
     306           0 :     zoomIn();
     307             :   }
     308           0 :   else if (event->matches(QKeySequence::ZoomOut))
     309             :   {
     310           0 :     zoomOut();
     311             :   }
     312             :   else
     313             :   {
     314           0 :     QPlainTextEdit::keyPressEvent(event);
     315             :   }
     316           0 : }
     317             : 
     318           0 : void CodeEditor::wheelEvent(QWheelEvent* event)
     319             : {
     320             :   /* zoom in in case of Ctrl+scrollup, zoom out in case of Ctrl+scrolldown */
     321           0 :   if (event->modifiers() == Qt::ControlModifier)
     322             :   {
     323           0 :     int numZooms = event->angleDelta().ry() / 120;
     324           0 :     if (numZooms > 0)
     325             :     {
     326           0 :       zoomIn(numZooms);
     327             :     }
     328           0 :     else if (numZooms < 0)
     329             :     {
     330           0 :       zoomOut(-numZooms);
     331             :     }
     332             :   }
     333             :   else
     334             :   {
     335           0 :     QPlainTextEdit::wheelEvent(event);
     336             :   }
     337           0 : }
     338             : 
     339           0 : void CodeEditor::changeEvent(QEvent* event)
     340             : {
     341           0 :   if (event->type() == QEvent::PaletteChange)
     342             :   {
     343           0 :     lightPalette = hasLightBackground(this);
     344           0 :     changeHighlightingRules();
     345             :   }
     346           0 :   QPlainTextEdit::changeEvent(event);
     347           0 : }
     348             : 
     349           0 : void CodeEditor::deleteChar()
     350             : {
     351           0 :   this->textCursor().deleteChar();
     352           0 : }
     353             : 
     354           0 : void CodeEditor::zoomIn(int range)
     355             : {
     356           0 :   setFontSize(codeFont.pixelSize() + range);
     357           0 : }
     358             : 
     359           0 : void CodeEditor::zoomOut(int range)
     360             : {
     361           0 :   setFontSize(codeFont.pixelSize() - range);
     362           0 : }
     363             : 
     364           0 : int CodeEditor::lineNumberAreaWidth()
     365             : {
     366           0 :   int digits = 1;
     367           0 :   int max = std::max(1, blockCount());
     368           0 :   while (max >= 10)
     369             :   {
     370           0 :     max /= 10;
     371           0 :     ++digits;
     372             :   }
     373             : 
     374           0 :   return 3 + QFontMetrics(lineNumberFont).width("9") * digits;
     375             : }
     376             : 
     377           0 : void CodeEditor::updateLineNumberAreaWidth(int)
     378             : {
     379           0 :   setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
     380           0 : }
     381             : 
     382           0 : void CodeEditor::updateLineNumberArea(const QRect& rect, int dy)
     383             : {
     384           0 :   if (dy)
     385             :   {
     386           0 :     lineNumberArea->scroll(0, dy);
     387             :   }
     388             :   else
     389             :   {
     390           0 :     lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
     391             :   }
     392             : 
     393           0 :   if (rect.contains(viewport()->rect()))
     394             :   {
     395           0 :     updateLineNumberAreaWidth(0);
     396             :   }
     397           0 : }
     398             : 
     399           0 : void CodeEditor::resizeEvent(QResizeEvent* e)
     400             : {
     401           0 :   QPlainTextEdit::resizeEvent(e);
     402             : 
     403           0 :   QRect cr = contentsRect();
     404           0 :   lineNumberArea->setGeometry(
     405           0 :       QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
     406           0 : }
     407             : 
     408           0 : void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent* event)
     409             : {
     410           0 :   QPainter painter(lineNumberArea);
     411           0 :   painter.fillRect(event->rect(), lightPalette ? Qt::lightGray : QColor(64, 64, 64));
     412             : 
     413           0 :   QTextBlock block = firstVisibleBlock();
     414           0 :   int blockNumber = block.blockNumber();
     415           0 :   int top = (int)blockBoundingGeometry(block).translated(contentOffset()).top();
     416           0 :   int bottom = top + (int)blockBoundingRect(block).height();
     417           0 :   int lineNumberHeight = QFontMetrics(lineNumberFont).height();
     418             : 
     419           0 :   while (block.isValid() && top <= event->rect().bottom())
     420             :   {
     421           0 :     if (block.isVisible() && bottom >= event->rect().top())
     422             :     {
     423           0 :       QString number = QString::number(blockNumber + 1);
     424           0 :       painter.setPen(lightPalette ? Qt::black : Qt::white);
     425           0 :       painter.setFont(lineNumberFont);
     426           0 :       painter.drawText(-2, top, lineNumberArea->width(), lineNumberHeight,
     427           0 :                        Qt::AlignRight | Qt::AlignBottom, number);
     428             :     }
     429             : 
     430           0 :     block = block.next();
     431           0 :     top = bottom;
     432           0 :     bottom = top + (int)blockBoundingRect(block).height();
     433           0 :     ++blockNumber;
     434             :   }
     435           0 : }

Generated by: LCOV version 1.13