aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp')
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp221
1 files changed, 139 insertions, 82 deletions
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp
index c4ef86a771..87dabadc7b 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp
@@ -1,24 +1,7 @@
/*
- Copyright (C) 2016 Volker Krause <vkrause@kde.org>
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
+
+ SPDX-License-Identifier: MIT
*/
#include "abstracthighlighter.h"
@@ -27,11 +10,12 @@
#include "definition_p.h"
#include "foldingregion.h"
#include "format.h"
+#include "ksyntaxhighlighting_logging.h"
#include "repository.h"
+#include "repository_p.h"
#include "rule_p.h"
#include "state.h"
#include "state_p.h"
-#include "ksyntaxhighlighting_logging.h"
#include "theme.h"
using namespace KSyntaxHighlighting;
@@ -53,20 +37,22 @@ void AbstractHighlighterPrivate::ensureDefinitionLoaded()
defData = DefinitionData::get(m_definition);
}
- if (Q_UNLIKELY(!defData->repo && !defData->fileName.isEmpty()))
+ if (Q_UNLIKELY(!defData->repo && !defData->fileName.isEmpty())) {
qCCritical(Log) << "Repository got deleted while a highlighter is still active!";
+ }
- if (m_definition.isValid())
+ if (m_definition.isValid()) {
defData->load();
+ }
}
-AbstractHighlighter::AbstractHighlighter() :
- d_ptr(new AbstractHighlighterPrivate)
+AbstractHighlighter::AbstractHighlighter()
+ : d_ptr(new AbstractHighlighterPrivate)
{
}
-AbstractHighlighter::AbstractHighlighter(AbstractHighlighterPrivate *dd) :
- d_ptr(dd)
+AbstractHighlighter::AbstractHighlighter(AbstractHighlighterPrivate *dd)
+ : d_ptr(dd)
{
}
@@ -102,7 +88,7 @@ void AbstractHighlighter::setTheme(const Theme &theme)
* Returns the index of the first non-space character. If the line is empty,
* or only contains white spaces, text.size() is returned.
*/
-static inline int firstNonSpaceChar(const QString & text)
+static inline int firstNonSpaceChar(QStringView text)
{
for (int i = 0; i < text.length(); ++i) {
if (!text[i].isSpace()) {
@@ -112,7 +98,7 @@ static inline int firstNonSpaceChar(const QString & text)
return text.size();
}
-State AbstractHighlighter::highlightLine(const QString& text, const State &state)
+State AbstractHighlighter::highlightLine(QStringView text, const State &state)
{
Q_D(AbstractHighlighter);
@@ -124,45 +110,49 @@ State AbstractHighlighter::highlightLine(const QString& text, const State &state
return State();
}
+ // limit the cache for unification to some reasonable size
+ // we use here at the moment 64k elements to not hog too much memory
+ // and to make the clearing no big stall
+ if (defData->unify.size() > 64 * 1024)
+ defData->unify.clear();
+
// verify/initialize state
auto newState = state;
auto stateData = StateData::get(newState);
- const DefinitionRef currentDefRef(d->m_definition);
- if (!stateData->isEmpty() && (stateData->m_defRef != currentDefRef)) {
+ bool isSharedData = true;
+ if (Q_UNLIKELY(stateData && stateData->m_defId != defData->id)) {
qCDebug(Log) << "Got invalid state, resetting.";
- stateData->clear();
+ stateData = nullptr;
}
- if (stateData->isEmpty()) {
+ if (Q_UNLIKELY(!stateData)) {
+ stateData = StateData::reset(newState);
stateData->push(defData->initialContext(), QStringList());
- stateData->m_defRef = currentDefRef;
+ stateData->m_defId = defData->id;
+ isSharedData = false;
}
// process empty lines
- if (text.isEmpty()) {
+ if (Q_UNLIKELY(text.isEmpty())) {
/**
* handle line empty context switches
* guard against endless loops
* see https://phabricator.kde.org/D18509
*/
int endlessLoopingCounter = 0;
- while (!stateData->topContext()->lineEmptyContext().isStay() || (stateData->topContext()->lineEmptyContext().isStay() && !stateData->topContext()->lineEndContext().isStay())) {
+ while (!stateData->topContext()->lineEmptyContext().isStay()) {
/**
* line empty context switches
*/
- if (!stateData->topContext()->lineEmptyContext().isStay()) {
- if (!d->switchContext(stateData, stateData->topContext()->lineEmptyContext(), QStringList())) {
- /**
- * end when trying to #pop the main context
- */
- break;
- }
- /**
- * line end context switches only when lineEmptyContext is #stay. This avoids
- * skipping empty lines after a line continuation character (see bug 405903)
- */
- } else if (!stateData->topContext()->lineEndContext().isStay() &&
- !d->switchContext(stateData, stateData->topContext()->lineEndContext(), QStringList()))
+ if (!d->switchContext(stateData, stateData->topContext()->lineEmptyContext(), QStringList(), newState, isSharedData)) {
+ /**
+ * end when trying to #pop the main context
+ */
break;
+ }
+
+ if (stateData->topContext()->stopEmptyLineContextSwitchLoop()) {
+ break;
+ }
// guard against endless loops
++endlessLoopingCounter;
@@ -173,12 +163,43 @@ State AbstractHighlighter::highlightLine(const QString& text, const State &state
}
auto context = stateData->topContext();
applyFormat(0, 0, context->attributeFormat());
- return newState;
+ return *defData->unify.insert(newState);
}
- int offset = 0, beginOffset = 0;
+ auto &dynamicRegexpCache = RepositoryPrivate::get(defData->repo)->m_dynamicRegexpCache;
+
+ int offset = 0;
+ int beginOffset = 0;
bool lineContinuation = false;
- QHash<Rule*, int> skipOffsets;
+
+ /**
+ * for expensive rules like regexes we do:
+ * - match them for the complete line, as this is faster than re-trying them at all positions
+ * - store the result of the first position that matches (or -1 for no match in the full line) in the skipOffsets hash for re-use
+ * - have capturesForLastDynamicSkipOffset as guard for dynamic regexes to invalidate the cache if they might have changed
+ */
+ QVarLengthArray<QPair<const Rule *, int>, 8> skipOffsets;
+ QStringList capturesForLastDynamicSkipOffset;
+
+ auto getSkipOffsetValue = [&skipOffsets](const Rule *r) -> int {
+ auto i = std::find_if(skipOffsets.begin(), skipOffsets.end(), [r](const auto &v) {
+ return v.first == r;
+ });
+ if (i == skipOffsets.end())
+ return 0;
+ return i->second;
+ };
+
+ auto insertSkipOffset = [&skipOffsets](const Rule *r, int i) {
+ auto it = std::find_if(skipOffsets.begin(), skipOffsets.end(), [r](const auto &v) {
+ return v.first == r;
+ });
+ if (it == skipOffsets.end()) {
+ skipOffsets.push_back({r, i});
+ } else {
+ it->second = i;
+ }
+ };
/**
* current active format
@@ -216,7 +237,8 @@ State AbstractHighlighter::highlightLine(const QString& text, const State &state
bool isLookAhead = false;
int newOffset = 0;
const Format *newFormat = nullptr;
- for (const auto &rule : stateData->topContext()->rules()) {
+ for (const auto &ruleShared : stateData->topContext()->rules()) {
+ auto rule = ruleShared.get();
/**
* filter out rules that require a specific column
*/
@@ -244,27 +266,43 @@ State AbstractHighlighter::highlightLine(const QString& text, const State &state
}
}
- /**
- * shall we skip application of this rule? two cases:
- * - rule can't match at all => currentSkipOffset < 0
- * - rule will only match for some higher offset => currentSkipOffset > offset
- */
- const auto currentSkipOffset = skipOffsets.value(rule.get());
- if (currentSkipOffset < 0 || currentSkipOffset > offset)
- continue;
-
+ int currentSkipOffset = 0;
+ if (Q_UNLIKELY(rule->hasSkipOffset())) {
+ /**
+ * shall we skip application of this rule? two cases:
+ * - rule can't match at all => currentSkipOffset < 0
+ * - rule will only match for some higher offset => currentSkipOffset > offset
+ *
+ * we need to invalidate this if we are dynamic and have different captures then last time
+ */
+ if (rule->isDynamic() && (capturesForLastDynamicSkipOffset != stateData->topCaptures())) {
+ skipOffsets.clear();
+ } else {
+ currentSkipOffset = getSkipOffsetValue(rule);
+ if (currentSkipOffset < 0 || currentSkipOffset > offset) {
+ continue;
+ }
+ }
+ }
- const auto newResult = rule->doMatch(text, offset, stateData->topCaptures());
+ auto newResult = rule->doMatch(text, offset, stateData->topCaptures(), dynamicRegexpCache);
newOffset = newResult.offset();
/**
* update skip offset if new one rules out any later match or is larger than current one
*/
- if (newResult.skipOffset() < 0 || newResult.skipOffset() > currentSkipOffset)
- skipOffsets.insert(rule.get(), newResult.skipOffset());
+ if (newResult.skipOffset() < 0 || newResult.skipOffset() > currentSkipOffset) {
+ insertSkipOffset(rule, newResult.skipOffset());
- if (newOffset <= offset)
+ // remember new captures, if dynamic to enforce proper reset above on change!
+ if (rule->isDynamic()) {
+ capturesForLastDynamicSkipOffset = stateData->topCaptures();
+ }
+ }
+
+ if (newOffset <= offset) {
continue;
+ }
/**
* apply folding.
@@ -272,32 +310,36 @@ State AbstractHighlighter::highlightLine(const QString& text, const State &state
* - rule with endRegion + beginRegion: in endRegion, the length is 0
* - rule with lookAhead: length is 0
*/
- if (rule->endRegion().isValid() && rule->beginRegion().isValid())
+ if (rule->endRegion().isValid() && rule->beginRegion().isValid()) {
applyFolding(offset, 0, rule->endRegion());
- else if (rule->endRegion().isValid())
+ } else if (rule->endRegion().isValid()) {
applyFolding(offset, rule->isLookAhead() ? 0 : newOffset - offset, rule->endRegion());
- if (rule->beginRegion().isValid())
+ }
+ if (rule->beginRegion().isValid()) {
applyFolding(offset, rule->isLookAhead() ? 0 : newOffset - offset, rule->beginRegion());
+ }
if (rule->isLookAhead()) {
Q_ASSERT(!rule->context().isStay());
- d->switchContext(stateData, rule->context(), newResult.captures());
+ d->switchContext(stateData, rule->context(), std::move(newResult.captures()), newState, isSharedData);
isLookAhead = true;
break;
}
- d->switchContext(stateData, rule->context(), newResult.captures());
+ d->switchContext(stateData, rule->context(), std::move(newResult.captures()), newState, isSharedData);
newFormat = rule->attributeFormat().isValid() ? &rule->attributeFormat() : &stateData->topContext()->attributeFormat();
- if (newOffset == text.size() && std::dynamic_pointer_cast<LineContinue>(rule))
+ if (newOffset == text.size() && rule->isLineContinue()) {
lineContinuation = true;
+ }
break;
}
- if (isLookAhead)
+ if (isLookAhead) {
continue;
+ }
if (newOffset <= offset) { // no matching rule
if (stateData->topContext()->fallthrough()) {
- d->switchContext(stateData, stateData->topContext()->fallthroughContext(), QStringList());
+ d->switchContext(stateData, stateData->topContext()->fallthroughContext(), QStringList(), newState, isSharedData);
continue;
}
@@ -314,8 +356,9 @@ State AbstractHighlighter::highlightLine(const QString& text, const State &state
* on format change, apply the last one and switch to new one
*/
if (newFormat != currentFormat && newFormat->id() != currentFormat->id()) {
- if (offset > 0)
+ if (offset > 0) {
applyFormat(beginOffset, offset - beginOffset, *currentFormat);
+ }
beginOffset = offset;
currentFormat = newFormat;
}
@@ -331,8 +374,9 @@ State AbstractHighlighter::highlightLine(const QString& text, const State &state
/**
* apply format for remaining text, if any
*/
- if (beginOffset < offset)
+ if (beginOffset < offset) {
applyFormat(beginOffset, text.size() - beginOffset, *currentFormat);
+ }
/**
* handle line end context switches
@@ -342,8 +386,9 @@ State AbstractHighlighter::highlightLine(const QString& text, const State &state
{
int endlessLoopingCounter = 0;
while (!stateData->topContext()->lineEndContext().isStay() && !lineContinuation) {
- if (!d->switchContext(stateData, stateData->topContext()->lineEndContext(), QStringList()))
+ if (!d->switchContext(stateData, stateData->topContext()->lineEndContext(), QStringList(), newState, isSharedData)) {
break;
+ }
// guard against endless loops
++endlessLoopingCounter;
@@ -354,18 +399,30 @@ State AbstractHighlighter::highlightLine(const QString& text, const State &state
}
}
- return newState;
+ return *defData->unify.insert(newState);
}
-bool AbstractHighlighterPrivate::switchContext(StateData *data, const ContextSwitch &contextSwitch, const QStringList &captures)
+bool AbstractHighlighterPrivate::switchContext(StateData *&data, const ContextSwitch &contextSwitch, QStringList &&captures, State &state, bool &isSharedData)
{
+ const auto popCount = contextSwitch.popCount();
+ const auto context = contextSwitch.context();
+ if (popCount <= 0 && !context) {
+ return true;
+ }
+
+ // a modified state must be detached before modification
+ if (isSharedData) {
+ data = StateData::detach(state);
+ isSharedData = false;
+ }
+
// kill as many items as requested from the stack, will always keep the initial context alive!
- const bool initialContextSurvived = data->pop(contextSwitch.popCount());
+ const bool initialContextSurvived = data->pop(popCount);
// if we have a new context to add, push it
// then we always "succeed"
- if (contextSwitch.context()) {
- data->push(contextSwitch.context(), captures);
+ if (context) {
+ data->push(context, std::move(captures));
return true;
}