File indexing completed on 2024-05-26 11:22:04

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2016 Ivan Lakhtanov <ivan.lakhtanov@gmail.com>
0004 */
0005 
0006 #include "juliahighlighter.h"
0007 #include "juliakeywords.h"
0008 #include "juliasession.h"
0009 
0010 #include <climits>
0011 #include <QTextEdit>
0012 #include <QDebug>
0013 
0014 JuliaHighlighter::JuliaHighlighter(QObject *parent, JuliaSession* session)
0015     : Cantor::DefaultHighlighter(parent, session)
0016 {
0017     addKeywords(JuliaKeywords::instance()->keywords());
0018     addVariables(JuliaKeywords::instance()->variables());
0019     addFunctions(JuliaKeywords::instance()->functions());
0020 }
0021 
0022 void JuliaHighlighter::highlightBlock(const QString &text)
0023 {
0024     if (skipHighlighting(text)) {
0025         return;
0026     }
0027 
0028     // Do some backend independent highlighting (brackets etc.)
0029     DefaultHighlighter::highlightBlock(text);
0030 
0031     // Now we are about to make correct strings and comments highlighting
0032     //
0033     // Main idea: as soon as string starts comment or anything else cant start
0034     // until current string ends. The same with comment, except '#' comment
0035     // that ends by newline
0036     //
0037     // To pass information to next block, we are using next states
0038     const int IN_MULTILINE_COMMENT = 1;
0039     const int IN_CHARACTER = 2;
0040     const int IN_SINGLE_QUOTE_STRING = 4;
0041     const int IN_TRIPLE_QUOTE_STRING = 8;
0042 
0043     // Markers of scopes start, ends
0044     static const QRegularExpression multiLineCommentStart(QStringLiteral("#="));
0045     static const QRegularExpression multiLineCommentEnd(QStringLiteral("=#"));
0046     static const QRegularExpression characterStartEnd(QStringLiteral("'"));
0047     static const QRegularExpression singleQuoteStringStartEnd(QStringLiteral("\""));
0048     static const QRegularExpression tripleQuoteStringStartEnd(QStringLiteral("\"\"\""));
0049     static const QRegularExpression singleLineCommentStart(QStringLiteral("#(?!=)"));
0050 
0051     // Get current state
0052     int state = previousBlockState();
0053     if (state == -1) {
0054         state = 0;
0055     }
0056 
0057     // This 4 arrays establish matching between state, start marker, end marker
0058     // and format to apply
0059     QList<int> flags = {
0060         IN_TRIPLE_QUOTE_STRING,
0061         IN_SINGLE_QUOTE_STRING,
0062         IN_CHARACTER,
0063         IN_MULTILINE_COMMENT
0064     };
0065     const QVector<QRegularExpression> regexps_starts = {
0066         tripleQuoteStringStartEnd,
0067         singleQuoteStringStartEnd,
0068         characterStartEnd,
0069         multiLineCommentStart
0070     };
0071     const QVector<QRegularExpression> regexps_ends = {
0072         tripleQuoteStringStartEnd,
0073         singleQuoteStringStartEnd,
0074         characterStartEnd,
0075         multiLineCommentEnd
0076     };
0077     QList<QTextCharFormat> formats = {
0078         stringFormat(),
0079         stringFormat(),
0080         stringFormat(),
0081         commentFormat()
0082     };
0083 
0084     int pos = 0; // current position in block
0085     while (pos < text.length()) {
0086         // Trying to close current environments
0087         bool triggered = false;
0088         for (int i = 0; i < flags.size() && !triggered; i++) {
0089             int flag = flags[i];
0090             QTextCharFormat &format = formats[i];
0091             if (state & flag) { // Found current state
0092                 // find where end marker is
0093                 const QRegularExpressionMatch match = regexps_ends.at(i).match(text, pos);
0094                 const int new_pos = match.capturedStart(0);
0095                 int length;
0096                 if (new_pos == -1) {
0097                     // not in this block, highlight till the end
0098                     length = text.length() - pos;
0099                 } else {
0100                     // highlight untill the marker and modify state
0101                     length = new_pos - pos + match.capturedLength(0);
0102                     state -= flag;
0103                 }
0104                 // Apply format to the found area
0105                 setFormat(pos, length, format);
0106                 pos = pos + length;
0107                 triggered = true;
0108             }
0109         }
0110         if (triggered) { // We have done something move to next iteration
0111             continue;
0112         }
0113 
0114         // Now we should found the scope that start the closest to current
0115         // position
0116         QRegularExpressionMatch match; // closest marker
0117         int minPos = INT_MAX; // closest pos
0118         int minIdx = -1; // closest scope index
0119         for (int i = 0; i < regexps_starts.size(); i++) {
0120             match = regexps_starts.at(i).match(text, pos);
0121             const int newPos = match.capturedStart(0);
0122             if (newPos != -1) {
0123                 minPos = qMin(minPos, newPos);
0124                 minIdx = i;
0125             }
0126         }
0127 
0128         // Check where single line comment starts
0129         const int singleLineCommentStartPos = text.indexOf(singleLineCommentStart, pos);
0130 
0131         if (singleLineCommentStartPos != -1
0132                 && singleLineCommentStartPos < minPos) {
0133             // single line comment starts earlier
0134             setFormat(singleLineCommentStartPos, text.length() - singleLineCommentStartPos, commentFormat());
0135             break;
0136         } else if (match.hasMatch()) {
0137             // We are going to another scope
0138             state += flags[minIdx];
0139             pos = minPos +  match.capturedLength(0);
0140             setFormat(minPos, match.capturedLength(0), formats[minIdx]);
0141         } else { // There is nothing to highlight
0142             break;
0143         }
0144     }
0145 
0146     setCurrentBlockState(state);
0147 }
0148 
0149 QString JuliaHighlighter::nonSeparatingCharacters() const
0150 {
0151     return QLatin1String("[\\w¡-ﻼ!]");
0152 }