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 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());
409 setFormat(offset, count, format);
414 void Highlighter::createWillContinueBlock()
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;
424 data->m_contextToContinue = m_currentContext;
426 setCurrentBlockState(computeState(WillContinue));
429 void Highlighter::analyseConsistencyOfWillContinueBlock(const QString &text)
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));
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));
444 void Highlighter::mapPersistentSequence(const QString &contextSequence)
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;
454 void Highlighter::mapLeadingSequence(const QString &contextSequence)
456 if (!m_leadingObservableStates.contains(contextSequence))
457 m_leadingObservableStates.insert(contextSequence,
458 extractObservableState(currentBlockState()));
461 void Highlighter::pushContextSequence(int state)
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));
468 QString Highlighter::currentContextSequence() const
471 for (int i = 0; i < m_contexts.size(); ++i)
472 sequence.append(m_contexts.at(i)->id());
477 Highlighter::BlockData *Highlighter::initializeBlockData()
479 BlockData *data = new BlockData;
480 setCurrentBlockUserData(data);
484 Highlighter::BlockData *Highlighter::blockData(QTextBlockUserData *userData)
486 return static_cast<BlockData *>(userData);
489 void Highlighter::pushDynamicContext(const QSharedPointer<Context> &baseContext)
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;
501 void Highlighter::assignCurrentContext()
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);
509 m_currentContext = m_contexts.back();
512 int Highlighter::extractRegionDepth(const int state)
517 int Highlighter::extractObservableState(const int state)
519 return state & 0xFFF;
522 int Highlighter::computeState(const int observableState) const
524 return m_regionDepth << 12 | observableState;
527 void Highlighter::applyRegionBasedFolding() const
530 BlockData *data = blockData(currentBlockUserData());
531 BlockData *previousData = blockData(currentBlock().previous().userData());
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);
539 previousData->setFoldingEndIncluded(false);
540 data->m_foldingIndentDelta = 0;
543 data->setFoldingEndIncluded(true);
544 data->setFoldingIndent(folding);
547 void Highlighter::applyIndentationBasedFolding(const QString &text) const
549 BlockData *data = blockData(currentBlockUserData());
550 data->setFoldingEndIncluded(true);
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);
562 data->setFoldingIndent(m_tabSettings->indentationColumn(text));
566 int Highlighter::neighbouringNonEmptyBlockIndent(QTextBlock block, const bool previous) const
569 if (!block.isValid())
571 if (block.text().trimmed().isEmpty()) {
573 block = block.previous();
575 block = block.next();
577 return m_tabSettings->indentationColumn(block.text());