1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
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.
16 ** GNU Lesser General Public License Usage
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.
25 ** If you are unsure which license is appropriate for your use, please
26 ** contact the sales department at http://qt.nokia.com/contact.
28 **************************************************************************/
30 #include "highlighter.h"
31 #include "highlightdefinition.h"
35 #include "highlighterexception.h"
36 #include "progressdata.h"
38 #include "tabsettings.h"
40 #include <QtCore/QLatin1String>
41 #include <QtCore/QLatin1Char>
43 using namespace TextEditor;
44 using namespace Internal;
47 static const QLatin1String kStay("#stay");
48 static const QLatin1String kPop("#pop");
49 static const QLatin1Char kBackSlash('\\');
50 static const QLatin1Char kHash('#');
53 const Highlighter::KateFormatMap Highlighter::m_kateFormats;
55 Highlighter::Highlighter(QTextDocument *parent) :
56 TextEditor::SyntaxHighlighter(parent),
58 m_indentationBasedFolding(false),
60 m_persistentObservableStatesCounter(PersistentsStart),
61 m_dynamicContextsCounter(0),
65 Highlighter::~Highlighter()
68 Highlighter::BlockData::BlockData() : m_foldingIndentDelta(0), m_originalObservableState(-1)
71 Highlighter::BlockData::~BlockData()
74 Highlighter::KateFormatMap::KateFormatMap()
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);
92 void Highlighter::configureFormat(TextFormatId id, const QTextCharFormat &format)
94 m_creatorFormats[id] = format;
97 void Highlighter::setDefaultContext(const QSharedPointer<Context> &defaultContext)
99 m_defaultContext = defaultContext;
100 m_persistentObservableStates.insert(m_defaultContext->name(), Default);
101 m_indentationBasedFolding = defaultContext->definition()->isIndentationBasedFolding();
104 void Highlighter::setTabSettings(const TabSettings &ts)
109 void Highlighter::highlightBlock(const QString &text)
111 if (!m_defaultContext.isNull() && !m_isBroken) {
113 if (!currentBlockUserData())
114 initializeBlockData();
115 setupDataForBlock(text);
117 handleContextChange(m_currentContext->lineBeginContext(),
118 m_currentContext->definition());
120 ProgressData progress;
121 const int length = text.length();
122 while (progress.offset() < length)
123 iterateThroughRules(text, length, &progress, false, m_currentContext->rules());
125 handleContextChange(m_currentContext->lineEndContext(),
126 m_currentContext->definition(),
130 if (m_indentationBasedFolding) {
131 applyIndentationBasedFolding(text);
133 applyRegionBasedFolding();
135 // In the case region depth has changed since the last time the state was set.
136 setCurrentBlockState(computeState(extractObservableState(currentBlockState())));
138 } catch (const HighlighterException &) {
143 applyFormatToSpaces(text, m_creatorFormats.value(VisualWhitespace));
146 void Highlighter::setupDataForBlock(const QString &text)
148 if (extractObservableState(currentBlockState()) == WillContinue)
149 analyseConsistencyOfWillContinueBlock(text);
151 if (previousBlockState() == -1) {
155 m_regionDepth = extractRegionDepth(previousBlockState());
156 const int observablePreviousState = extractObservableState(previousBlockState());
157 if (observablePreviousState == Default)
159 else if (observablePreviousState == WillContinue)
160 setupFromWillContinue();
161 else if (observablePreviousState == Continued)
162 setupFromContinued();
164 setupFromPersistent();
166 blockData(currentBlockUserData())->m_foldingRegions =
167 blockData(currentBlock().previous().userData())->m_foldingRegions;
170 assignCurrentContext();
173 void Highlighter::setupDefault()
175 m_contexts.push_back(m_defaultContext);
177 setCurrentBlockState(computeState(Default));
180 void Highlighter::setupFromWillContinue()
182 BlockData *previousData = blockData(currentBlock().previous().userData());
183 m_contexts.push_back(previousData->m_contextToContinue);
185 BlockData *data = blockData(currentBlock().userData());
186 data->m_originalObservableState = previousData->m_originalObservableState;
188 if (currentBlockState() == -1 || extractObservableState(currentBlockState()) == Default)
189 setCurrentBlockState(computeState(Continued));
192 void Highlighter::setupFromContinued()
194 BlockData *previousData = blockData(currentBlock().previous().userData());
196 Q_ASSERT(previousData->m_originalObservableState != WillContinue &&
197 previousData->m_originalObservableState != Continued);
199 if (previousData->m_originalObservableState == Default ||
200 previousData->m_originalObservableState == -1) {
201 m_contexts.push_back(m_defaultContext);
203 pushContextSequence(previousData->m_originalObservableState);
206 setCurrentBlockState(computeState(previousData->m_originalObservableState));
209 void Highlighter::setupFromPersistent()
211 pushContextSequence(extractObservableState(previousBlockState()));
213 setCurrentBlockState(previousBlockState());
216 void Highlighter::iterateThroughRules(const QString &text,
218 ProgressData *progress,
219 const bool childRule,
220 const QList<QSharedPointer<Rule> > &rules)
222 typedef QList<QSharedPointer<Rule> >::const_iterator RuleIterator;
224 bool contextChanged = false;
225 bool atLeastOneMatch = false;
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;
235 if (!m_indentationBasedFolding) {
236 if (!rule->beginRegion().isEmpty()) {
237 blockData(currentBlockUserData())->m_foldingRegions.push(rule->beginRegion());
239 if (progress->isOpeningBraceMatchAtFirstNonSpace())
240 ++blockData(currentBlockUserData())->m_foldingIndentDelta;
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();
248 if (progress->isClosingBraceMatchAtNonEnd())
249 --blockData(currentBlockUserData())->m_foldingIndentDelta;
252 progress->clearBracesMatches();
255 if (progress->isWillContinueLine()) {
256 createWillContinueBlock();
257 progress->setWillContinueLine(false);
259 if (rule->hasChild())
260 iterateThroughRules(text, length, progress, true, rule->childs());
262 if (!rule->context().isEmpty() && contextChangeRequired(rule->context())) {
263 m_currentCaptures = progress->captures();
264 changeContext(rule->context(), rule->definition());
265 contextChanged = true;
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());
276 applyFormat(startOffset, progress->offset() - startOffset, rule->itemData(),
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) {
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());
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();
307 bool Highlighter::contextChangeRequired(const QString &contextName) const
309 if (contextName == kStay)
314 void Highlighter::changeContext(const QString &contextName,
315 const QSharedPointer<HighlightDefinition> &definition,
316 const bool assignCurrent)
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();
323 if (extractObservableState(currentBlockState()) >= PersistentsStart) {
324 // One or more contexts were popped during during a persistent state.
325 const QString ¤tSequence = currentContextSequence();
326 if (m_persistentObservableStates.contains(currentSequence))
327 setCurrentBlockState(
328 computeState(m_persistentObservableStates.value(currentSequence)));
330 setCurrentBlockState(
331 computeState(m_leadingObservableStates.value(currentSequence)));
334 const QSharedPointer<Context> &context = definition->context(contextName);
336 if (context->isDynamic())
337 pushDynamicContext(context);
339 m_contexts.push_back(context);
341 if (m_contexts.back()->lineEndContext() == kStay ||
342 extractObservableState(currentBlockState()) >= PersistentsStart) {
343 const QString ¤tSequence = 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)));
355 assignCurrentContext();
358 void Highlighter::handleContextChange(const QString &contextName,
359 const QSharedPointer<HighlightDefinition> &definition,
360 const bool setCurrent)
362 if (!contextName.isEmpty() && contextChangeRequired(contextName))
363 changeContext(contextName, definition, setCurrent);
366 void Highlighter::applyFormat(int offset,
368 const QString &itemDataName,
369 const QSharedPointer<HighlightDefinition> &definition)
374 QSharedPointer<ItemData> itemData;
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.
383 TextFormatId formatId = m_kateFormats.m_ids.value(itemData->style());
384 if (formatId != Normal) {
385 QTextCharFormat format = m_creatorFormats.value(formatId);
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());
407 setFormat(offset, count, format);
411 void Highlighter::createWillContinueBlock()
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;
421 data->m_contextToContinue = m_currentContext;
423 setCurrentBlockState(computeState(WillContinue));
426 void Highlighter::analyseConsistencyOfWillContinueBlock(const QString &text)
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));
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));
441 void Highlighter::mapPersistentSequence(const QString &contextSequence)
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;
451 void Highlighter::mapLeadingSequence(const QString &contextSequence)
453 if (!m_leadingObservableStates.contains(contextSequence))
454 m_leadingObservableStates.insert(contextSequence,
455 extractObservableState(currentBlockState()));
458 void Highlighter::pushContextSequence(int state)
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));
465 QString Highlighter::currentContextSequence() const
468 for (int i = 0; i < m_contexts.size(); ++i)
469 sequence.append(m_contexts.at(i)->id());
474 Highlighter::BlockData *Highlighter::initializeBlockData()
476 BlockData *data = new BlockData;
477 setCurrentBlockUserData(data);
481 Highlighter::BlockData *Highlighter::blockData(QTextBlockUserData *userData)
483 return static_cast<BlockData *>(userData);
486 void Highlighter::pushDynamicContext(const QSharedPointer<Context> &baseContext)
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;
498 void Highlighter::assignCurrentContext()
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);
506 m_currentContext = m_contexts.back();
509 int Highlighter::extractRegionDepth(const int state)
514 int Highlighter::extractObservableState(const int state)
516 return state & 0xFFF;
519 int Highlighter::computeState(const int observableState) const
521 return m_regionDepth << 12 | observableState;
524 void Highlighter::applyRegionBasedFolding() const
527 BlockData *data = blockData(currentBlockUserData());
528 BlockData *previousData = blockData(currentBlock().previous().userData());
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);
536 previousData->setFoldingEndIncluded(false);
537 data->m_foldingIndentDelta = 0;
540 data->setFoldingEndIncluded(true);
541 data->setFoldingIndent(folding);
544 void Highlighter::applyIndentationBasedFolding(const QString &text) const
546 BlockData *data = blockData(currentBlockUserData());
547 data->setFoldingEndIncluded(true);
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);
559 data->setFoldingIndent(m_tabSettings->indentationColumn(text));
563 int Highlighter::neighbouringNonEmptyBlockIndent(QTextBlock block, const bool previous) const
566 if (!block.isValid())
568 if (block.text().trimmed().isEmpty()) {
570 block = block.previous();
572 block = block.next();
574 return m_tabSettings->indentationColumn(block.text());