LCOV - code coverage report
Current view: top level - gui/source - codeeditor.cpp (source / functions) Hit Total Coverage
Test: mcrl2_coverage.info.cleaned Lines: 0 222 0.0 %
Date: 2020-02-19 00:44:21 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           0 :   setFontSize(13);
     210             : 
     211           0 :   lineNumberArea = new LineNumberArea(this);
     212           0 :   updateLineNumberAreaWidth(0);
     213             : 
     214           0 :   this->setContextMenuPolicy(Qt::CustomContextMenu);
     215           0 :   connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this,
     216             :           SLOT(showContextMenu(const QPoint&)));
     217             : 
     218             :   /* change the line number area when needed */
     219           0 :   connect(this, SIGNAL(blockCountChanged(int)), this,
     220             :           SLOT(updateLineNumberAreaWidth(int)));
     221           0 :   connect(this, SIGNAL(updateRequest(QRect, int)), this,
     222             :           SLOT(updateLineNumberArea(QRect, int)));
     223           0 : }
     224             : 
     225           0 : CodeEditor::~CodeEditor()
     226             : {
     227           0 :   lineNumberArea->deleteLater();
     228           0 :   highlighter->deleteLater();
     229           0 : }
     230             : 
     231           0 : void CodeEditor::setPurpose(bool isSpecificationEditor)
     232             : {
     233           0 :   this->isSpecificationEditor = isSpecificationEditor;
     234           0 :   changeHighlightingRules();
     235           0 : }
     236             : 
     237           0 : void CodeEditor::setFontSize(int pixelSize)
     238             : {
     239           0 :   codeFont.setPixelSize(pixelSize);
     240           0 :   this->setFont(codeFont);
     241           0 :   lineNumberFont.setPixelSize(pixelSize);
     242             : 
     243             :   /* set the tab width to 4 characters */
     244           0 :   QFontMetrics codeFontMetrics = QFontMetrics(codeFont);
     245           0 :   this->setTabStopWidth(codeFontMetrics.width("1234"));
     246           0 : }
     247             : 
     248           0 : void CodeEditor::changeHighlightingRules()
     249             : {
     250           0 :   highlighter = new CodeHighlighter(isSpecificationEditor, lightPalette,
     251           0 :                                     this->document());
     252           0 : }
     253             : 
     254           0 : void CodeEditor::showContextMenu(const QPoint& position)
     255             : {
     256           0 :   QMenu* contextMenu = this->createStandardContextMenu();
     257           0 :   contextMenu->addSeparator();
     258           0 :   zoomInAction = contextMenu->addAction("Zoom in", this, SLOT(zoomIn()));
     259           0 :   zoomOutAction = contextMenu->addAction("Zoom out", this, SLOT(zoomOut()));
     260           0 :   contextMenu->exec(mapToGlobal(position));
     261           0 :   delete contextMenu;
     262           0 : }
     263             : 
     264           0 : void CodeEditor::highlightCurrentLine()
     265             : {
     266           0 :   QList<QTextEdit::ExtraSelection> extraSelections;
     267           0 :   QTextEdit::ExtraSelection selection;
     268             : 
     269             :   QColor lineColor =
     270           0 :       lightPalette ? QColor(Qt::lightGray) : QColor(64, 64, 64);
     271             : 
     272           0 :   selection.format.setBackground(lineColor);
     273           0 :   selection.format.setProperty(QTextFormat::FullWidthSelection, true);
     274           0 :   selection.cursor = textCursor();
     275           0 :   selection.cursor.clearSelection();
     276           0 :   extraSelections.append(selection);
     277             : 
     278           0 :   setExtraSelections(extraSelections);
     279           0 : }
     280             : 
     281           0 : void CodeEditor::paintEvent(QPaintEvent* event)
     282             : {
     283             :   /* highlight the line the cursor is on when in focus */
     284           0 :   if (this->hasFocus())
     285             :   {
     286           0 :     highlightCurrentLine();
     287             :   }
     288           0 :   QPlainTextEdit::paintEvent(event);
     289           0 : }
     290             : 
     291           0 : void CodeEditor::keyPressEvent(QKeyEvent* event)
     292             : {
     293             :   /* zoom in in case of Ctrl++ or Ctrl+=, zoom out in case of Ctrl +- */
     294           0 :   if (event->matches(QKeySequence::ZoomIn) ||
     295           0 :       (event->modifiers() == Qt::ControlModifier &&
     296           0 :        event->key() == Qt::Key_Equal))
     297             :   {
     298           0 :     zoomIn();
     299             :   }
     300           0 :   else if (event->matches(QKeySequence::ZoomOut))
     301             :   {
     302           0 :     zoomOut();
     303             :   }
     304             :   else
     305             :   {
     306           0 :     QPlainTextEdit::keyPressEvent(event);
     307             :   }
     308           0 : }
     309             : 
     310           0 : void CodeEditor::wheelEvent(QWheelEvent* event)
     311             : {
     312             :   /* zoom in in case of Ctrl+scrollup, zoom out in case of Ctrl+scrolldown */
     313           0 :   if (event->modifiers() == Qt::ControlModifier)
     314             :   {
     315           0 :     int numZooms = event->angleDelta().ry() / 120;
     316           0 :     if (numZooms > 0)
     317             :     {
     318           0 :       zoomIn(numZooms);
     319             :     }
     320           0 :     else if (numZooms < 0)
     321             :     {
     322           0 :       zoomOut(-numZooms);
     323             :     }
     324             :   }
     325             :   else
     326             :   {
     327           0 :     QPlainTextEdit::wheelEvent(event);
     328             :   }
     329           0 : }
     330             : 
     331           0 : void CodeEditor::changeEvent(QEvent* event)
     332             : {
     333           0 :   if (event->type() == QEvent::PaletteChange)
     334             :   {
     335           0 :     lightPalette = hasLightBackground(this);
     336           0 :     changeHighlightingRules();
     337             :   }
     338           0 :   QPlainTextEdit::changeEvent(event);
     339           0 : }
     340             : 
     341           0 : void CodeEditor::deleteChar()
     342             : {
     343           0 :   this->textCursor().deleteChar();
     344           0 : }
     345             : 
     346           0 : void CodeEditor::zoomIn(int range)
     347             : {
     348           0 :   setFontSize(codeFont.pixelSize() + range);
     349           0 : }
     350             : 
     351           0 : void CodeEditor::zoomOut(int range)
     352             : {
     353           0 :   setFontSize(codeFont.pixelSize() - range);
     354           0 : }
     355             : 
     356           0 : int CodeEditor::lineNumberAreaWidth()
     357             : {
     358           0 :   int digits = 1;
     359           0 :   int max = std::max(1, blockCount());
     360           0 :   while (max >= 10)
     361             :   {
     362           0 :     max /= 10;
     363           0 :     ++digits;
     364             :   }
     365             : 
     366           0 :   return 3 + QFontMetrics(lineNumberFont).width("9") * digits;
     367             : }
     368             : 
     369           0 : void CodeEditor::updateLineNumberAreaWidth(int)
     370             : {
     371           0 :   setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
     372           0 : }
     373             : 
     374           0 : void CodeEditor::updateLineNumberArea(const QRect& rect, int dy)
     375             : {
     376           0 :   if (dy)
     377             :   {
     378           0 :     lineNumberArea->scroll(0, dy);
     379             :   }
     380             :   else
     381             :   {
     382           0 :     lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
     383             :   }
     384             : 
     385           0 :   if (rect.contains(viewport()->rect()))
     386             :   {
     387           0 :     updateLineNumberAreaWidth(0);
     388             :   }
     389           0 : }
     390             : 
     391           0 : void CodeEditor::resizeEvent(QResizeEvent* e)
     392             : {
     393           0 :   QPlainTextEdit::resizeEvent(e);
     394             : 
     395           0 :   QRect cr = contentsRect();
     396           0 :   lineNumberArea->setGeometry(
     397           0 :       QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
     398           0 : }
     399             : 
     400           0 : void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent* event)
     401             : {
     402           0 :   QPainter painter(lineNumberArea);
     403           0 :   painter.fillRect(event->rect(), lightPalette ? Qt::lightGray : QColor(64, 64, 64));
     404             : 
     405           0 :   QTextBlock block = firstVisibleBlock();
     406           0 :   int blockNumber = block.blockNumber();
     407           0 :   int top = (int)blockBoundingGeometry(block).translated(contentOffset()).top();
     408           0 :   int bottom = top + (int)blockBoundingRect(block).height();
     409           0 :   int lineNumberHeight = QFontMetrics(lineNumberFont).height();
     410             : 
     411           0 :   while (block.isValid() && top <= event->rect().bottom())
     412             :   {
     413           0 :     if (block.isVisible() && bottom >= event->rect().top())
     414             :     {
     415           0 :       QString number = QString::number(blockNumber + 1);
     416           0 :       painter.setPen(lightPalette ? Qt::black : Qt::white);
     417           0 :       painter.setFont(lineNumberFont);
     418           0 :       painter.drawText(-2, top, lineNumberArea->width(), lineNumberHeight,
     419           0 :                        Qt::AlignRight | Qt::AlignBottom, number);
     420             :     }
     421             : 
     422           0 :     block = block.next();
     423           0 :     top = bottom;
     424           0 :     bottom = top + (int)blockBoundingRect(block).height();
     425           0 :     ++blockNumber;
     426             :   }
     427           0 : }

Generated by: LCOV version 1.13