1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
31 **************************************************************************/
33 #include "specificrules.h"
34 #include "highlightdefinition.h"
35 #include "keywordlist.h"
36 #include "progressdata.h"
39 #include <QtCore/QLatin1Char>
41 using namespace TextEditor;
42 using namespace Internal;
46 void replaceByCaptures(QChar *c, const QStringList &captures)
48 int index = c->digitValue();
50 const QString &capture = captures.at(index);
51 if (!capture.isEmpty())
56 void replaceByCaptures(QString *s, const QStringList &captures)
58 static const QLatin1Char kPercent('%');
62 while ((index = s->indexOf(kPercent, from)) != -1) {
66 while (from < s->length() && s->at(from).isDigit()) {
67 accumulator.append(s->at(from));
72 int number = accumulator.toInt(&ok);
75 s->replace(index, accumulator.length() + 1, captures.at(number));
82 void DetectCharRule::setChar(const QString &character)
83 { setStartCharacter(&m_char, character); }
85 void DetectCharRule::doReplaceExpressions(const QStringList &captures)
86 { replaceByCaptures(&m_char, captures); }
88 bool DetectCharRule::doMatchSucceed(const QString &text,
90 ProgressData *progress)
92 if (matchCharacter(text, length, progress, m_char)) {
93 // This is to make code folding have a control flow style look in the case of braces.
94 // Naturally, this assumes that language definitions use braces with this meaning.
95 if (m_char == kOpeningBrace && progress->isOnlySpacesSoFar() && !isLookAhead()) {
96 progress->setOpeningBraceMatchAtFirstNonSpace(true);
97 } else if (m_char == kClosingBrace &&
98 !text.right(length - progress->offset()).trimmed().isEmpty()) {
99 progress->setClosingBraceMatchAtNonEnd(true);
107 void Detect2CharsRule::setChar(const QString &character)
108 { setStartCharacter(&m_char, character); }
110 void Detect2CharsRule::setChar1(const QString &character)
111 { setStartCharacter(&m_char1, character); }
113 void Detect2CharsRule::doReplaceExpressions(const QStringList &captures)
115 replaceByCaptures(&m_char, captures);
116 replaceByCaptures(&m_char1, captures);
119 bool Detect2CharsRule::doMatchSucceed(const QString &text,
121 ProgressData *progress)
123 if (matchCharacter(text, length, progress, m_char)) {
124 if (progress->offset() < length && matchCharacter(text, length, progress, m_char1, false))
127 progress->restoreOffset();
134 void AnyCharRule::setCharacterSet(const QString &s)
135 { m_characterSet = s; }
137 bool AnyCharRule::doMatchSucceed(const QString &text,
139 ProgressData *progress)
143 if (m_characterSet.contains(text.at(progress->offset()))) {
144 progress->incrementOffset();
152 void StringDetectRule::setString(const QString &s)
155 m_length = m_string.length();
158 void StringDetectRule::setInsensitive(const QString &insensitive)
159 { m_caseSensitivity = toCaseSensitivity(!toBool(insensitive)); }
161 void StringDetectRule::doReplaceExpressions(const QStringList &captures)
163 replaceByCaptures(&m_string, captures);
164 m_length = m_string.length();
167 bool StringDetectRule::doMatchSucceed(const QString &text,
169 ProgressData *progress)
171 if (length - progress->offset() >= m_length) {
172 QString candidate = text.fromRawData(text.unicode() + progress->offset(), m_length);
173 if (candidate.compare(m_string, m_caseSensitivity) == 0) {
174 progress->incrementOffset(m_length);
183 void RegExprRule::setPattern(const QString &pattern)
185 if (pattern.startsWith(QLatin1Char('^')))
187 m_expression.setPattern(pattern);
190 void RegExprRule::setInsensitive(const QString &insensitive)
191 { m_expression.setCaseSensitivity(toCaseSensitivity(!toBool(insensitive))); }
193 void RegExprRule::setMinimal(const QString &minimal)
194 { m_expression.setMinimal(toBool(minimal)); }
196 void RegExprRule::doReplaceExpressions(const QStringList &captures)
198 QString s = m_expression.pattern();
199 replaceByCaptures(&s, captures);
200 m_expression.setPattern(s);
203 void RegExprRule::doProgressFinished()
208 bool RegExprRule::isExactMatch(ProgressData *progress)
210 if (progress->offset() == m_offset && m_length > 0) {
211 progress->incrementOffset(m_length);
212 progress->setCaptures(m_captures);
218 bool RegExprRule::doMatchSucceed(const QString &text,
220 ProgressData *progress)
224 // A regular expression match is considered valid if it happens at the current position
225 // and if the match length is not zero.
226 const int offset = progress->offset();
227 if (offset > 0 && m_onlyBegin)
231 if (offset < m_offset || m_offset == -1 || m_length == 0)
233 if (isExactMatch(progress))
237 m_offset = m_expression.indexIn(text, offset, QRegExp::CaretAtOffset);
238 m_length = m_expression.matchedLength();
239 m_captures = m_expression.capturedTexts();
241 if (isExactMatch(progress))
245 progress->trackRule(this);
251 KeywordRule::KeywordRule(const QSharedPointer<HighlightDefinition> &definition) :
252 m_overrideGlobal(false),
253 m_localCaseSensitivity(Qt::CaseSensitive)
255 setDefinition(definition);
258 KeywordRule::~KeywordRule()
261 void KeywordRule::setInsensitive(const QString &insensitive)
263 if (!insensitive.isEmpty()) {
264 m_overrideGlobal = true;
265 m_localCaseSensitivity = toCaseSensitivity(!toBool(insensitive));
269 void KeywordRule::setList(const QString &listName)
270 { m_list = definition()->keywordList(listName); }
272 bool KeywordRule::doMatchSucceed(const QString &text,
274 ProgressData *progress)
276 int current = progress->offset();
278 if (current > 0 && !definition()->isDelimiter(text.at(current - 1)))
280 if (definition()->isDelimiter(text.at(current)))
283 while (current < length && !definition()->isDelimiter(text.at(current)))
287 QString::fromRawData(text.unicode() + progress->offset(), current - progress->offset());
288 if ((m_overrideGlobal && m_list->isKeyword(candidate, m_localCaseSensitivity)) ||
289 (!m_overrideGlobal && m_list->isKeyword(candidate, definition()->keywordsSensitive()))) {
290 progress->setOffset(current);
298 bool IntRule::doMatchSucceed(const QString &text,
300 ProgressData *progress)
302 const int offset = progress->offset();
304 // This is necessary to correctly highlight an invalid octal like 09, for example.
305 if (offset > 0 && text.at(offset - 1).isDigit())
308 if (text.at(offset).isDigit() && text.at(offset) != kZero) {
309 progress->incrementOffset();
310 charPredicateMatchSucceed(text, length, progress, &QChar::isDigit);
318 bool FloatRule::doMatchSucceed(const QString &text, const int length, ProgressData *progress)
320 progress->saveOffset();
322 bool integralPart = charPredicateMatchSucceed(text, length, progress, &QChar::isDigit);
324 bool decimalPoint = false;
325 if (progress->offset() < length && text.at(progress->offset()) == kDot) {
326 progress->incrementOffset();
330 bool fractionalPart = charPredicateMatchSucceed(text, length, progress, &QChar::isDigit);
332 bool exponentialPart = false;
333 int offset = progress->offset();
334 if (offset < length && (text.at(offset) == kE || text.at(offset).toLower() == kE)) {
335 progress->incrementOffset();
337 offset = progress->offset();
338 if (offset < length && (text.at(offset) == kPlus || text.at(offset) == kMinus))
339 progress->incrementOffset();
341 if (charPredicateMatchSucceed(text, length, progress, &QChar::isDigit)) {
342 exponentialPart = true;
344 progress->restoreOffset();
349 if ((integralPart || fractionalPart) && (decimalPoint || exponentialPart))
352 progress->restoreOffset();
357 bool HlCOctRule::doMatchSucceed(const QString &text,
359 ProgressData *progress)
361 if (matchCharacter(text, length, progress, kZero)) {
362 // In the definition files the number matching rules which are more restrictive should
363 // appear before the rules which are least resctritive. Although this happens in general
364 // there is at least one case where this is not strictly followed for existent definition
365 // files (specifically, HlCHex comes before HlCOct). So the condition below.
366 const int offset = progress->offset();
367 if (offset < length && (text.at(offset) == kX || text.at(offset).toLower() == kX)) {
368 progress->restoreOffset();
372 charPredicateMatchSucceed(text, length, progress, &isOctalDigit);
380 bool HlCHexRule::doMatchSucceed(const QString &text,
382 ProgressData *progress)
384 if (matchCharacter(text, length, progress, kZero)) {
385 const int offset = progress->offset();
386 if (offset < length && text.at(offset) != kX && text.at(offset).toLower() != kX) {
387 progress->restoreOffset();
391 progress->incrementOffset();
392 if (charPredicateMatchSucceed(text, length, progress, &isHexDigit))
395 progress->restoreOffset();
402 bool HlCStringCharRule::doMatchSucceed(const QString &text,
404 ProgressData *progress)
406 if (matchEscapeSequence(text, length, progress))
409 if (matchOctalSequence(text, length, progress))
412 if (matchHexSequence(text, length, progress))
419 bool HlCCharRule::doMatchSucceed(const QString &text,
421 ProgressData *progress)
423 if (matchCharacter(text, length, progress, kSingleQuote)) {
424 if (progress->offset() < length) {
425 if (text.at(progress->offset()) != kBackSlash &&
426 text.at(progress->offset()) != kSingleQuote) {
427 progress->incrementOffset();
428 } else if (!matchEscapeSequence(text, length, progress, false)) {
429 progress->restoreOffset();
433 if (progress->offset() < length &&
434 matchCharacter(text, length, progress, kSingleQuote, false)) {
437 progress->restoreOffset();
440 progress->restoreOffset();
448 void RangeDetectRule::setChar(const QString &character)
449 { setStartCharacter(&m_char, character); }
451 void RangeDetectRule::setChar1(const QString &character)
452 { setStartCharacter(&m_char1, character); }
454 bool RangeDetectRule::doMatchSucceed(const QString &text,
456 ProgressData *progress)
458 if (matchCharacter(text, length, progress, m_char)) {
459 while (progress->offset() < length) {
460 if (matchCharacter(text, length, progress, m_char1, false))
462 progress->incrementOffset();
464 progress->restoreOffset();
471 bool LineContinueRule::doMatchSucceed(const QString &text,
473 ProgressData *progress)
475 if (progress->offset() != length - 1)
478 if (text.at(progress->offset()) == kBackSlash) {
479 progress->incrementOffset();
480 progress->setWillContinueLine(true);
488 DetectSpacesRule::DetectSpacesRule() : Rule(false)
491 bool DetectSpacesRule::doMatchSucceed(const QString &text,
493 ProgressData *progress)
495 return charPredicateMatchSucceed(text, length, progress, &QChar::isSpace);
499 bool DetectIdentifierRule::doMatchSucceed(const QString &text,
501 ProgressData *progress)
503 // Identifiers are characterized by a letter or underscore as the first character and then
504 // zero or more word characters (\w*).
505 if (text.at(progress->offset()).isLetter() || text.at(progress->offset()) == kUnderscore) {
506 progress->incrementOffset();
507 while (progress->offset() < length) {
508 const QChar ¤t = text.at(progress->offset());
509 if (current.isLetterOrNumber() || current.isMark() || current == kUnderscore)
510 progress->incrementOffset();