1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "specificrules.h"
35 #include "highlightdefinition.h"
36 #include "keywordlist.h"
37 #include "progressdata.h"
38 #include "reuse.h"
39 #include <QDebug>
40
41 #include <QtCore/QLatin1Char>
42
43 using namespace TextEditor;
44 using namespace Internal;
45
46 namespace {
47
replaceByCaptures(QChar * c,const QStringList & captures)48 void replaceByCaptures(QChar *c, const QStringList &captures)
49 {
50 int index = c->digitValue();
51 if (index > 0) {
52 const QString &capture = captures.at(index);
53 if (!capture.isEmpty())
54 *c = capture.at(0);
55 }
56 }
57
replaceByCaptures(QString * s,const QStringList & captures)58 void replaceByCaptures(QString *s, const QStringList &captures)
59 {
60 static const QLatin1Char kPercent('%');
61
62 int index;
63 int from = 0;
64 while ((index = s->indexOf(kPercent, from)) != -1) {
65 from = index + 1;
66
67 QString accumulator;
68 while (from < s->length() && s->at(from).isDigit()) {
69 accumulator.append(s->at(from));
70 ++from;
71 }
72
73 bool ok;
74 int number = accumulator.toInt(&ok);
75 Q_ASSERT(ok);
76
77 s->replace(index, accumulator.length() + 1, captures.at(number));
78 }
79 }
80 }
81
82 // DetectChar
setChar(const QString & character)83 void DetectCharRule::setChar(const QString &character)
84 { setStartCharacter(&m_char, character); }
85
doReplaceExpressions(const QStringList & captures)86 void DetectCharRule::doReplaceExpressions(const QStringList &captures)
87 { replaceByCaptures(&m_char, captures); }
88
doMatchSucceed(const QString & text,const int length,ProgressData * progress)89 bool DetectCharRule::doMatchSucceed(const QString &text,
90 const int length,
91 ProgressData *progress)
92 {
93 if (matchCharacter(text, length, progress, m_char)) {
94 return true;
95 }
96 return false;
97 // if (matchCharacter(text, length, progress, m_char)) {
98 // return true;
99 // // This is to make code folding have a control flow style look in the case of braces.
100 // // Naturally, this assumes that language definitions use braces with this meaning.
101 // if (m_char == kOpeningBrace && progress->isOnlySpacesSoFar() && !isLookAhead()) {
102 // progress->setOpeningBraceMatchAtFirstNonSpace(true);
103 // } else if (m_char == kClosingBrace &&
104 // !text.right(length - progress->offset()).trimmed().isEmpty()) {
105 // progress->setClosingBraceMatchAtNonEnd(true);
106 // }
107 // return true;
108 // }
109 // return false;
110
111 // if (matchCharacter(text, length, progress, m_char)) {
112 // qDebug() << m_char << text;
113 // if (text.trimmed() == "} else {" && m_char == '}') {
114 // //qDebug() << "error";
115 // progress->setClosingBraceMatchAtNonEnd(true);
116 // }
117 // return true;
118 // // This is to make code folding have a control flow style look in the case of braces.
119 // // Naturally, this assumes that language definitions use braces with this meaning.
120 // if ( (m_char == '{' || m_char == '(' || m_char == '[')
121 // && progress->isOnlySpacesSoFar() && !isLookAhead()) {
122 // progress->setOpeningBraceMatchAtFirstNonSpace(true);
123 // } else if ( (m_char == '}' || m_char == ')' || m_char == ']') &&
124 // !text.right(length - progress->offset()).trimmed().isEmpty()) {
125 // progress->setClosingBraceMatchAtNonEnd(true);
126 // }
127 // if (m_char == kOpeningBrace && progress->isOnlySpacesSoFar() && !isLookAhead()) {
128 // progress->setOpeningBraceMatchAtFirstNonSpace(true);
129 // } else if (m_char == kClosingBrace &&
130 // !text.right(length - progress->offset()).trimmed().isEmpty()) {
131 // progress->setClosingBraceMatchAtNonEnd(true);
132 // }
133 // return true;
134 // }
135 // return false;
136 }
137
138 // Detect2Chars
setChar(const QString & character)139 void Detect2CharsRule::setChar(const QString &character)
140 { setStartCharacter(&m_char, character); }
141
setChar1(const QString & character)142 void Detect2CharsRule::setChar1(const QString &character)
143 { setStartCharacter(&m_char1, character); }
144
doReplaceExpressions(const QStringList & captures)145 void Detect2CharsRule::doReplaceExpressions(const QStringList &captures)
146 {
147 replaceByCaptures(&m_char, captures);
148 replaceByCaptures(&m_char1, captures);
149 }
150
doMatchSucceed(const QString & text,const int length,ProgressData * progress)151 bool Detect2CharsRule::doMatchSucceed(const QString &text,
152 const int length,
153 ProgressData *progress)
154 {
155 if (matchCharacter(text, length, progress, m_char)) {
156 if (progress->offset() < length && matchCharacter(text, length, progress, m_char1, false))
157 return true;
158 else
159 progress->restoreOffset();
160 }
161
162 return false;
163 }
164
165 // AnyChar
setCharacterSet(const QString & s)166 void AnyCharRule::setCharacterSet(const QString &s)
167 { m_characterSet = s; }
168
doMatchSucceed(const QString & text,const int length,ProgressData * progress)169 bool AnyCharRule::doMatchSucceed(const QString &text,
170 const int length,
171 ProgressData *progress)
172 {
173 Q_UNUSED(length)
174
175 if (m_characterSet.contains(text.at(progress->offset()))) {
176 progress->incrementOffset();
177 return true;
178 }
179
180 return false;
181 }
182
183 // StringDetect
setString(const QString & s)184 void StringDetectRule::setString(const QString &s)
185 {
186 m_string = s;
187 m_length = m_string.length();
188 }
189
setInsensitive(const QString & insensitive)190 void StringDetectRule::setInsensitive(const QString &insensitive)
191 { m_caseSensitivity = toCaseSensitivity(!toBool(insensitive)); }
192
doReplaceExpressions(const QStringList & captures)193 void StringDetectRule::doReplaceExpressions(const QStringList &captures)
194 {
195 replaceByCaptures(&m_string, captures);
196 m_length = m_string.length();
197 }
198
doMatchSucceed(const QString & text,const int length,ProgressData * progress)199 bool StringDetectRule::doMatchSucceed(const QString &text,
200 const int length,
201 ProgressData *progress)
202 {
203 if (length - progress->offset() >= m_length) {
204 QString candidate = text.fromRawData(text.unicode() + progress->offset(), m_length);
205 if (candidate.compare(m_string, m_caseSensitivity) == 0) {
206 progress->incrementOffset(m_length);
207 return true;
208 }
209 }
210
211 return false;
212 }
213
214 // RegExpr
setPattern(const QString & pattern)215 void RegExprRule::setPattern(const QString &pattern)
216 {
217 if (pattern.startsWith(QLatin1Char('^')))
218 m_onlyBegin = true;
219 m_expression.setPattern(pattern);
220 }
221
setInsensitive(const QString & insensitive)222 void RegExprRule::setInsensitive(const QString &insensitive)
223 { m_expression.setCaseSensitivity(toCaseSensitivity(!toBool(insensitive))); }
224
setMinimal(const QString & minimal)225 void RegExprRule::setMinimal(const QString &minimal)
226 { m_expression.setMinimal(toBool(minimal)); }
227
doReplaceExpressions(const QStringList & captures)228 void RegExprRule::doReplaceExpressions(const QStringList &captures)
229 {
230 QString s = m_expression.pattern();
231 replaceByCaptures(&s, captures);
232 m_expression.setPattern(s);
233 }
234
doProgressFinished()235 void RegExprRule::doProgressFinished()
236 {
237 m_isCached = false;
238 }
239
isExactMatch(ProgressData * progress)240 bool RegExprRule::isExactMatch(ProgressData *progress)
241 {
242 if (progress->offset() == m_offset && m_length > 0) {
243 progress->incrementOffset(m_length);
244 progress->setCaptures(m_captures);
245 return true;
246 }
247 return false;
248 }
249
doMatchSucceed(const QString & text,const int length,ProgressData * progress)250 bool RegExprRule::doMatchSucceed(const QString &text,
251 const int length,
252 ProgressData *progress)
253 {
254 Q_UNUSED(length)
255
256 // A regular expression match is considered valid if it happens at the current position
257 // and if the match length is not zero.
258 const int offset = progress->offset();
259 if (offset > 0 && m_onlyBegin)
260 return false;
261
262 if (m_isCached) {
263 if (offset < m_offset || m_offset == -1 || m_length == 0)
264 return false;
265 if (isExactMatch(progress))
266 return true;
267 }
268
269 m_offset = m_expression.indexIn(text, offset, QRegExp::CaretAtOffset);
270 m_length = m_expression.matchedLength();
271 m_captures = m_expression.capturedTexts();
272
273 if (isExactMatch(progress))
274 return true;
275
276 m_isCached = true;
277 progress->trackRule(this);
278
279 return false;
280 }
281
282 // Keyword
KeywordRule(const QSharedPointer<HighlightDefinition> & definition)283 KeywordRule::KeywordRule(const QSharedPointer<HighlightDefinition> &definition) :
284 m_overrideGlobal(false),
285 m_localCaseSensitivity(Qt::CaseSensitive)
286 {
287 setDefinition(definition);
288 }
289
~KeywordRule()290 KeywordRule::~KeywordRule()
291 {}
292
setInsensitive(const QString & insensitive)293 void KeywordRule::setInsensitive(const QString &insensitive)
294 {
295 if (!insensitive.isEmpty()) {
296 m_overrideGlobal = true;
297 m_localCaseSensitivity = toCaseSensitivity(!toBool(insensitive));
298 }
299 }
300
setList(const QString & listName)301 void KeywordRule::setList(const QString &listName)
302 { m_list = definition()->keywordList(listName); }
303
doMatchSucceed(const QString & text,const int length,ProgressData * progress)304 bool KeywordRule::doMatchSucceed(const QString &text,
305 const int length,
306 ProgressData *progress)
307 {
308 int current = progress->offset();
309
310 if (current > 0 && !definition()->isDelimiter(text.at(current - 1)))
311 return false;
312 if (definition()->isDelimiter(text.at(current)))
313 return false;
314
315 while (current < length && !definition()->isDelimiter(text.at(current)))
316 ++current;
317
318 QString candidate =
319 QString::fromRawData(text.unicode() + progress->offset(), current - progress->offset());
320 if ((m_overrideGlobal && m_list->isKeyword(candidate, m_localCaseSensitivity)) ||
321 (!m_overrideGlobal && m_list->isKeyword(candidate, definition()->keywordsSensitive()))) {
322 progress->setOffset(current);
323 return true;
324 }
325
326 return false;
327 }
328
329 // Int
doMatchSucceed(const QString & text,const int length,ProgressData * progress)330 bool IntRule::doMatchSucceed(const QString &text,
331 const int length,
332 ProgressData *progress)
333 {
334 const int offset = progress->offset();
335
336 // This is necessary to correctly highlight an invalid octal like 09, for example.
337 if (offset > 0 && text.at(offset - 1).isDigit())
338 return false;
339
340 if (text.at(offset).isDigit() && text.at(offset) != kZero) {
341 progress->incrementOffset();
342 charPredicateMatchSucceed(text, length, progress, &QChar::isDigit);
343 return true;
344 }
345
346 return false;
347 }
348
349 // Float
doMatchSucceed(const QString & text,const int length,ProgressData * progress)350 bool FloatRule::doMatchSucceed(const QString &text, const int length, ProgressData *progress)
351 {
352 progress->saveOffset();
353
354 bool integralPart = charPredicateMatchSucceed(text, length, progress, &QChar::isDigit);
355
356 bool decimalPoint = false;
357 if (progress->offset() < length && text.at(progress->offset()) == kDot) {
358 progress->incrementOffset();
359 decimalPoint = true;
360 }
361
362 bool fractionalPart = charPredicateMatchSucceed(text, length, progress, &QChar::isDigit);
363
364 bool exponentialPart = false;
365 int offset = progress->offset();
366 if (offset < length && (text.at(offset) == kE || text.at(offset).toLower() == kE)) {
367 progress->incrementOffset();
368
369 offset = progress->offset();
370 if (offset < length && (text.at(offset) == kPlus || text.at(offset) == kMinus))
371 progress->incrementOffset();
372
373 if (charPredicateMatchSucceed(text, length, progress, &QChar::isDigit)) {
374 exponentialPart = true;
375 } else {
376 progress->restoreOffset();
377 return false;
378 }
379 }
380
381 if ((integralPart || fractionalPart) && (decimalPoint || exponentialPart))
382 return true;
383
384 progress->restoreOffset();
385 return false;
386 }
387
388 // COctal
doMatchSucceed(const QString & text,const int length,ProgressData * progress)389 bool HlCOctRule::doMatchSucceed(const QString &text,
390 const int length,
391 ProgressData *progress)
392 {
393 if (matchCharacter(text, length, progress, kZero)) {
394 // In the definition files the number matching rules which are more restrictive should
395 // appear before the rules which are least resctritive. Although this happens in general
396 // there is at least one case where this is not strictly followed for existent definition
397 // files (specifically, HlCHex comes before HlCOct). So the condition below.
398 const int offset = progress->offset();
399 if (offset < length && (text.at(offset) == kX || text.at(offset).toLower() == kX)) {
400 progress->restoreOffset();
401 return false;
402 }
403
404 charPredicateMatchSucceed(text, length, progress, &isOctalDigit);
405 return true;
406 }
407
408 return false;
409 }
410
411 // CHex
doMatchSucceed(const QString & text,const int length,ProgressData * progress)412 bool HlCHexRule::doMatchSucceed(const QString &text,
413 const int length,
414 ProgressData *progress)
415 {
416 if (matchCharacter(text, length, progress, kZero)) {
417 const int offset = progress->offset();
418 if (offset < length && text.at(offset) != kX && text.at(offset).toLower() != kX) {
419 progress->restoreOffset();
420 return false;
421 }
422
423 progress->incrementOffset();
424 if (charPredicateMatchSucceed(text, length, progress, &isHexDigit))
425 return true;
426 else
427 progress->restoreOffset();
428 }
429
430 return false;
431 }
432
433 // CString
doMatchSucceed(const QString & text,const int length,ProgressData * progress)434 bool HlCStringCharRule::doMatchSucceed(const QString &text,
435 const int length,
436 ProgressData *progress)
437 {
438 if (matchEscapeSequence(text, length, progress))
439 return true;
440
441 if (matchOctalSequence(text, length, progress))
442 return true;
443
444 if (matchHexSequence(text, length, progress))
445 return true;
446
447 return false;
448 }
449
450 // CChar
doMatchSucceed(const QString & text,const int length,ProgressData * progress)451 bool HlCCharRule::doMatchSucceed(const QString &text,
452 const int length,
453 ProgressData *progress)
454 {
455 if (matchCharacter(text, length, progress, kSingleQuote)) {
456 if (progress->offset() < length) {
457 if (text.at(progress->offset()) != kBackSlash &&
458 text.at(progress->offset()) != kSingleQuote) {
459 progress->incrementOffset();
460 } else if (!matchEscapeSequence(text, length, progress, false)) {
461 progress->restoreOffset();
462 return false;
463 }
464
465 if (progress->offset() < length &&
466 matchCharacter(text, length, progress, kSingleQuote, false)) {
467 return true;
468 } else {
469 progress->restoreOffset();
470 }
471 } else {
472 progress->restoreOffset();
473 }
474 }
475
476 return false;
477 }
478
479 // RangeDetect
setChar(const QString & character)480 void RangeDetectRule::setChar(const QString &character)
481 { setStartCharacter(&m_char, character); }
482
setChar1(const QString & character)483 void RangeDetectRule::setChar1(const QString &character)
484 { setStartCharacter(&m_char1, character); }
485
doMatchSucceed(const QString & text,const int length,ProgressData * progress)486 bool RangeDetectRule::doMatchSucceed(const QString &text,
487 const int length,
488 ProgressData *progress)
489 {
490 if (matchCharacter(text, length, progress, m_char)) {
491 while (progress->offset() < length) {
492 if (matchCharacter(text, length, progress, m_char1, false))
493 return true;
494 progress->incrementOffset();
495 }
496 progress->restoreOffset();
497 }
498
499 return false;
500 }
501
502 // LineContinue
doMatchSucceed(const QString & text,const int length,ProgressData * progress)503 bool LineContinueRule::doMatchSucceed(const QString &text,
504 const int length,
505 ProgressData *progress)
506 {
507 if (progress->offset() != length - 1)
508 return false;
509
510 if (text.at(progress->offset()) == kBackSlash) {
511 progress->incrementOffset();
512 progress->setWillContinueLine(true);
513 return true;
514 }
515
516 return false;
517 }
518
519 // DetectSpaces
DetectSpacesRule()520 DetectSpacesRule::DetectSpacesRule() : Rule(false)
521 {}
522
doMatchSucceed(const QString & text,const int length,ProgressData * progress)523 bool DetectSpacesRule::doMatchSucceed(const QString &text,
524 const int length,
525 ProgressData *progress)
526 {
527 return charPredicateMatchSucceed(text, length, progress, &QChar::isSpace);
528 }
529
530 // DetectIdentifier
doMatchSucceed(const QString & text,const int length,ProgressData * progress)531 bool DetectIdentifierRule::doMatchSucceed(const QString &text,
532 const int length,
533 ProgressData *progress)
534 {
535 // Identifiers are characterized by a letter or underscore as the first character and then
536 // zero or more word characters (\w*).
537 if (text.at(progress->offset()).isLetter() || text.at(progress->offset()) == kUnderscore) {
538 progress->incrementOffset();
539 while (progress->offset() < length) {
540 const QChar ¤t = text.at(progress->offset());
541 if (current.isLetterOrNumber() || current.isMark() || current == kUnderscore)
542 progress->incrementOffset();
543 else
544 break;
545 }
546 return true;
547 }
548 return false;
549 }
550