OSDN Git Service

3775a63d51f3633800a1a717bf5bad1c09d5f88b
[qt-creator-jp/qt-creator-jp.git] / src / plugins / texteditor / generichighlighter / highlighter.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** Commercial Usage
10 **
11 ** Licensees holding valid Qt Commercial licenses may use this file in
12 ** accordance with the Qt Commercial License Agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and Nokia.
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 ** If you are unsure which license is appropriate for your use, please
26 ** contact the sales department at http://qt.nokia.com/contact.
27 **
28 **************************************************************************/
29
30 #include "highlighter.h"
31 #include "highlightdefinition.h"
32 #include "context.h"
33 #include "rule.h"
34 #include "itemdata.h"
35 #include "highlighterexception.h"
36 #include "progressdata.h"
37 #include "reuse.h"
38 #include "tabsettings.h"
39
40 #include <QtCore/QLatin1String>
41 #include <QtCore/QLatin1Char>
42
43 using namespace TextEditor;
44 using namespace Internal;
45
46 namespace {
47     static const QLatin1String kStay("#stay");
48     static const QLatin1String kPop("#pop");
49     static const QLatin1Char kBackSlash('\\');
50     static const QLatin1Char kHash('#');
51 }
52
53 const Highlighter::KateFormatMap Highlighter::m_kateFormats;
54
55 Highlighter::Highlighter(QTextDocument *parent) :
56     TextEditor::SyntaxHighlighter(parent),
57     m_regionDepth(0),
58     m_indentationBasedFolding(false),
59     m_tabSettings(0),
60     m_persistentObservableStatesCounter(PersistentsStart),
61     m_dynamicContextsCounter(0),
62     m_isBroken(false)
63 {}
64
65 Highlighter::~Highlighter()
66 {}
67
68 Highlighter::BlockData::BlockData() : m_foldingIndentDelta(0), m_originalObservableState(-1)
69 {}
70
71 Highlighter::BlockData::~BlockData()
72 {}
73
74 Highlighter::KateFormatMap::KateFormatMap()
75 {
76     m_ids.insert(QLatin1String("dsNormal"), Highlighter::Normal);
77     m_ids.insert(QLatin1String("dsKeyword"), Highlighter::Keyword);
78     m_ids.insert(QLatin1String("dsDataType"), Highlighter::DataType);
79     m_ids.insert(QLatin1String("dsDecVal"), Highlighter::Decimal);
80     m_ids.insert(QLatin1String("dsBaseN"), Highlighter::BaseN);
81     m_ids.insert(QLatin1String("dsFloat"), Highlighter::Float);
82     m_ids.insert(QLatin1String("dsChar"), Highlighter::Char);
83     m_ids.insert(QLatin1String("dsString"), Highlighter::String);
84     m_ids.insert(QLatin1String("dsComment"), Highlighter::Comment);
85     m_ids.insert(QLatin1String("dsOthers"), Highlighter::Others);
86     m_ids.insert(QLatin1String("dsAlert"), Highlighter::Alert);
87     m_ids.insert(QLatin1String("dsFunction"), Highlighter::Function);
88     m_ids.insert(QLatin1String("dsRegionMarker"), Highlighter::RegionMarker);
89     m_ids.insert(QLatin1String("dsError"), Highlighter::Error);
90 }
91
92 void Highlighter::configureFormat(TextFormatId id, const QTextCharFormat &format)
93 {
94     m_creatorFormats[id] = format;
95 }
96
97 void  Highlighter::setDefaultContext(const QSharedPointer<Context> &defaultContext)
98 {
99     m_defaultContext = defaultContext;
100     m_persistentObservableStates.insert(m_defaultContext->name(), Default);
101     m_indentationBasedFolding = defaultContext->definition()->isIndentationBasedFolding();
102 }
103
104 void Highlighter::setTabSettings(const TabSettings &ts)
105 {
106     m_tabSettings = &ts;
107 }
108
109 void Highlighter::highlightBlock(const QString &text)
110 {
111     if (!m_defaultContext.isNull() && !m_isBroken) {
112         try {
113             if (!currentBlockUserData())
114                 initializeBlockData();
115             setupDataForBlock(text);
116
117             handleContextChange(m_currentContext->lineBeginContext(),
118                                 m_currentContext->definition());
119
120             ProgressData progress;
121             const int length = text.length();
122             while (progress.offset() < length)
123                 iterateThroughRules(text, length, &progress, false, m_currentContext->rules());
124
125             handleContextChange(m_currentContext->lineEndContext(),
126                                 m_currentContext->definition(),
127                                 false);
128             m_contexts.clear();
129
130             if (m_indentationBasedFolding) {
131                 applyIndentationBasedFolding(text);
132             } else {
133                 applyRegionBasedFolding();
134
135                 // In the case region depth has changed since the last time the state was set.
136                 setCurrentBlockState(computeState(extractObservableState(currentBlockState())));
137             }
138         } catch (const HighlighterException &) {
139             m_isBroken = true;
140         }
141     }
142
143     applyFormatToSpaces(text, m_creatorFormats.value(VisualWhitespace));
144 }
145
146 void Highlighter::setupDataForBlock(const QString &text)
147 {
148     if (extractObservableState(currentBlockState()) == WillContinue)
149         analyseConsistencyOfWillContinueBlock(text);
150
151     if (previousBlockState() == -1) {
152         m_regionDepth = 0;
153         setupDefault();
154     } else {
155         m_regionDepth = extractRegionDepth(previousBlockState());
156         const int observablePreviousState = extractObservableState(previousBlockState());
157         if (observablePreviousState == Default)
158             setupDefault();
159         else if (observablePreviousState == WillContinue)
160             setupFromWillContinue();
161         else if (observablePreviousState == Continued)
162             setupFromContinued();
163         else
164             setupFromPersistent();
165
166         blockData(currentBlockUserData())->m_foldingRegions =
167             blockData(currentBlock().previous().userData())->m_foldingRegions;
168     }
169
170     assignCurrentContext();
171 }
172
173 void Highlighter::setupDefault()
174 {
175     m_contexts.push_back(m_defaultContext);
176
177     setCurrentBlockState(computeState(Default));
178 }
179
180 void Highlighter::setupFromWillContinue()
181 {
182     BlockData *previousData = blockData(currentBlock().previous().userData());
183     m_contexts.push_back(previousData->m_contextToContinue);
184
185     BlockData *data = blockData(currentBlock().userData());
186     data->m_originalObservableState = previousData->m_originalObservableState;
187
188     if (currentBlockState() == -1 || extractObservableState(currentBlockState()) == Default)
189         setCurrentBlockState(computeState(Continued));
190 }
191
192 void Highlighter::setupFromContinued()
193 {
194     BlockData *previousData = blockData(currentBlock().previous().userData());
195
196     Q_ASSERT(previousData->m_originalObservableState != WillContinue &&
197              previousData->m_originalObservableState != Continued);
198
199     if (previousData->m_originalObservableState == Default ||
200         previousData->m_originalObservableState == -1) {
201         m_contexts.push_back(m_defaultContext);
202     } else {
203         pushContextSequence(previousData->m_originalObservableState);
204     }
205
206     setCurrentBlockState(computeState(previousData->m_originalObservableState));
207 }
208
209 void Highlighter::setupFromPersistent()
210 {
211     pushContextSequence(extractObservableState(previousBlockState()));
212
213     setCurrentBlockState(previousBlockState());
214 }
215
216 void Highlighter::iterateThroughRules(const QString &text,
217                                       const int length,
218                                       ProgressData *progress,
219                                       const bool childRule,
220                                       const QList<QSharedPointer<Rule> > &rules)
221 {
222     typedef QList<QSharedPointer<Rule> >::const_iterator RuleIterator;
223
224     bool contextChanged = false;
225     bool atLeastOneMatch = false;
226
227     RuleIterator it = rules.begin();
228     RuleIterator endIt = rules.end();
229     while (it != endIt && progress->offset() < length) {
230         int startOffset = progress->offset();
231         const QSharedPointer<Rule> &rule = *it;
232         if (rule->matchSucceed(text, length, progress)) {
233             atLeastOneMatch = true;
234
235             if (!m_indentationBasedFolding) {
236                 if (!rule->beginRegion().isEmpty()) {
237                     blockData(currentBlockUserData())->m_foldingRegions.push(rule->beginRegion());
238                     ++m_regionDepth;
239                     if (progress->isOpeningBraceMatchAtFirstNonSpace())
240                         ++blockData(currentBlockUserData())->m_foldingIndentDelta;
241                 }
242                 if (!rule->endRegion().isEmpty()) {
243                     QStack<QString> *currentRegions =
244                         &blockData(currentBlockUserData())->m_foldingRegions;
245                     if (!currentRegions->isEmpty() && rule->endRegion() == currentRegions->top()) {
246                         currentRegions->pop();
247                         --m_regionDepth;
248                         if (progress->isClosingBraceMatchAtNonEnd())
249                             --blockData(currentBlockUserData())->m_foldingIndentDelta;
250                     }
251                 }
252                 progress->clearBracesMatches();
253             }
254
255             if (progress->isWillContinueLine()) {
256                 createWillContinueBlock();
257                 progress->setWillContinueLine(false);
258             } else {
259                 if (rule->hasChild())
260                     iterateThroughRules(text, length, progress, true, rule->childs());
261
262                 if (!rule->context().isEmpty() && contextChangeRequired(rule->context())) {
263                     m_currentCaptures = progress->captures();
264                     changeContext(rule->context(), rule->definition());
265                     contextChanged = true;
266                 }
267             }
268
269             // Format is not applied to child rules directly (but relative to the offset of their
270             // parent) nor to look ahead rules.
271             if (!childRule && !rule->isLookAhead()) {
272                 if (rule->itemData().isEmpty())
273                     applyFormat(startOffset, progress->offset() - startOffset,
274                                 m_currentContext->itemData(), m_currentContext->definition());
275                 else
276                     applyFormat(startOffset, progress->offset() - startOffset, rule->itemData(),
277                                 rule->definition());
278             }
279
280             // When there is a match of one child rule the others should be skipped. Otherwise
281             // the highlighting would be incorret in a case like 9ULLLULLLUULLULLUL, for example.
282             if (contextChanged || childRule) {
283                 break;
284             } else {
285                 it = rules.begin();
286                 continue;
287             }
288         }
289         ++it;
290     }
291
292     if (!childRule && !atLeastOneMatch) {
293         if (m_currentContext->isFallthrough()) {
294             handleContextChange(m_currentContext->fallthroughContext(),
295                                 m_currentContext->definition());
296             iterateThroughRules(text, length, progress, false, m_currentContext->rules());
297         } else {
298             applyFormat(progress->offset(), 1, m_currentContext->itemData(),
299                         m_currentContext->definition());
300             if (progress->isOnlySpacesSoFar() && !text.at(progress->offset()).isSpace())
301                 progress->setOnlySpacesSoFar(false);
302             progress->incrementOffset();
303         }
304     }
305 }
306
307 bool Highlighter::contextChangeRequired(const QString &contextName) const
308 {
309     if (contextName == kStay)
310         return false;
311     return true;
312 }
313
314 void Highlighter::changeContext(const QString &contextName,
315                                 const QSharedPointer<HighlightDefinition> &definition,
316                                 const bool assignCurrent)
317 {
318     if (contextName.startsWith(kPop)) {
319         QStringList list = contextName.split(kHash, QString::SkipEmptyParts);
320         for (int i = 0; i < list.size(); ++i)
321             m_contexts.pop_back();
322
323         if (extractObservableState(currentBlockState()) >= PersistentsStart) {
324             // One or more contexts were popped during during a persistent state.
325             const QString &currentSequence = currentContextSequence();
326             if (m_persistentObservableStates.contains(currentSequence))
327                 setCurrentBlockState(
328                     computeState(m_persistentObservableStates.value(currentSequence)));
329             else
330                 setCurrentBlockState(
331                     computeState(m_leadingObservableStates.value(currentSequence)));
332         }
333     } else {
334         const QSharedPointer<Context> &context = definition->context(contextName);
335
336         if (context->isDynamic())
337             pushDynamicContext(context);
338         else
339             m_contexts.push_back(context);
340
341         if (m_contexts.back()->lineEndContext() == kStay ||
342             extractObservableState(currentBlockState()) >= PersistentsStart) {
343             const QString &currentSequence = currentContextSequence();
344             mapLeadingSequence(currentSequence);
345             if (m_contexts.back()->lineEndContext() == kStay) {
346                 // A persistent context was pushed.
347                 mapPersistentSequence(currentSequence);
348                 setCurrentBlockState(
349                     computeState(m_persistentObservableStates.value(currentSequence)));
350             }
351         }
352     }
353
354     if (assignCurrent)
355         assignCurrentContext();
356 }
357
358 void Highlighter::handleContextChange(const QString &contextName,
359                                       const QSharedPointer<HighlightDefinition> &definition,
360                                       const bool setCurrent)
361 {
362     if (!contextName.isEmpty() && contextChangeRequired(contextName))
363         changeContext(contextName, definition, setCurrent);
364 }
365
366 void Highlighter::applyFormat(int offset,
367                               int count,
368                               const QString &itemDataName,
369                               const QSharedPointer<HighlightDefinition> &definition)
370 {
371     if (count == 0)
372         return;
373
374     QSharedPointer<ItemData> itemData;
375     try {
376         itemData = definition->itemData(itemDataName);
377     } catch (const HighlighterException &) {
378         // There are some broken files. For instance, the Printf context in java.xml points to an
379         // inexistent Printf item data. These cases are considered to have normal text style.
380         return;
381     }
382
383     TextFormatId formatId = m_kateFormats.m_ids.value(itemData->style());
384     if (formatId != Normal) {
385         QTextCharFormat format = m_creatorFormats.value(formatId);
386
387         if (itemData->isCustomized()) {
388             // Please notice that the following are applied every time for item datas which have
389             // customizations. The configureFormats method could be used to provide a "one time"
390             // configuration, but it would probably require to traverse all item datas from all
391             // definitions available/loaded (either to set the values or for some "notifying"
392             // strategy). This is because the highlighter does not really know on which
393             // definition(s) it is working. Since not many item datas specify customizations I
394             // think this approach would fit better. If there are other ideas...
395             if (itemData->color().isValid())
396                 format.setForeground(itemData->color());
397             if (itemData->isItalicSpecified())
398                 format.setFontItalic(itemData->isItalic());
399             if (itemData->isBoldSpecified())
400                 format.setFontWeight(toFontWeight(itemData->isBold()));
401             if (itemData->isUnderlinedSpecified())
402                 format.setFontUnderline(itemData->isUnderlined());
403             if (itemData->isStrikedOutSpecified())
404                 format.setFontStrikeOut(itemData->isStrikedOut());
405         }
406
407         setFormat(offset, count, format);
408     }
409 }
410
411 void Highlighter::createWillContinueBlock()
412 {
413     BlockData *data = blockData(currentBlockUserData());
414     const int currentObservableState = extractObservableState(currentBlockState());
415     if (currentObservableState == Continued) {
416         BlockData *previousData = blockData(currentBlock().previous().userData());
417         data->m_originalObservableState = previousData->m_originalObservableState;
418     } else if (currentObservableState != WillContinue) {
419         data->m_originalObservableState = currentObservableState;
420     }
421     data->m_contextToContinue = m_currentContext;
422
423     setCurrentBlockState(computeState(WillContinue));
424 }
425
426 void Highlighter::analyseConsistencyOfWillContinueBlock(const QString &text)
427 {
428     if (currentBlock().next().isValid() && (
429         text.length() == 0 || text.at(text.length() - 1) != kBackSlash) &&
430         extractObservableState(currentBlock().next().userState()) != Continued) {
431         currentBlock().next().setUserState(computeState(Continued));
432     }
433
434     if (text.length() == 0 || text.at(text.length() - 1) != kBackSlash) {
435         BlockData *data = blockData(currentBlockUserData());
436         data->m_contextToContinue.clear();
437         setCurrentBlockState(computeState(data->m_originalObservableState));
438     }
439 }
440
441 void Highlighter::mapPersistentSequence(const QString &contextSequence)
442 {
443     if (!m_persistentObservableStates.contains(contextSequence)) {
444         int newState = m_persistentObservableStatesCounter;
445         m_persistentObservableStates.insert(contextSequence, newState);
446         m_persistentContexts.insert(newState, m_contexts);
447         ++m_persistentObservableStatesCounter;
448     }
449 }
450
451 void Highlighter::mapLeadingSequence(const QString &contextSequence)
452 {
453     if (!m_leadingObservableStates.contains(contextSequence))
454         m_leadingObservableStates.insert(contextSequence,
455                                          extractObservableState(currentBlockState()));
456 }
457
458 void Highlighter::pushContextSequence(int state)
459 {
460     const QVector<QSharedPointer<Context> > &contexts = m_persistentContexts.value(state);
461     for (int i = 0; i < contexts.size(); ++i)
462         m_contexts.push_back(contexts.at(i));
463 }
464
465 QString Highlighter::currentContextSequence() const
466 {
467     QString sequence;
468     for (int i = 0; i < m_contexts.size(); ++i)
469         sequence.append(m_contexts.at(i)->id());
470
471     return sequence;
472 }
473
474 Highlighter::BlockData *Highlighter::initializeBlockData()
475 {
476     BlockData *data = new BlockData;
477     setCurrentBlockUserData(data);
478     return data;
479 }
480
481 Highlighter::BlockData *Highlighter::blockData(QTextBlockUserData *userData)
482 {
483     return static_cast<BlockData *>(userData);
484 }
485
486 void Highlighter::pushDynamicContext(const QSharedPointer<Context> &baseContext)
487 {
488     // A dynamic context is created from another context which serves as its basis. Then,
489     // its rules are updated according to the captures from the calling regular expression which
490     // triggered the push of the dynamic context.
491     QSharedPointer<Context> context(new Context(*baseContext));
492     context->configureId(m_dynamicContextsCounter);
493     context->updateDynamicRules(m_currentCaptures);
494     m_contexts.push_back(context);
495     ++m_dynamicContextsCounter;
496 }
497
498 void Highlighter::assignCurrentContext()
499 {
500     if (m_contexts.isEmpty()) {
501         // This is not supposed to happen. However, there are broken files (for example, php.xml)
502         // which will cause this behaviour. In such cases pushing the default context is enough to
503         // keep highlighter working.
504         m_contexts.push_back(m_defaultContext);
505     }
506     m_currentContext = m_contexts.back();
507 }
508
509 int Highlighter::extractRegionDepth(const int state)
510 {
511     return state >> 12;
512 }
513
514 int Highlighter::extractObservableState(const int state)
515 {
516     return state & 0xFFF;
517 }
518
519 int Highlighter::computeState(const int observableState) const
520 {
521     return m_regionDepth << 12 | observableState;
522 }
523
524 void Highlighter::applyRegionBasedFolding() const
525 {
526     int folding = 0;
527     BlockData *data = blockData(currentBlockUserData());
528     BlockData *previousData = blockData(currentBlock().previous().userData());
529     if (previousData) {
530         folding = extractRegionDepth(previousBlockState());
531         if (data->m_foldingIndentDelta != 0) {
532             folding += data->m_foldingIndentDelta;
533             if (data->m_foldingIndentDelta > 0)
534                 data->setFoldingStartIncluded(true);
535             else
536                 previousData->setFoldingEndIncluded(false);
537             data->m_foldingIndentDelta = 0;
538         }
539     }
540     data->setFoldingEndIncluded(true);
541     data->setFoldingIndent(folding);
542 }
543
544 void Highlighter::applyIndentationBasedFolding(const QString &text) const
545 {
546     BlockData *data = blockData(currentBlockUserData());
547     data->setFoldingEndIncluded(true);
548
549     // If this line is empty, check its neighbours. They all might be part of the same block.
550     if (text.trimmed().isEmpty()) {
551         data->setFoldingIndent(0);
552         const int previousIndent = neighbouringNonEmptyBlockIndent(currentBlock().previous(), true);
553         if (previousIndent > 0) {
554             const int nextIndent = neighbouringNonEmptyBlockIndent(currentBlock().next(), false);
555             if (previousIndent == nextIndent)
556                 data->setFoldingIndent(previousIndent);
557         }
558     } else {
559         data->setFoldingIndent(m_tabSettings->indentationColumn(text));
560     }
561 }
562
563 int Highlighter::neighbouringNonEmptyBlockIndent(QTextBlock block, const bool previous) const
564 {
565     while (true) {
566         if (!block.isValid())
567             return 0;
568         if (block.text().trimmed().isEmpty()) {
569             if (previous)
570                 block = block.previous();
571             else
572                 block = block.next();
573         } else {
574             return m_tabSettings->indentationColumn(block.text());
575         }
576     }
577 }