OSDN Git Service

Generic highlighter: Apply format only if it's a configured one; Remove hard-coded...
[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         QHash<TextFormatId, QTextCharFormat>::const_iterator cit =
386             m_creatorFormats.constFind(formatId);
387         if (cit != m_creatorFormats.constEnd()) {
388             QTextCharFormat format = cit.value();
389             if (itemData->isCustomized()) {
390                 // Please notice that the following are applied every time for item datas which have
391                 // customizations. The configureFormats method could be used to provide a "one time"
392                 // configuration, but it would probably require to traverse all item datas from all
393                 // definitions available/loaded (either to set the values or for some "notifying"
394                 // strategy). This is because the highlighter does not really know on which
395                 // definition(s) it is working. Since not many item datas specify customizations I
396                 // think this approach would fit better. If there are other ideas...
397                 if (itemData->color().isValid())
398                     format.setForeground(itemData->color());
399                 if (itemData->isItalicSpecified())
400                     format.setFontItalic(itemData->isItalic());
401                 if (itemData->isBoldSpecified())
402                     format.setFontWeight(toFontWeight(itemData->isBold()));
403                 if (itemData->isUnderlinedSpecified())
404                     format.setFontUnderline(itemData->isUnderlined());
405                 if (itemData->isStrikedOutSpecified())
406                     format.setFontStrikeOut(itemData->isStrikedOut());
407             }
408
409             setFormat(offset, count, format);
410         }
411     }
412 }
413
414 void Highlighter::createWillContinueBlock()
415 {
416     BlockData *data = blockData(currentBlockUserData());
417     const int currentObservableState = extractObservableState(currentBlockState());
418     if (currentObservableState == Continued) {
419         BlockData *previousData = blockData(currentBlock().previous().userData());
420         data->m_originalObservableState = previousData->m_originalObservableState;
421     } else if (currentObservableState != WillContinue) {
422         data->m_originalObservableState = currentObservableState;
423     }
424     data->m_contextToContinue = m_currentContext;
425
426     setCurrentBlockState(computeState(WillContinue));
427 }
428
429 void Highlighter::analyseConsistencyOfWillContinueBlock(const QString &text)
430 {
431     if (currentBlock().next().isValid() && (
432         text.length() == 0 || text.at(text.length() - 1) != kBackSlash) &&
433         extractObservableState(currentBlock().next().userState()) != Continued) {
434         currentBlock().next().setUserState(computeState(Continued));
435     }
436
437     if (text.length() == 0 || text.at(text.length() - 1) != kBackSlash) {
438         BlockData *data = blockData(currentBlockUserData());
439         data->m_contextToContinue.clear();
440         setCurrentBlockState(computeState(data->m_originalObservableState));
441     }
442 }
443
444 void Highlighter::mapPersistentSequence(const QString &contextSequence)
445 {
446     if (!m_persistentObservableStates.contains(contextSequence)) {
447         int newState = m_persistentObservableStatesCounter;
448         m_persistentObservableStates.insert(contextSequence, newState);
449         m_persistentContexts.insert(newState, m_contexts);
450         ++m_persistentObservableStatesCounter;
451     }
452 }
453
454 void Highlighter::mapLeadingSequence(const QString &contextSequence)
455 {
456     if (!m_leadingObservableStates.contains(contextSequence))
457         m_leadingObservableStates.insert(contextSequence,
458                                          extractObservableState(currentBlockState()));
459 }
460
461 void Highlighter::pushContextSequence(int state)
462 {
463     const QVector<QSharedPointer<Context> > &contexts = m_persistentContexts.value(state);
464     for (int i = 0; i < contexts.size(); ++i)
465         m_contexts.push_back(contexts.at(i));
466 }
467
468 QString Highlighter::currentContextSequence() const
469 {
470     QString sequence;
471     for (int i = 0; i < m_contexts.size(); ++i)
472         sequence.append(m_contexts.at(i)->id());
473
474     return sequence;
475 }
476
477 Highlighter::BlockData *Highlighter::initializeBlockData()
478 {
479     BlockData *data = new BlockData;
480     setCurrentBlockUserData(data);
481     return data;
482 }
483
484 Highlighter::BlockData *Highlighter::blockData(QTextBlockUserData *userData)
485 {
486     return static_cast<BlockData *>(userData);
487 }
488
489 void Highlighter::pushDynamicContext(const QSharedPointer<Context> &baseContext)
490 {
491     // A dynamic context is created from another context which serves as its basis. Then,
492     // its rules are updated according to the captures from the calling regular expression which
493     // triggered the push of the dynamic context.
494     QSharedPointer<Context> context(new Context(*baseContext));
495     context->configureId(m_dynamicContextsCounter);
496     context->updateDynamicRules(m_currentCaptures);
497     m_contexts.push_back(context);
498     ++m_dynamicContextsCounter;
499 }
500
501 void Highlighter::assignCurrentContext()
502 {
503     if (m_contexts.isEmpty()) {
504         // This is not supposed to happen. However, there are broken files (for example, php.xml)
505         // which will cause this behaviour. In such cases pushing the default context is enough to
506         // keep highlighter working.
507         m_contexts.push_back(m_defaultContext);
508     }
509     m_currentContext = m_contexts.back();
510 }
511
512 int Highlighter::extractRegionDepth(const int state)
513 {
514     return state >> 12;
515 }
516
517 int Highlighter::extractObservableState(const int state)
518 {
519     return state & 0xFFF;
520 }
521
522 int Highlighter::computeState(const int observableState) const
523 {
524     return m_regionDepth << 12 | observableState;
525 }
526
527 void Highlighter::applyRegionBasedFolding() const
528 {
529     int folding = 0;
530     BlockData *data = blockData(currentBlockUserData());
531     BlockData *previousData = blockData(currentBlock().previous().userData());
532     if (previousData) {
533         folding = extractRegionDepth(previousBlockState());
534         if (data->m_foldingIndentDelta != 0) {
535             folding += data->m_foldingIndentDelta;
536             if (data->m_foldingIndentDelta > 0)
537                 data->setFoldingStartIncluded(true);
538             else
539                 previousData->setFoldingEndIncluded(false);
540             data->m_foldingIndentDelta = 0;
541         }
542     }
543     data->setFoldingEndIncluded(true);
544     data->setFoldingIndent(folding);
545 }
546
547 void Highlighter::applyIndentationBasedFolding(const QString &text) const
548 {
549     BlockData *data = blockData(currentBlockUserData());
550     data->setFoldingEndIncluded(true);
551
552     // If this line is empty, check its neighbours. They all might be part of the same block.
553     if (text.trimmed().isEmpty()) {
554         data->setFoldingIndent(0);
555         const int previousIndent = neighbouringNonEmptyBlockIndent(currentBlock().previous(), true);
556         if (previousIndent > 0) {
557             const int nextIndent = neighbouringNonEmptyBlockIndent(currentBlock().next(), false);
558             if (previousIndent == nextIndent)
559                 data->setFoldingIndent(previousIndent);
560         }
561     } else {
562         data->setFoldingIndent(m_tabSettings->indentationColumn(text));
563     }
564 }
565
566 int Highlighter::neighbouringNonEmptyBlockIndent(QTextBlock block, const bool previous) const
567 {
568     while (true) {
569         if (!block.isValid())
570             return 0;
571         if (block.text().trimmed().isEmpty()) {
572             if (previous)
573                 block = block.previous();
574             else
575                 block = block.next();
576         } else {
577             return m_tabSettings->indentationColumn(block.text());
578         }
579     }
580 }