1 /*
2 * Copyright (C) Pedram Pourang (aka Tsu Jan) 2021 <tsujan2000@gmail.com>
3 *
4 * FeatherPad is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * FeatherPad is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 * See the GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * @license GPL-3.0+ <https://spdx.org/licenses/GPL-3.0+.html>
18 */
19
20 #include "highlighter.h"
21
22 namespace FeatherPad {
23
24 static const QRegularExpression pascalCommentStartExp ("\\(\\*|\\{");
25
isPascalQuoted(const QString & text,const int index,const int start) const26 bool Highlighter::isPascalQuoted (const QString &text, const int index,
27 const int start) const
28 {
29 if (index < 0) return false;
30 int N = 0;
31 int indx = start;
32 while ((indx = text.indexOf (quoteMark, indx)) > -1 && indx < index)
33 {
34 if (format (indx) != commentFormat && format (indx) != urlFormat
35 && format (indx) != regexFormat)
36 {
37 ++ N;
38 }
39 ++ indx;
40 }
41 return N % 2 != 0;
42 }
43 /*************************/
isPascalMLCommented(const QString & text,const int index,const int start) const44 bool Highlighter::isPascalMLCommented (const QString &text, const int index,
45 const int start) const
46 {
47 if (index < 0 || start < 0 || index < start)
48 return false;
49
50 int prevState = previousBlockState();
51 bool res = false;
52 int pos = start - 1;
53 int N;
54 QRegularExpressionMatch commentMatch;
55 QRegularExpression commentExpression;
56 if (pos >= 0 || prevState != commentState)
57 {
58 N = 0;
59 commentExpression = pascalCommentStartExp;
60 }
61 else
62 {
63 N = 1;
64 res = true;
65 bool oldComment = false;
66 QTextBlock prevBlock = currentBlock().previous();
67 if (prevBlock.isValid())
68 {
69 TextBlockData *prevData = static_cast<TextBlockData *>(prevBlock.userData());
70 oldComment = prevData && prevData->getProperty();
71 }
72 if (oldComment)
73 commentExpression.setPattern ("\\*\\)");
74 else
75 commentExpression.setPattern ("\\}");
76 }
77
78 while ((pos = text.indexOf (commentExpression, pos + 1, &commentMatch)) >= 0)
79 {
80 /* skip formatted quotations */
81 if (format (pos) == quoteFormat) continue;
82
83 ++N;
84
85 if (index < pos + (N % 2 == 0 ? commentMatch.capturedLength() : 0))
86 {
87 if (N % 2 == 0) res = true;
88 else res = false;
89 break;
90 }
91
92 if (N % 2 != 0)
93 {
94 if (text.at (pos) == '(')
95 commentExpression.setPattern ("\\*\\)");
96 else
97 commentExpression.setPattern ("\\}");
98 res = true;
99 }
100 else
101 {
102 commentExpression = pascalCommentStartExp;
103 res = false;
104 }
105 }
106
107 return res;
108
109 }
110 /*************************/
singleLinePascalComment(const QString & text,const int start)111 void Highlighter::singleLinePascalComment (const QString &text, const int start)
112 {
113 QRegularExpression commentExp ("//.*");
114 int startIndex = qMax (start, 0);
115 startIndex = text.indexOf (commentExp, startIndex);
116 /* skip quoted comments */
117 while (format (startIndex) == quoteFormat // only for multiLinePascalComment()
118 || isPascalQuoted (text, startIndex, qMax (start, 0)))
119 {
120 startIndex = text.indexOf (commentExp, startIndex + 1);
121 }
122 if (startIndex > -1)
123 {
124 int l = text.length();
125 setFormat (startIndex, l - startIndex, commentFormat);
126
127 /* also format urls and email addresses inside the comment */
128 QString str = text.mid (startIndex, l - startIndex);
129 int pIndex = 0;
130 QRegularExpressionMatch urlMatch;
131 while ((pIndex = str.indexOf (urlPattern, pIndex, &urlMatch)) > -1)
132 {
133 setFormat (pIndex + startIndex, urlMatch.capturedLength(), urlFormat);
134 pIndex += urlMatch.capturedLength();
135 }
136 /* format note patterns too */
137 pIndex = 0;
138 while ((pIndex = str.indexOf (notePattern, pIndex, &urlMatch)) > -1)
139 {
140 if (format (pIndex + startIndex) != urlFormat)
141 setFormat (pIndex + startIndex, urlMatch.capturedLength(), noteFormat);
142 pIndex += urlMatch.capturedLength();
143 }
144 }
145 }
146 /*************************/
pascalQuote(const QString & text,const int start)147 void Highlighter::pascalQuote (const QString &text, const int start)
148 {
149 int index = start;
150 index = text.indexOf (quoteMark, index);
151 /* skip escaped start quotes and all comments */
152 while (isPascalMLCommented (text, index))
153 index = text.indexOf (quoteMark, index + 1);
154 while (format (index) == commentFormat || format (index) == urlFormat) // single-line
155 index = text.indexOf (quoteMark, index + 1);
156
157 while (index >= 0)
158 {
159 QRegularExpressionMatch quoteMatch;
160 int endIndex = text.indexOf (quoteMark, index + 1, "eMatch);
161 if (endIndex == -1)
162 {
163 setFormat (index, text.length() - index, quoteFormat);
164 return;
165 }
166
167 int quoteLength = endIndex - index
168 + quoteMatch.capturedLength();
169 setFormat (index, quoteLength, quoteFormat);
170
171 index = text.indexOf (quoteMark, index + quoteLength);
172 while (isPascalMLCommented (text, index, endIndex + 1))
173 index = text.indexOf (quoteMark, index + 1);
174 while (format (index) == commentFormat || format (index) == urlFormat)
175 index = text.indexOf (quoteMark, index + 1);
176 }
177 }
178 /*************************/
multiLinePascalComment(const QString & text)179 void Highlighter::multiLinePascalComment (const QString &text)
180 {
181 int prevState = previousBlockState();
182 int startIndex = 0;
183 bool oldComment = false;
184
185 QRegularExpression commentEndExp;
186 QRegularExpressionMatch startMatch;
187
188 if (prevState == commentState)
189 {
190 QTextBlock prevBlock = currentBlock().previous();
191 if (prevBlock.isValid())
192 {
193 TextBlockData *prevData = static_cast<TextBlockData *>(prevBlock.userData());
194 oldComment = prevData && prevData->getProperty();
195 }
196 }
197 else
198 {
199 startIndex = text.indexOf (pascalCommentStartExp, startIndex, &startMatch);
200 /* skip quotations (all formatted to this point) */
201 while (format (startIndex) == quoteFormat)
202 startIndex = text.indexOf (pascalCommentStartExp, startIndex + 1, &startMatch);
203 /* skip single-line comments */
204 if (format (startIndex) == commentFormat || format (startIndex) == urlFormat)
205 return;
206 oldComment = startIndex >= 0 && text.at (startIndex) == '(';
207 }
208
209 while (startIndex >= 0)
210 {
211 int endIndex;
212 QRegularExpressionMatch endMatch;
213 if (oldComment)
214 commentEndExp.setPattern ("\\*\\)");
215 else
216 commentEndExp.setPattern ("\\}");
217
218 if (prevState == commentState && startIndex == 0)
219 endIndex = text.indexOf (commentEndExp, 0, &endMatch);
220 else
221 {
222 endIndex = text.indexOf (commentEndExp,
223 startIndex + startMatch.capturedLength(),
224 &endMatch);
225 }
226
227 /* if there's a comment end ... */
228 if (endIndex >= 0)
229 {
230 /* ... clear the comment format from there to reformat
231 because a single-line comment may have changed now */
232 int badIndex = endIndex + endMatch.capturedLength();
233 bool hadSingleLineComment = false;
234 int i = 0;
235 for (i = badIndex; i < text.length(); ++i)
236 {
237 if (format (i) == commentFormat || format (i) == urlFormat)
238 {
239 setFormat (i, text.length() - i, mainFormat);
240 hadSingleLineComment = true;
241 break;
242 }
243 }
244 singleLinePascalComment (text, badIndex);
245 if (hadSingleLineComment)
246 pascalQuote (text, i);
247 }
248
249 bool compilerDirective (!(startIndex == 0 && prevState == commentState)
250 && text.length() > (startIndex + (oldComment ? 2 : 1))
251 && text.at (startIndex + (oldComment ? 2 : 1)) == '$');
252 int commentLength;
253 if (endIndex == -1)
254 {
255 commentLength = text.length() - startIndex;
256 if (!compilerDirective)
257 {
258 setCurrentBlockState (commentState);
259 if (oldComment)
260 {
261 if (TextBlockData *data = static_cast<TextBlockData *>(currentBlock().userData()))
262 data->setProperty (true);
263 }
264 }
265 }
266 else
267 commentLength = endIndex - startIndex
268 + endMatch.capturedLength();
269
270 setFormat (startIndex, commentLength, compilerDirective ? regexFormat
271 : commentFormat);
272
273 if (!compilerDirective)
274 {
275 /* format urls and email addresses inside the comment */
276 QString str = text.mid (startIndex, commentLength);
277 int pIndex = 0;
278 QRegularExpressionMatch urlMatch;
279 while ((pIndex = str.indexOf (urlPattern, pIndex, &urlMatch)) > -1)
280 {
281 setFormat (pIndex + startIndex, urlMatch.capturedLength(), urlFormat);
282 pIndex += urlMatch.capturedLength();
283 }
284 /* format note patterns too */
285 pIndex = 0;
286 while ((pIndex = str.indexOf (notePattern, pIndex, &urlMatch)) > -1)
287 {
288 if (format (pIndex + startIndex) != urlFormat)
289 setFormat (pIndex + startIndex, urlMatch.capturedLength(), noteFormat);
290 pIndex += urlMatch.capturedLength();
291 }
292 }
293
294 startIndex = text.indexOf (pascalCommentStartExp, startIndex + commentLength, &startMatch);
295 while (format (startIndex) == quoteFormat)
296 startIndex = text.indexOf (pascalCommentStartExp, startIndex + 1, &startMatch);
297 if (format (startIndex) == commentFormat || format (startIndex) == urlFormat)
298 return;
299 oldComment = startIndex >= 0 && text.at (startIndex) == '(';
300 }
301 }
302
303 }
304