1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3     SPDX-FileCopyrightText: 2016 Ivan Lakhtanov <ivan.lakhtanov@gmail.com>
4 */
5 
6 #include "juliahighlighter.h"
7 #include "juliakeywords.h"
8 #include "juliasession.h"
9 
10 #include <climits>
11 #include <QTextEdit>
12 #include <QDebug>
13 
JuliaHighlighter(QObject * parent,JuliaSession * session)14 JuliaHighlighter::JuliaHighlighter(QObject *parent, JuliaSession* session)
15     : Cantor::DefaultHighlighter(parent, session)
16 {
17     addKeywords(JuliaKeywords::instance()->keywords());
18     addVariables(JuliaKeywords::instance()->variables());
19     addFunctions(JuliaKeywords::instance()->functions());
20 }
21 
highlightBlock(const QString & text)22 void JuliaHighlighter::highlightBlock(const QString &text)
23 {
24     if (skipHighlighting(text)) {
25         return;
26     }
27 
28     // Do some backend independent highlighting (brackets etc.)
29     DefaultHighlighter::highlightBlock(text);
30 
31     // Now we are about to make correct strings and comments highlighting
32     //
33     // Main idea: as soon as string starts comment or anything else cant start
34     // until current string ends. The same with comment, except '#' comment
35     // that ends by newline
36     //
37     // To pass information to next block, we are using next states
38     const int IN_MULTILINE_COMMENT = 1;
39     const int IN_CHARACTER = 2;
40     const int IN_SINGLE_QUOTE_STRING = 4;
41     const int IN_TRIPLE_QUOTE_STRING = 8;
42 
43     // Markers of scopes start, ends
44     static const QRegularExpression multiLineCommentStart(QStringLiteral("#="));
45     static const QRegularExpression multiLineCommentEnd(QStringLiteral("=#"));
46     static const QRegularExpression characterStartEnd(QStringLiteral("'"));
47     static const QRegularExpression singleQuoteStringStartEnd(QStringLiteral("\""));
48     static const QRegularExpression tripleQuoteStringStartEnd(QStringLiteral("\"\"\""));
49     static const QRegularExpression singleLineCommentStart(QStringLiteral("#(?!=)"));
50 
51     // Get current state
52     int state = previousBlockState();
53     if (state == -1) {
54         state = 0;
55     }
56 
57     // This 4 arrays establish matching between state, start marker, end marker
58     // and format to apply
59     QList<int> flags = {
60         IN_TRIPLE_QUOTE_STRING,
61         IN_SINGLE_QUOTE_STRING,
62         IN_CHARACTER,
63         IN_MULTILINE_COMMENT
64     };
65     const QVector<QRegularExpression> regexps_starts = {
66         tripleQuoteStringStartEnd,
67         singleQuoteStringStartEnd,
68         characterStartEnd,
69         multiLineCommentStart
70     };
71     const QVector<QRegularExpression> regexps_ends = {
72         tripleQuoteStringStartEnd,
73         singleQuoteStringStartEnd,
74         characterStartEnd,
75         multiLineCommentEnd
76     };
77     QList<QTextCharFormat> formats = {
78         stringFormat(),
79         stringFormat(),
80         stringFormat(),
81         commentFormat()
82     };
83 
84     int pos = 0; // current position in block
85     while (pos < text.length()) {
86         // Trying to close current environments
87         bool triggered = false;
88         for (int i = 0; i < flags.size() && !triggered; i++) {
89             int flag = flags[i];
90             QTextCharFormat &format = formats[i];
91             if (state & flag) { // Found current state
92                 // find where end marker is
93                 const QRegularExpressionMatch match = regexps_ends.at(i).match(text, pos);
94                 const int new_pos = match.capturedStart(0);
95                 int length;
96                 if (new_pos == -1) {
97                     // not in this block, highlight till the end
98                     length = text.length() - pos;
99                 } else {
100                     // highlight untill the marker and modify state
101                     length = new_pos - pos + match.capturedLength(0);
102                     state -= flag;
103                 }
104                 // Apply format to the found area
105                 setFormat(pos, length, format);
106                 pos = pos + length;
107                 triggered = true;
108             }
109         }
110         if (triggered) { // We have done something move to next iteration
111             continue;
112         }
113 
114         // Now we should found the scope that start the closest to current
115         // position
116         QRegularExpressionMatch match; // closest marker
117         int minPos = INT_MAX; // closest pos
118         int minIdx = -1; // closest scope index
119         for (int i = 0; i < regexps_starts.size(); i++) {
120             match = regexps_starts.at(i).match(text, pos);
121             const int newPos = match.capturedStart(0);
122             if (newPos != -1) {
123                 minPos = qMin(minPos, newPos);
124                 minIdx = i;
125             }
126         }
127 
128         // Check where single line comment starts
129         const int singleLineCommentStartPos = text.indexOf(singleLineCommentStart, pos);
130 
131         if (singleLineCommentStartPos != -1
132                 && singleLineCommentStartPos < minPos) {
133             // single line comment starts earlier
134             setFormat(singleLineCommentStartPos, text.length() - singleLineCommentStartPos, commentFormat());
135             break;
136         } else if (match.hasMatch()) {
137             // We are going to another scope
138             state += flags[minIdx];
139             pos = minPos +  match.capturedLength(0);
140             setFormat(minPos, match.capturedLength(0), formats[minIdx]);
141         } else { // There is nothing to highlight
142             break;
143         }
144     }
145 
146     setCurrentBlockState(state);
147 }
148 
nonSeparatingCharacters() const149 QString JuliaHighlighter::nonSeparatingCharacters() const
150 {
151     return QLatin1String("[\\w¡-ﻼ!]");
152 }
153