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