OSDN Git Service

Update license.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / texteditor / generichighlighter / specificrules.cpp
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 (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
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.
18 **
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.
22 **
23 ** Other Usage
24 **
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.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "specificrules.h"
34 #include "highlightdefinition.h"
35 #include "keywordlist.h"
36 #include "progressdata.h"
37 #include "reuse.h"
38
39 #include <QtCore/QLatin1Char>
40
41 using namespace TextEditor;
42 using namespace Internal;
43
44 namespace {
45
46 void replaceByCaptures(QChar *c, const QStringList &captures)
47 {
48     int index = c->digitValue();
49     if (index > 0) {
50         const QString &capture = captures.at(index);
51         if (!capture.isEmpty())
52             *c = capture.at(0);
53     }
54 }
55
56 void replaceByCaptures(QString *s, const QStringList &captures)
57 {
58     static const QLatin1Char kPercent('%');
59
60     int index;
61     int from = 0;
62     while ((index = s->indexOf(kPercent, from)) != -1) {
63         from = index + 1;
64
65         QString accumulator;
66         while (from < s->length() && s->at(from).isDigit()) {
67             accumulator.append(s->at(from));
68             ++from;
69         }
70
71         bool ok;
72         int number = accumulator.toInt(&ok);
73         Q_ASSERT(ok);
74
75         s->replace(index, accumulator.length() + 1, captures.at(number));
76         index = from;
77     }
78 }
79 }
80
81 // DetectChar
82 void DetectCharRule::setChar(const QString &character)
83 { setStartCharacter(&m_char, character); }
84
85 void DetectCharRule::doReplaceExpressions(const QStringList &captures)
86 { replaceByCaptures(&m_char, captures); }
87
88 bool DetectCharRule::doMatchSucceed(const QString &text,
89                                     const int length,
90                                     ProgressData *progress)
91 {
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);
100         }
101         return true;
102     }
103     return false;
104 }
105
106 // Detect2Chars
107 void Detect2CharsRule::setChar(const QString &character)
108 { setStartCharacter(&m_char, character); }
109
110 void Detect2CharsRule::setChar1(const QString &character)
111 { setStartCharacter(&m_char1, character); }
112
113 void Detect2CharsRule::doReplaceExpressions(const QStringList &captures)
114 {
115     replaceByCaptures(&m_char, captures);
116     replaceByCaptures(&m_char1, captures);
117 }
118
119 bool Detect2CharsRule::doMatchSucceed(const QString &text,
120                                       const int length,
121                                       ProgressData *progress)
122 {
123     if (matchCharacter(text, length, progress, m_char)) {
124         if (progress->offset() < length && matchCharacter(text, length, progress, m_char1, false))
125             return true;
126         else
127             progress->restoreOffset();
128     }
129
130     return false;
131 }
132
133 // AnyChar
134 void AnyCharRule::setCharacterSet(const QString &s)
135 { m_characterSet = s; }
136
137 bool AnyCharRule::doMatchSucceed(const QString &text,
138                                  const int length,
139                                  ProgressData *progress)
140 {
141     Q_UNUSED(length)
142
143     if (m_characterSet.contains(text.at(progress->offset()))) {
144         progress->incrementOffset();
145         return true;
146     }
147
148     return false;
149 }
150
151 // StringDetect
152 void StringDetectRule::setString(const QString &s)
153 {
154     m_string = s;
155     m_length = m_string.length();
156 }
157
158 void StringDetectRule::setInsensitive(const QString &insensitive)
159 { m_caseSensitivity = toCaseSensitivity(!toBool(insensitive)); }
160
161 void StringDetectRule::doReplaceExpressions(const QStringList &captures)
162 {
163     replaceByCaptures(&m_string, captures);
164     m_length = m_string.length();
165 }
166
167 bool StringDetectRule::doMatchSucceed(const QString &text,
168                                       const int length,
169                                       ProgressData *progress)
170 {
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);
175             return true;
176         }
177     }
178
179     return false;
180 }
181
182 // RegExpr
183 void RegExprRule::setPattern(const QString &pattern)
184 {
185     if (pattern.startsWith(QLatin1Char('^')))
186         m_onlyBegin = true;
187     m_expression.setPattern(pattern);
188 }
189
190 void RegExprRule::setInsensitive(const QString &insensitive)
191 { m_expression.setCaseSensitivity(toCaseSensitivity(!toBool(insensitive))); }
192
193 void RegExprRule::setMinimal(const QString &minimal)
194 { m_expression.setMinimal(toBool(minimal)); }
195
196 void RegExprRule::doReplaceExpressions(const QStringList &captures)
197 {
198     QString s = m_expression.pattern();
199     replaceByCaptures(&s, captures);
200     m_expression.setPattern(s);
201 }
202
203 void RegExprRule::doProgressFinished()
204 {
205     m_isCached = false;
206 }
207
208 bool RegExprRule::isExactMatch(ProgressData *progress)
209 {
210     if (progress->offset() == m_offset && m_length > 0) {
211         progress->incrementOffset(m_length);
212         progress->setCaptures(m_captures);
213         return true;
214     }
215     return false;
216 }
217
218 bool RegExprRule::doMatchSucceed(const QString &text,
219                                  const int length,
220                                  ProgressData *progress)
221 {
222     Q_UNUSED(length)
223
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)
228         return false;
229
230     if (m_isCached) {
231         if (offset < m_offset || m_offset == -1 || m_length == 0)
232             return false;
233         if (isExactMatch(progress))
234             return true;
235     }
236
237     m_offset = m_expression.indexIn(text, offset, QRegExp::CaretAtOffset);
238     m_length = m_expression.matchedLength();
239     m_captures = m_expression.capturedTexts();
240
241     if (isExactMatch(progress))
242         return true;
243
244     m_isCached = true;
245     progress->trackRule(this);
246
247     return false;
248 }
249
250 // Keyword
251 KeywordRule::KeywordRule(const QSharedPointer<HighlightDefinition> &definition) :
252     m_overrideGlobal(false),
253     m_localCaseSensitivity(Qt::CaseSensitive)
254 {
255     setDefinition(definition);
256 }
257
258 KeywordRule::~KeywordRule()
259 {}
260
261 void KeywordRule::setInsensitive(const QString &insensitive)
262 {
263     if (!insensitive.isEmpty()) {
264         m_overrideGlobal = true;
265         m_localCaseSensitivity = toCaseSensitivity(!toBool(insensitive));
266     }
267 }
268
269 void KeywordRule::setList(const QString &listName)
270 { m_list = definition()->keywordList(listName); }
271
272 bool KeywordRule::doMatchSucceed(const QString &text,
273                                  const int length,
274                                  ProgressData *progress)
275 {
276     int current = progress->offset();
277
278     if (current > 0 && !definition()->isDelimiter(text.at(current - 1)))
279         return false;
280     if (definition()->isDelimiter(text.at(current)))
281         return false;
282
283     while (current < length && !definition()->isDelimiter(text.at(current)))
284         ++current;
285
286     QString candidate =
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);
291         return true;
292     }
293
294     return false;
295 }
296
297 // Int
298 bool IntRule::doMatchSucceed(const QString &text,
299                              const int length,
300                              ProgressData *progress)
301 {
302     const int offset = progress->offset();
303
304     // This is necessary to correctly highlight an invalid octal like 09, for example.
305     if (offset > 0 && text.at(offset - 1).isDigit())
306         return false;
307
308     if (text.at(offset).isDigit() && text.at(offset) != kZero) {
309         progress->incrementOffset();
310         charPredicateMatchSucceed(text, length, progress, &QChar::isDigit);
311         return true;
312     }
313
314     return false;
315 }
316
317 // Float
318 bool FloatRule::doMatchSucceed(const QString &text, const int length, ProgressData *progress)
319 {
320     progress->saveOffset();
321
322     bool integralPart = charPredicateMatchSucceed(text, length, progress, &QChar::isDigit);
323
324     bool decimalPoint = false;
325     if (progress->offset() < length && text.at(progress->offset()) == kDot) {
326         progress->incrementOffset();
327         decimalPoint = true;
328     }
329
330     bool fractionalPart = charPredicateMatchSucceed(text, length, progress, &QChar::isDigit);
331
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();
336
337         offset = progress->offset();
338         if (offset < length && (text.at(offset) == kPlus || text.at(offset) == kMinus))
339             progress->incrementOffset();
340
341         if (charPredicateMatchSucceed(text, length, progress, &QChar::isDigit)) {
342             exponentialPart = true;
343         } else {
344             progress->restoreOffset();
345             return false;
346         }
347     }
348
349     if ((integralPart || fractionalPart) && (decimalPoint || exponentialPart))
350         return true;
351
352     progress->restoreOffset();
353     return false;
354 }
355
356 // COctal
357 bool HlCOctRule::doMatchSucceed(const QString &text,
358                                 const int length,
359                                 ProgressData *progress)
360 {
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();
369             return false;
370         }
371
372         charPredicateMatchSucceed(text, length, progress, &isOctalDigit);
373         return true;
374     }
375
376     return false;
377 }
378
379 // CHex
380 bool HlCHexRule::doMatchSucceed(const QString &text,
381                                 const int length,
382                                 ProgressData *progress)
383 {
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();
388             return false;
389         }
390
391         progress->incrementOffset();
392         if (charPredicateMatchSucceed(text, length, progress, &isHexDigit))
393             return true;
394         else
395             progress->restoreOffset();
396     }
397
398     return false;
399 }
400
401 // CString
402 bool HlCStringCharRule::doMatchSucceed(const QString &text,
403                                        const int length,
404                                        ProgressData *progress)
405 {
406     if (matchEscapeSequence(text, length, progress))
407         return true;
408
409     if (matchOctalSequence(text, length, progress))
410         return true;
411
412     if (matchHexSequence(text, length, progress))
413         return true;
414
415     return false;
416 }
417
418 // CChar
419 bool HlCCharRule::doMatchSucceed(const QString &text,
420                                  const int length,
421                                  ProgressData *progress)
422 {
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();
430                 return false;
431             }
432
433             if (progress->offset() < length &&
434                 matchCharacter(text, length, progress, kSingleQuote, false)) {
435                 return true;
436             } else {
437                 progress->restoreOffset();
438             }
439         } else {
440             progress->restoreOffset();
441         }
442     }
443
444     return false;
445 }
446
447 // RangeDetect
448 void RangeDetectRule::setChar(const QString &character)
449 { setStartCharacter(&m_char, character); }
450
451 void RangeDetectRule::setChar1(const QString &character)
452 { setStartCharacter(&m_char1, character); }
453
454 bool RangeDetectRule::doMatchSucceed(const QString &text,
455                                      const int length,
456                                      ProgressData *progress)
457 {
458     if (matchCharacter(text, length, progress, m_char)) {
459         while (progress->offset() < length) {
460             if (matchCharacter(text, length, progress, m_char1, false))
461                 return true;
462             progress->incrementOffset();
463         }
464         progress->restoreOffset();
465     }
466
467     return false;
468 }
469
470 // LineContinue
471 bool LineContinueRule::doMatchSucceed(const QString &text,
472                                       const int length,
473                                       ProgressData *progress)
474 {
475     if (progress->offset() != length - 1)
476         return false;
477
478     if (text.at(progress->offset()) == kBackSlash) {
479         progress->incrementOffset();
480         progress->setWillContinueLine(true);
481         return true;
482     }
483
484     return false;
485 }
486
487 // DetectSpaces
488 DetectSpacesRule::DetectSpacesRule() : Rule(false)
489 {}
490
491 bool DetectSpacesRule::doMatchSucceed(const QString &text,
492                                       const int length,
493                                       ProgressData *progress)
494 {
495     return charPredicateMatchSucceed(text, length, progress, &QChar::isSpace);
496 }
497
498 // DetectIdentifier
499 bool DetectIdentifierRule::doMatchSucceed(const QString &text,
500                                           const int length,
501                                           ProgressData *progress)
502 {
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 &current = text.at(progress->offset());
509             if (current.isLetterOrNumber() || current.isMark() || current == kUnderscore)
510                 progress->incrementOffset();
511             else
512                 break;
513         }
514         return true;
515     }
516     return false;
517 }