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, &quoteMatch);
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