diff options
Diffstat (limited to 'src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp')
-rw-r--r-- | src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp | 221 |
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; } |