feat: add missing KF6 framework recipes
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
add_subdirectory(indexer)
|
||||
if(TARGET Qt6::Gui)
|
||||
add_subdirectory(lib)
|
||||
add_subdirectory(cli)
|
||||
endif()
|
||||
if(TARGET Qt6::Quick)
|
||||
add_subdirectory(quick)
|
||||
endif()
|
||||
|
||||
ecm_qt_install_logging_categories(
|
||||
EXPORT KSYNTAXHIGHLIGHTING
|
||||
FILE ksyntaxhighlighting.categories
|
||||
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
# extract language and section names from XML syntax definition files
|
||||
# and adapt for lupdate-based extraction to match the rest of the code
|
||||
$EXTRACTATTR --attr=language,name,Language --attr="language,section,Language Section" ../data/syntax/*.xml >> rc.cpp || exit 12
|
||||
sed -i -e 's/^i18nc/QT_TRANSLATE_NOOP/' rc.cpp
|
||||
|
||||
grep --no-filename '"name"' ../data/themes/*.theme | \
|
||||
sed -r -e 's/"name"/QT_TRANSLATE_NOOP("Theme", /; s/://; s/,?$/);/' >> rc.cpp
|
||||
|
||||
$EXTRACT_TR_STRINGS `find . -name \*.cpp -o -name \*.h -o -name \*.ui -o -name \*.qml` -o $podir/syntaxhighlighting6_qt.pot
|
||||
@@ -0,0 +1,5 @@
|
||||
add_executable(ksyntaxhighlighter6 ksyntaxhighlighter.cpp)
|
||||
ecm_mark_nongui_executable(ksyntaxhighlighter6)
|
||||
target_link_libraries(ksyntaxhighlighter6 KF6SyntaxHighlighting)
|
||||
|
||||
install(TARGETS ksyntaxhighlighter6 ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "ksyntaxhighlighting_version.h"
|
||||
|
||||
#include <KSyntaxHighlighting/Definition>
|
||||
#include <KSyntaxHighlighting/DefinitionDownloader>
|
||||
#include <KSyntaxHighlighting/Repository>
|
||||
#include <KSyntaxHighlighting/Theme>
|
||||
#include <ansihighlighter.h>
|
||||
#include <htmlhighlighter.h>
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
template<class Highlighter, class... Ts>
|
||||
static void applyHighlighter(Highlighter &highlighter,
|
||||
QCommandLineParser &parser,
|
||||
bool fromFileName,
|
||||
const QString &inFileName,
|
||||
const QCommandLineOption &outputName,
|
||||
const Ts &...highlightParams)
|
||||
{
|
||||
if (parser.isSet(outputName)) {
|
||||
highlighter.setOutputFile(parser.value(outputName));
|
||||
} else {
|
||||
highlighter.setOutputFile(stdout);
|
||||
}
|
||||
|
||||
if (fromFileName) {
|
||||
highlighter.highlightFile(inFileName, highlightParams...);
|
||||
} else {
|
||||
QFile inFile;
|
||||
inFile.open(stdin, QIODevice::ReadOnly);
|
||||
highlighter.highlightData(&inFile, highlightParams...);
|
||||
}
|
||||
}
|
||||
|
||||
static Theme theme(const Repository &repo, const QString &themeName, Repository::DefaultTheme t)
|
||||
{
|
||||
if (themeName.isEmpty()) {
|
||||
return repo.defaultTheme(t);
|
||||
}
|
||||
return repo.theme(themeName);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
QCoreApplication::setApplicationName(QStringLiteral("ksyntaxhighlighter"));
|
||||
QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org"));
|
||||
QCoreApplication::setOrganizationName(QStringLiteral("KDE"));
|
||||
QCoreApplication::setApplicationVersion(QStringLiteral(KSYNTAXHIGHLIGHTING_VERSION_STRING));
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(app.translate("SyntaxHighlightingCLI", "Command line syntax highlighter using KSyntaxHighlighting syntax definitions."));
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
parser.addPositionalArgument(
|
||||
app.translate("SyntaxHighlightingCLI", "source"),
|
||||
app.translate("SyntaxHighlightingCLI", "The source file to highlight. If absent, read the file from stdin and the --syntax option must be used."));
|
||||
|
||||
QCommandLineOption listDefs(QStringList() << QStringLiteral("l") << QStringLiteral("list"),
|
||||
app.translate("SyntaxHighlightingCLI", "List all available syntax definitions."));
|
||||
parser.addOption(listDefs);
|
||||
QCommandLineOption listThemes(QStringList() << QStringLiteral("list-themes"), app.translate("SyntaxHighlightingCLI", "List all available themes."));
|
||||
parser.addOption(listThemes);
|
||||
|
||||
QCommandLineOption updateDefs(QStringList() << QStringLiteral("u") << QStringLiteral("update"),
|
||||
app.translate("SyntaxHighlightingCLI", "Download new/updated syntax definitions."));
|
||||
parser.addOption(updateDefs);
|
||||
|
||||
QCommandLineOption outputName(QStringList() << QStringLiteral("o") << QStringLiteral("output"),
|
||||
app.translate("SyntaxHighlightingCLI", "File to write HTML output to (default: stdout)."),
|
||||
app.translate("SyntaxHighlightingCLI", "output"));
|
||||
parser.addOption(outputName);
|
||||
|
||||
QCommandLineOption syntaxName(QStringList() << QStringLiteral("s") << QStringLiteral("syntax"),
|
||||
app.translate("SyntaxHighlightingCLI", "Highlight using this syntax definition (default: auto-detect based on input file)."),
|
||||
app.translate("SyntaxHighlightingCLI", "syntax"));
|
||||
parser.addOption(syntaxName);
|
||||
|
||||
QCommandLineOption themeName(QStringList() << QStringLiteral("t") << QStringLiteral("theme"),
|
||||
app.translate("SyntaxHighlightingCLI", "Color theme to use for highlighting."),
|
||||
app.translate("SyntaxHighlightingCLI", "theme"));
|
||||
parser.addOption(themeName);
|
||||
|
||||
QCommandLineOption outputFormatOption(QStringList() << QStringLiteral("f") << QStringLiteral("output-format"),
|
||||
app.translate("SyntaxHighlightingCLI", "Use the specified format instead of html. Must be html, ansi or ansi256."),
|
||||
app.translate("SyntaxHighlightingCLI", "format"),
|
||||
QStringLiteral("html"));
|
||||
parser.addOption(outputFormatOption);
|
||||
|
||||
QCommandLineOption traceOption(QStringList() << QStringLiteral("syntax-trace"),
|
||||
app.translate("SyntaxHighlightingCLI",
|
||||
"Add information to debug a syntax file. Only works with --output-format=ansi or ansi256. Possible "
|
||||
"values are format, region, context, stackSize and all."),
|
||||
app.translate("SyntaxHighlightingCLI", "type"));
|
||||
parser.addOption(traceOption);
|
||||
|
||||
QCommandLineOption noAnsiEditorBg(QStringList() << QStringLiteral("b") << QStringLiteral("no-ansi-background"),
|
||||
app.translate("SyntaxHighlightingCLI", "Disable ANSI background for the default color."));
|
||||
parser.addOption(noAnsiEditorBg);
|
||||
|
||||
QCommandLineOption bgRole(QStringList() << QStringLiteral("B") << QStringLiteral("background-role"),
|
||||
app.translate("SyntaxHighlightingCLI", "Select background color role from theme."),
|
||||
app.translate("SyntaxHighlightingCLI", "role"));
|
||||
parser.addOption(bgRole);
|
||||
|
||||
QCommandLineOption unbufferedAnsi(QStringList() << QStringLiteral("U") << QStringLiteral("unbuffered"),
|
||||
app.translate("SyntaxHighlightingCLI", "For ansi and ansi256 formats, flush the output buffer on each line."));
|
||||
parser.addOption(unbufferedAnsi);
|
||||
|
||||
QCommandLineOption titleOption(
|
||||
QStringList() << QStringLiteral("T") << QStringLiteral("title"),
|
||||
app.translate("SyntaxHighlightingCLI", "Set HTML page's title\n(default: the filename or \"KSyntaxHighlighter\" if reading from stdin)."),
|
||||
app.translate("SyntaxHighlightingCLI", "title"));
|
||||
parser.addOption(titleOption);
|
||||
|
||||
parser.process(app);
|
||||
|
||||
Repository repo;
|
||||
|
||||
if (parser.isSet(listDefs)) {
|
||||
for (const auto &def : repo.definitions()) {
|
||||
fprintf(stdout, "%s\n", qPrintable(def.name()));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (parser.isSet(listThemes)) {
|
||||
for (const auto &theme : repo.themes()) {
|
||||
fprintf(stdout, "%s\n", qPrintable(theme.name()));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Theme::EditorColorRole bgColorRole = Theme::BackgroundColor;
|
||||
|
||||
if (parser.isSet(bgRole)) {
|
||||
/*
|
||||
* Theme::EditorColorRole contains border, foreground and background colors.
|
||||
* To ensure that only the background colors used in text editing are used,
|
||||
* QMetaEnum is avoided and values are listed in hard.
|
||||
*/
|
||||
|
||||
struct BgRole {
|
||||
QStringView name;
|
||||
Theme::EditorColorRole role;
|
||||
// name for display
|
||||
const char *asciiName;
|
||||
};
|
||||
|
||||
#define BG_ROLE(role) \
|
||||
BgRole \
|
||||
{ \
|
||||
QStringView(u"" #role, sizeof(#role) - 1), Theme::role, #role \
|
||||
}
|
||||
constexpr BgRole bgRoles[] = {
|
||||
BG_ROLE(BackgroundColor),
|
||||
BG_ROLE(TextSelection),
|
||||
BG_ROLE(CurrentLine),
|
||||
BG_ROLE(SearchHighlight),
|
||||
BG_ROLE(ReplaceHighlight),
|
||||
BG_ROLE(BracketMatching),
|
||||
BG_ROLE(CodeFolding),
|
||||
BG_ROLE(MarkBookmark),
|
||||
BG_ROLE(MarkBreakpointActive),
|
||||
BG_ROLE(MarkBreakpointReached),
|
||||
BG_ROLE(MarkBreakpointDisabled),
|
||||
BG_ROLE(MarkExecution),
|
||||
BG_ROLE(MarkWarning),
|
||||
BG_ROLE(MarkError),
|
||||
BG_ROLE(TemplateBackground),
|
||||
BG_ROLE(TemplatePlaceholder),
|
||||
BG_ROLE(TemplateFocusedPlaceholder),
|
||||
BG_ROLE(TemplateReadOnlyPlaceholder),
|
||||
};
|
||||
#undef BG_ROLE
|
||||
|
||||
const auto role = parser.value(bgRole);
|
||||
bool ok = false;
|
||||
for (const auto &def : bgRoles) {
|
||||
if (def.name == role) {
|
||||
bgColorRole = def.role;
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
fprintf(stderr, "Unknown background role. Expected:\n");
|
||||
for (const auto &def : bgRoles) {
|
||||
fprintf(stderr, " - %s\n", def.asciiName);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.isSet(updateDefs)) {
|
||||
DefinitionDownloader downloader(&repo);
|
||||
QObject::connect(&downloader, &DefinitionDownloader::informationMessage, &app, [](const QString &msg) {
|
||||
fprintf(stdout, "%s\n", qPrintable(msg));
|
||||
});
|
||||
QObject::connect(&downloader, &DefinitionDownloader::done, &app, &QCoreApplication::quit);
|
||||
downloader.start();
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
bool fromFileName = false;
|
||||
QString inFileName;
|
||||
if (parser.positionalArguments().size() == 1) {
|
||||
fromFileName = true;
|
||||
inFileName = parser.positionalArguments().at(0);
|
||||
}
|
||||
|
||||
Definition def;
|
||||
if (parser.isSet(syntaxName)) {
|
||||
const QString syntax = parser.value(syntaxName);
|
||||
def = repo.definitionForName(syntax);
|
||||
if (!def.isValid()) {
|
||||
/* see if it's a mimetype instead */
|
||||
def = repo.definitionForMimeType(syntax);
|
||||
if (!def.isValid()) {
|
||||
/* see if it's a extension instead */
|
||||
def = repo.definitionForFileName(QLatin1String("f.") + syntax);
|
||||
if (!def.isValid()) {
|
||||
/* see if it's a filename instead */
|
||||
def = repo.definitionForFileName(syntax);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (fromFileName) {
|
||||
def = repo.definitionForFileName(inFileName);
|
||||
} else {
|
||||
parser.showHelp(1);
|
||||
}
|
||||
|
||||
if (!def.isValid()) {
|
||||
fprintf(stderr, "Unknown syntax.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const QString outputFormat = parser.value(outputFormatOption);
|
||||
if (0 == outputFormat.compare(QLatin1String("html"), Qt::CaseInsensitive)) {
|
||||
QString title;
|
||||
if (parser.isSet(titleOption)) {
|
||||
title = parser.value(titleOption);
|
||||
}
|
||||
|
||||
HtmlHighlighter highlighter;
|
||||
highlighter.setDefinition(def);
|
||||
highlighter.setBackgroundRole(bgColorRole);
|
||||
highlighter.setTheme(theme(repo, parser.value(themeName), Repository::LightTheme));
|
||||
applyHighlighter(highlighter, parser, fromFileName, inFileName, outputName, title);
|
||||
} else {
|
||||
auto AnsiFormat = AnsiHighlighter::AnsiFormat::TrueColor;
|
||||
// compatible with the old ansi256Colors value
|
||||
if (outputFormat.startsWith(QLatin1String("ansi256"), Qt::CaseInsensitive)) {
|
||||
AnsiFormat = AnsiHighlighter::AnsiFormat::XTerm256Color;
|
||||
} else if (0 != outputFormat.compare(QLatin1String("ansi"), Qt::CaseInsensitive)) {
|
||||
fprintf(stderr, "Unknown output format.\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
AnsiHighlighter::Options options{};
|
||||
options |= parser.isSet(noAnsiEditorBg) ? AnsiHighlighter::Option::NoOptions : AnsiHighlighter::Option::UseEditorBackground;
|
||||
options |= parser.isSet(unbufferedAnsi) ? AnsiHighlighter::Option::Unbuffered : AnsiHighlighter::Option::NoOptions;
|
||||
if (parser.isSet(traceOption)) {
|
||||
const auto traceOptions = parser.values(traceOption);
|
||||
for (auto const &option : traceOptions) {
|
||||
if (option == QStringLiteral("format")) {
|
||||
options |= AnsiHighlighter::Option::TraceFormat;
|
||||
} else if (option == QStringLiteral("region")) {
|
||||
options |= AnsiHighlighter::Option::TraceRegion;
|
||||
} else if (option == QStringLiteral("context")) {
|
||||
options |= AnsiHighlighter::Option::TraceContext;
|
||||
} else if (option == QStringLiteral("stackSize")) {
|
||||
options |= AnsiHighlighter::Option::TraceStackSize;
|
||||
} else if (option == QStringLiteral("all")) {
|
||||
options |= AnsiHighlighter::Option::TraceAll;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown trace name.\n");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnsiHighlighter highlighter;
|
||||
highlighter.setDefinition(def);
|
||||
highlighter.setBackgroundRole(bgColorRole);
|
||||
highlighter.setTheme(theme(repo, parser.value(themeName), Repository::DarkTheme));
|
||||
applyHighlighter(highlighter, parser, fromFileName, inFileName, outputName, AnsiFormat, options);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# when cross compiling, use either the executable offered or try to cross-compile it in place
|
||||
|
||||
if(CMAKE_CROSSCOMPILING AND KATEHIGHLIGHTINGINDEXER_EXECUTABLE)
|
||||
add_executable(katehighlightingindexer IMPORTED GLOBAL)
|
||||
set_target_properties(katehighlightingindexer PROPERTIES IMPORTED_LOCATION ${KATEHIGHLIGHTINGINDEXER_EXECUTABLE})
|
||||
elseif(CMAKE_CROSSCOMPILING)
|
||||
include(ECMQueryQt)
|
||||
ecm_query_qt(NATIVE_PREFIX QT_HOST_PREFIX)
|
||||
message(STATUS "Building katehighlightingindexer against ${NATIVE_PREFIX}")
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(native_katehighlightingindexer
|
||||
SOURCE_DIR ${CMAKE_SOURCE_DIR}
|
||||
CMAKE_ARGS -DKSYNTAXHIGHLIGHTING_USE_GUI=OFF
|
||||
-DECM_DIR=${ECM_DIR} -DCMAKE_PREFIX_PATH=${NATIVE_PREFIX}
|
||||
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}
|
||||
-DQT_MAJOR_VERSION=6
|
||||
INSTALL_COMMAND ""
|
||||
BUILD_BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/native_katehighlightingindexer-prefix/src/native_katehighlightingindexer-build/bin/katehighlightingindexer
|
||||
)
|
||||
add_executable(katehighlightingindexer IMPORTED GLOBAL)
|
||||
add_dependencies(katehighlightingindexer native_katehighlightingindexer)
|
||||
set_target_properties(katehighlightingindexer PROPERTIES IMPORTED_LOCATION
|
||||
${CMAKE_CURRENT_BINARY_DIR}/native_katehighlightingindexer-prefix/src/native_katehighlightingindexer-build/bin/katehighlightingindexer)
|
||||
else()
|
||||
# host build
|
||||
add_executable(katehighlightingindexer katehighlightingindexer.cpp ../lib/worddelimiters.cpp)
|
||||
ecm_mark_nongui_executable(katehighlightingindexer)
|
||||
if(XercesC_FOUND)
|
||||
add_definitions(-DHAS_XERCESC)
|
||||
kde_target_enable_exceptions(katehighlightingindexer PRIVATE)
|
||||
target_link_libraries(katehighlightingindexer Qt6::Core XercesC::XercesC)
|
||||
else()
|
||||
target_link_libraries(katehighlightingindexer Qt6::Core)
|
||||
endif()
|
||||
endif()
|
||||
+3486
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,115 @@
|
||||
add_library(KF6SyntaxHighlighting)
|
||||
|
||||
set_target_properties(KF6SyntaxHighlighting PROPERTIES
|
||||
VERSION ${KSYNTAXHIGHLIGHTING_VERSION}
|
||||
SOVERSION ${KSYNTAXHIGHLIGHTING_SOVERSION}
|
||||
EXPORT_NAME SyntaxHighlighting
|
||||
)
|
||||
|
||||
ecm_create_qm_loader(syntax_highlighting_QM_LOADER syntaxhighlighting6_qt)
|
||||
|
||||
target_sources(KF6SyntaxHighlighting PRIVATE
|
||||
abstracthighlighter.cpp
|
||||
context.cpp
|
||||
contextswitch.cpp
|
||||
definitiondownloader.cpp
|
||||
highlightingdata.cpp
|
||||
foldingregion.cpp
|
||||
format.cpp
|
||||
htmlhighlighter.cpp
|
||||
ansihighlighter.cpp
|
||||
keywordlist.cpp
|
||||
rule.cpp
|
||||
definition.cpp
|
||||
repository.cpp
|
||||
state.cpp
|
||||
syntaxhighlighter.cpp
|
||||
theme.cpp
|
||||
wildcardmatcher.cpp
|
||||
themedata.cpp
|
||||
worddelimiters.cpp
|
||||
${syntax_highlighting_QM_LOADER}
|
||||
$<TARGET_OBJECTS:SyntaxHighlightingData>
|
||||
)
|
||||
ecm_qt_declare_logging_category(KF6SyntaxHighlighting
|
||||
HEADER ksyntaxhighlighting_logging.h
|
||||
IDENTIFIER KSyntaxHighlighting::Log
|
||||
CATEGORY_NAME kf.syntaxhighlighting
|
||||
OLD_CATEGORY_NAMES org.kde.ksyntaxhighlighting
|
||||
DESCRIPTION "Syntax Highlighting"
|
||||
EXPORT KSYNTAXHIGHLIGHTING
|
||||
)
|
||||
|
||||
ecm_generate_export_header(KF6SyntaxHighlighting
|
||||
BASE_NAME KSyntaxHighlighting
|
||||
GROUP_BASE_NAME KF
|
||||
VERSION ${KF_VERSION}
|
||||
USE_VERSION_HEADER
|
||||
DEPRECATED_BASE_VERSION 0
|
||||
DEPRECATION_VERSIONS
|
||||
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
|
||||
)
|
||||
|
||||
target_link_libraries(KF6SyntaxHighlighting
|
||||
PUBLIC
|
||||
Qt6::Gui
|
||||
PRIVATE
|
||||
Qt6::Network
|
||||
)
|
||||
|
||||
set(Forwarding_Header_Names
|
||||
AbstractHighlighter
|
||||
Definition
|
||||
DefinitionDownloader
|
||||
FoldingRegion
|
||||
Format
|
||||
Repository
|
||||
State
|
||||
SyntaxHighlighter
|
||||
Theme
|
||||
WildcardMatcher
|
||||
)
|
||||
|
||||
ecm_generate_headers(CamelCase_HEADERS
|
||||
HEADER_NAMES ${Forwarding_Header_Names}
|
||||
REQUIRED_HEADERS SyntaxHighlighting_HEADERS
|
||||
OUTPUT_DIR ${CMAKE_BINARY_DIR}/KSyntaxHighlighting/KSyntaxHighlighting
|
||||
)
|
||||
|
||||
target_include_directories(KF6SyntaxHighlighting
|
||||
INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KSyntaxHighlighting>"
|
||||
PUBLIC "$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/KSyntaxHighlighting;>"
|
||||
)
|
||||
|
||||
install(TARGETS KF6SyntaxHighlighting EXPORT KF6SyntaxHighlightingTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
|
||||
install(FILES
|
||||
${CamelCase_HEADERS}
|
||||
${SyntaxHighlighting_HEADERS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/ksyntaxhighlighting_export.h
|
||||
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KSyntaxHighlighting/KSyntaxHighlighting
|
||||
)
|
||||
|
||||
if(BUILD_QCH)
|
||||
ecm_add_qch(
|
||||
KF6SyntaxHighlighting_QCH
|
||||
NAME KSyntaxHighlighting
|
||||
BASE_NAME KF6SyntaxHighlighting
|
||||
VERSION ${KF_VERSION}
|
||||
ORG_DOMAIN org.kde
|
||||
SOURCES # using only public headers, to cover only public API
|
||||
${SyntaxHighlighting_HEADERS}
|
||||
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
|
||||
LINK_QCHS
|
||||
Qt6Core_QCH
|
||||
Qt6Gui_QCH
|
||||
INCLUDE_DIRS
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
BLANK_MACROS
|
||||
KSYNTAXHIGHLIGHTING_EXPORT
|
||||
KSYNTAXHIGHLIGHTING_DEPRECATED
|
||||
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
|
||||
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
|
||||
COMPONENT Devel
|
||||
)
|
||||
endif()
|
||||
@@ -0,0 +1,440 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "abstracthighlighter.h"
|
||||
#include "abstracthighlighter_p.h"
|
||||
#include "context_p.h"
|
||||
#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 "theme.h"
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
AbstractHighlighterPrivate::AbstractHighlighterPrivate()
|
||||
{
|
||||
}
|
||||
|
||||
AbstractHighlighterPrivate::~AbstractHighlighterPrivate()
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractHighlighterPrivate::ensureDefinitionLoaded()
|
||||
{
|
||||
auto defData = DefinitionData::get(m_definition);
|
||||
if (Q_UNLIKELY(!m_definition.isValid())) {
|
||||
if (defData->repo && !defData->name.isEmpty()) {
|
||||
qCDebug(Log) << "Definition became invalid, trying re-lookup.";
|
||||
m_definition = defData->repo->definitionForName(defData->name);
|
||||
defData = DefinitionData::get(m_definition);
|
||||
}
|
||||
|
||||
if (Q_UNLIKELY(!defData->repo && !defData->fileName.isEmpty())) {
|
||||
qCCritical(Log) << "Repository got deleted while a highlighter is still active!";
|
||||
}
|
||||
}
|
||||
|
||||
if (m_definition.isValid()) {
|
||||
defData->load();
|
||||
}
|
||||
}
|
||||
|
||||
AbstractHighlighter::AbstractHighlighter()
|
||||
: d_ptr(new AbstractHighlighterPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
AbstractHighlighter::AbstractHighlighter(AbstractHighlighterPrivate *dd)
|
||||
: d_ptr(dd)
|
||||
{
|
||||
}
|
||||
|
||||
AbstractHighlighter::~AbstractHighlighter()
|
||||
{
|
||||
delete d_ptr;
|
||||
}
|
||||
|
||||
Definition AbstractHighlighter::definition() const
|
||||
{
|
||||
return d_ptr->m_definition;
|
||||
}
|
||||
|
||||
void AbstractHighlighter::setDefinition(const Definition &def)
|
||||
{
|
||||
Q_D(AbstractHighlighter);
|
||||
d->m_definition = def;
|
||||
}
|
||||
|
||||
Theme AbstractHighlighter::theme() const
|
||||
{
|
||||
Q_D(const AbstractHighlighter);
|
||||
return d->m_theme;
|
||||
}
|
||||
|
||||
void AbstractHighlighter::setTheme(const Theme &theme)
|
||||
{
|
||||
Q_D(AbstractHighlighter);
|
||||
d->m_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(QStringView text)
|
||||
{
|
||||
for (int i = 0; i < text.length(); ++i) {
|
||||
if (!text[i].isSpace()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return text.size();
|
||||
}
|
||||
|
||||
State AbstractHighlighter::highlightLine(QStringView text, const State &state)
|
||||
{
|
||||
Q_D(AbstractHighlighter);
|
||||
|
||||
// verify definition, deal with no highlighting being enabled
|
||||
d->ensureDefinitionLoaded();
|
||||
const auto defData = DefinitionData::get(d->m_definition);
|
||||
if (!d->m_definition.isValid() || !defData->isLoaded()) {
|
||||
applyFormat(0, text.size(), Format());
|
||||
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);
|
||||
bool isSharedData = true;
|
||||
if (Q_UNLIKELY(stateData && stateData->m_defId != defData->id)) {
|
||||
qCDebug(Log) << "Got invalid state, resetting.";
|
||||
stateData = nullptr;
|
||||
}
|
||||
if (Q_UNLIKELY(!stateData)) {
|
||||
stateData = StateData::reset(newState);
|
||||
stateData->push(defData->initialContext(), QStringList());
|
||||
stateData->m_defId = defData->id;
|
||||
isSharedData = false;
|
||||
}
|
||||
|
||||
// process empty lines
|
||||
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()) {
|
||||
/**
|
||||
* line empty context switches
|
||||
*/
|
||||
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;
|
||||
if (endlessLoopingCounter > 1024) {
|
||||
qCDebug(Log) << "Endless switch context transitions for line empty context, aborting highlighting of line.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto context = stateData->topContext();
|
||||
applyFormat(0, 0, context->attributeFormat());
|
||||
return *defData->unify.insert(newState);
|
||||
}
|
||||
|
||||
auto &dynamicRegexpCache = RepositoryPrivate::get(defData->repo)->m_dynamicRegexpCache;
|
||||
|
||||
int offset = 0;
|
||||
int beginOffset = 0;
|
||||
bool lineContinuation = false;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* stored as pointer to avoid deconstruction/constructions inside the internal loop
|
||||
* the pointers are stable, the formats are either in the contexts or rules
|
||||
*/
|
||||
auto currentFormat = &stateData->topContext()->attributeFormat();
|
||||
|
||||
/**
|
||||
* cached first non-space character, needs to be computed if < 0
|
||||
*/
|
||||
int firstNonSpace = -1;
|
||||
int lastOffset = offset;
|
||||
int endlessLoopingCounter = 0;
|
||||
do {
|
||||
/**
|
||||
* avoid that we loop endless for some broken hl definitions
|
||||
*/
|
||||
if (lastOffset == offset) {
|
||||
++endlessLoopingCounter;
|
||||
if (endlessLoopingCounter > 1024) {
|
||||
qCDebug(Log) << "Endless state transitions, aborting highlighting of line.";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// ensure we made progress, clear the endlessLoopingCounter
|
||||
Q_ASSERT(offset > lastOffset);
|
||||
lastOffset = offset;
|
||||
endlessLoopingCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* try to match all rules in the context in order of declaration in XML
|
||||
*/
|
||||
bool isLookAhead = false;
|
||||
int newOffset = 0;
|
||||
const Format *newFormat = nullptr;
|
||||
for (const auto &ruleShared : stateData->topContext()->rules()) {
|
||||
auto rule = ruleShared.get();
|
||||
/**
|
||||
* filter out rules that require a specific column
|
||||
*/
|
||||
if ((rule->requiredColumn() >= 0) && (rule->requiredColumn() != offset)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* filter out rules that only match for leading whitespace
|
||||
*/
|
||||
if (rule->firstNonSpace()) {
|
||||
/**
|
||||
* compute the first non-space lazy
|
||||
* avoids computing it for contexts without any such rules
|
||||
*/
|
||||
if (firstNonSpace < 0) {
|
||||
firstNonSpace = firstNonSpaceChar(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* can we skip?
|
||||
*/
|
||||
if (offset > firstNonSpace) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
insertSkipOffset(rule, newResult.skipOffset());
|
||||
|
||||
// remember new captures, if dynamic to enforce proper reset above on change!
|
||||
if (rule->isDynamic()) {
|
||||
capturesForLastDynamicSkipOffset = stateData->topCaptures();
|
||||
}
|
||||
}
|
||||
|
||||
if (newOffset <= offset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* apply folding.
|
||||
* special cases:
|
||||
* - rule with endRegion + beginRegion: in endRegion, the length is 0
|
||||
* - rule with lookAhead: length is 0
|
||||
*/
|
||||
if (rule->endRegion().isValid() && rule->beginRegion().isValid()) {
|
||||
applyFolding(offset, 0, rule->endRegion());
|
||||
} else if (rule->endRegion().isValid()) {
|
||||
applyFolding(offset, rule->isLookAhead() ? 0 : newOffset - offset, rule->endRegion());
|
||||
}
|
||||
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(), std::move(newResult.captures()), newState, isSharedData);
|
||||
isLookAhead = true;
|
||||
break;
|
||||
}
|
||||
|
||||
d->switchContext(stateData, rule->context(), std::move(newResult.captures()), newState, isSharedData);
|
||||
newFormat = rule->attributeFormat().isValid() ? &rule->attributeFormat() : &stateData->topContext()->attributeFormat();
|
||||
if (newOffset == text.size() && rule->isLineContinue()) {
|
||||
lineContinuation = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (isLookAhead) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newOffset <= offset) { // no matching rule
|
||||
if (stateData->topContext()->fallthrough()) {
|
||||
d->switchContext(stateData, stateData->topContext()->fallthroughContext(), QStringList(), newState, isSharedData);
|
||||
continue;
|
||||
}
|
||||
|
||||
newOffset = offset + 1;
|
||||
newFormat = &stateData->topContext()->attributeFormat();
|
||||
}
|
||||
|
||||
/**
|
||||
* if we arrive here, some new format has to be set!
|
||||
*/
|
||||
Q_ASSERT(newFormat);
|
||||
|
||||
/**
|
||||
* on format change, apply the last one and switch to new one
|
||||
*/
|
||||
if (newFormat != currentFormat && newFormat->id() != currentFormat->id()) {
|
||||
if (offset > 0) {
|
||||
applyFormat(beginOffset, offset - beginOffset, *currentFormat);
|
||||
}
|
||||
beginOffset = offset;
|
||||
currentFormat = newFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* we must have made progress if we arrive here!
|
||||
*/
|
||||
Q_ASSERT(newOffset > offset);
|
||||
offset = newOffset;
|
||||
|
||||
} while (offset < text.size());
|
||||
|
||||
/**
|
||||
* apply format for remaining text, if any
|
||||
*/
|
||||
if (beginOffset < offset) {
|
||||
applyFormat(beginOffset, text.size() - beginOffset, *currentFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* handle line end context switches
|
||||
* guard against endless loops
|
||||
* see https://phabricator.kde.org/D18509
|
||||
*/
|
||||
{
|
||||
int endlessLoopingCounter = 0;
|
||||
while (!stateData->topContext()->lineEndContext().isStay() && !lineContinuation) {
|
||||
if (!d->switchContext(stateData, stateData->topContext()->lineEndContext(), QStringList(), newState, isSharedData)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// guard against endless loops
|
||||
++endlessLoopingCounter;
|
||||
if (endlessLoopingCounter > 1024) {
|
||||
qCDebug(Log) << "Endless switch context transitions for line end context, aborting highlighting of line.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return *defData->unify.insert(newState);
|
||||
}
|
||||
|
||||
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(popCount);
|
||||
|
||||
// if we have a new context to add, push it
|
||||
// then we always "succeed"
|
||||
if (context) {
|
||||
data->push(context, std::move(captures));
|
||||
return true;
|
||||
}
|
||||
|
||||
// else we abort, if we did try to pop the initial context
|
||||
return initialContextSurvived;
|
||||
}
|
||||
|
||||
void AbstractHighlighter::applyFolding(int offset, int length, FoldingRegion region)
|
||||
{
|
||||
Q_UNUSED(offset);
|
||||
Q_UNUSED(length);
|
||||
Q_UNUSED(region);
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_ABSTRACTHIGHLIGHTERM_H
|
||||
#define KSYNTAXHIGHLIGHTING_ABSTRACTHIGHLIGHTERM_H
|
||||
|
||||
#include "definition.h"
|
||||
#include "ksyntaxhighlighting_export.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QStringView>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class AbstractHighlighterPrivate;
|
||||
class FoldingRegion;
|
||||
class Format;
|
||||
class State;
|
||||
class Theme;
|
||||
|
||||
/**
|
||||
* Abstract base class for highlighters.
|
||||
*
|
||||
* @section abshl_intro Introduction
|
||||
*
|
||||
* The AbstractHighlighter provides an interface to highlight text.
|
||||
*
|
||||
* The SyntaxHighlighting framework already ships with one implementation,
|
||||
* namely the SyntaxHighlighter, which also derives from QSyntaxHighlighter,
|
||||
* meaning that it can be used to highlight a QTextDocument or a QML TextEdit.
|
||||
* In order to use the SyntaxHighlighter, just call setDefinition() and
|
||||
* setTheme(), and the associated documents will automatically be highlighted.
|
||||
*
|
||||
* However, if you want to use the SyntaxHighlighting framework to implement
|
||||
* your own syntax highlighter, you need to sublcass from AbstractHighlighter.
|
||||
*
|
||||
* @section abshl_impl Implementing your own Syntax Highlighter
|
||||
*
|
||||
* In order to implement your own syntax highlighter, you need to inherit from
|
||||
* AbstractHighlighter. Then, pass each text line that needs to be highlighted
|
||||
* in order to highlightLine(). Internally, highlightLine() uses the Definition
|
||||
* initially set through setDefinition() and the State of the previous text line
|
||||
* to parse and highlight the given text line. For each visual highlighting
|
||||
* change, highlightLine() will call applyFormat(). Therefore, reimplement
|
||||
* applyFormat() to get notified of the Format that is valid in the range
|
||||
* starting at the given offset with the specified length. Similarly, for each
|
||||
* text part that starts or ends a code folding region, highlightLine() will
|
||||
* call applyFolding(). Therefore, if you are interested in code folding,
|
||||
* reimplement applyFolding() to get notified of the starting and ending code
|
||||
* folding regions, again specified in the range starting at the given offset
|
||||
* with the given length.
|
||||
*
|
||||
* The Format class itself depends on the current Theme. A theme must be
|
||||
* initially set once such that the Format%s instances can be queried for
|
||||
* concrete colors.
|
||||
*
|
||||
* Optionally, you can also reimplement setTheme() and setDefinition() to get
|
||||
* notified whenever the Definition or the Theme changes.
|
||||
*
|
||||
* @see SyntaxHighlighter
|
||||
* @since 5.28
|
||||
*/
|
||||
class KSYNTAXHIGHLIGHTING_EXPORT AbstractHighlighter
|
||||
{
|
||||
public:
|
||||
virtual ~AbstractHighlighter();
|
||||
|
||||
/**
|
||||
* Returns the syntax definition used for highlighting.
|
||||
*
|
||||
* @see setDefinition()
|
||||
*/
|
||||
Definition definition() const;
|
||||
|
||||
/**
|
||||
* Sets the syntax definition used for highlighting.
|
||||
*
|
||||
* Subclasses can re-implement this method to e.g. trigger
|
||||
* re-highlighting or clear internal data structures if needed.
|
||||
*/
|
||||
virtual void setDefinition(const Definition &def);
|
||||
|
||||
/**
|
||||
* Returns the currently selected theme for highlighting.
|
||||
*
|
||||
* @note If no Theme was set through setTheme(), the returned Theme will be
|
||||
* invalid, see Theme::isValid().
|
||||
*/
|
||||
Theme theme() const;
|
||||
|
||||
/**
|
||||
* Sets the theme used for highlighting.
|
||||
*
|
||||
* Subclasses can re-implement this method to e.g. trigger
|
||||
* re-highlighing or to do general palette color setup.
|
||||
*/
|
||||
virtual void setTheme(const Theme &theme);
|
||||
|
||||
protected:
|
||||
AbstractHighlighter();
|
||||
KSYNTAXHIGHLIGHTING_NO_EXPORT explicit AbstractHighlighter(AbstractHighlighterPrivate *dd);
|
||||
|
||||
/**
|
||||
* Highlight the given line. Call this from your derived class
|
||||
* where appropriate. This will result in any number of applyFormat()
|
||||
* and applyFolding() calls as a result.
|
||||
* @param text A string containing the text of the line to highlight.
|
||||
* @param state The highlighting state handle returned by the call
|
||||
* to highlightLine() for the previous line. For the very first line,
|
||||
* just pass a default constructed State().
|
||||
* @returns The state of the highlighting engine after processing the
|
||||
* given line. This needs to passed into highlightLine() for the
|
||||
* next line. You can store the state for efficient partial
|
||||
* re-highlighting for example during editing.
|
||||
*
|
||||
* @see applyFormat(), applyFolding()
|
||||
*/
|
||||
State highlightLine(QStringView text, const State &state);
|
||||
|
||||
/**
|
||||
* Reimplement this to apply formats to your output. The provided @p format
|
||||
* is valid for the interval [@p offset, @p offset + @p length).
|
||||
*
|
||||
* @param offset The start column of the interval for which @p format matches
|
||||
* @param length The length of the matching text
|
||||
* @param format The Format that applies to the range [offset, offset + length)
|
||||
*
|
||||
* @note Make sure to set a valid Definition, otherwise the parameter
|
||||
* @p format is invalid for the entire line passed to highlightLine()
|
||||
* (cf. Format::isValid()).
|
||||
*
|
||||
* @see applyFolding(), highlightLine()
|
||||
*/
|
||||
virtual void applyFormat(int offset, int length, const Format &format) = 0;
|
||||
|
||||
/**
|
||||
* Reimplement this to apply folding to your output. The provided
|
||||
* FoldingRegion @p region either stars or ends a code folding region in the
|
||||
* interval [@p offset, @p offset + @p length).
|
||||
*
|
||||
* @param offset The start column of the FoldingRegion
|
||||
* @param length The length of the matching text that starts / ends a
|
||||
* folding region
|
||||
* @param region The FoldingRegion that applies to the range [offset, offset + length)
|
||||
*
|
||||
* @note The FoldingRegion @p region is @e always either of type
|
||||
* FoldingRegion::Type::Begin or FoldingRegion::Type::End.
|
||||
*
|
||||
* @see applyFormat(), highlightLine(), FoldingRegion
|
||||
*/
|
||||
virtual void applyFolding(int offset, int length, FoldingRegion region);
|
||||
|
||||
protected:
|
||||
AbstractHighlighterPrivate *d_ptr;
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE(AbstractHighlighter)
|
||||
Q_DISABLE_COPY(AbstractHighlighter)
|
||||
};
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
Q_DECLARE_INTERFACE(KSyntaxHighlighting::AbstractHighlighter, "org.kde.SyntaxHighlighting.AbstractHighlighter")
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_ABSTRACTHIGHLIGHTERM_H
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_ABSTRACTHIGHLIGHTER_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_ABSTRACTHIGHLIGHTER_P_H
|
||||
|
||||
#include "definition.h"
|
||||
#include "theme.h"
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class ContextSwitch;
|
||||
class StateData;
|
||||
class State;
|
||||
|
||||
class AbstractHighlighterPrivate
|
||||
{
|
||||
public:
|
||||
AbstractHighlighterPrivate();
|
||||
virtual ~AbstractHighlighterPrivate();
|
||||
|
||||
void ensureDefinitionLoaded();
|
||||
bool switchContext(StateData *&data, const ContextSwitch &contextSwitch, QStringList &&captures, State &state, bool &isSharedData);
|
||||
|
||||
Definition m_definition;
|
||||
Theme m_theme;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_ANSIHIGHLIGHTER_H
|
||||
#define KSYNTAXHIGHLIGHTING_ANSIHIGHLIGHTER_H
|
||||
|
||||
#include "abstracthighlighter.h"
|
||||
#include "ksyntaxhighlighting_export.h"
|
||||
#include "theme.h"
|
||||
|
||||
#include <QFlags>
|
||||
#include <QString>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QIODevice;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class AnsiHighlighterPrivate;
|
||||
|
||||
// Exported for a bundled helper program
|
||||
class KSYNTAXHIGHLIGHTING_EXPORT AnsiHighlighter final : public AbstractHighlighter
|
||||
{
|
||||
public:
|
||||
enum class AnsiFormat {
|
||||
TrueColor,
|
||||
XTerm256Color,
|
||||
};
|
||||
|
||||
enum class Option {
|
||||
NoOptions,
|
||||
UseEditorBackground = 1 << 0,
|
||||
Unbuffered = 1 << 1,
|
||||
|
||||
// Options that displays a useful visual aid for syntax creation
|
||||
TraceFormat = 1 << 2,
|
||||
TraceRegion = 1 << 3,
|
||||
TraceContext = 1 << 4,
|
||||
TraceStackSize = 1 << 5,
|
||||
TraceAll = TraceFormat | TraceRegion | TraceContext | TraceStackSize,
|
||||
};
|
||||
Q_DECLARE_FLAGS(Options, Option)
|
||||
|
||||
AnsiHighlighter();
|
||||
~AnsiHighlighter() override;
|
||||
|
||||
void highlightFile(const QString &fileName, AnsiFormat format = AnsiFormat::TrueColor, Options options = Option::UseEditorBackground);
|
||||
void highlightData(QIODevice *device, AnsiFormat format = AnsiFormat::TrueColor, Options options = Option::UseEditorBackground);
|
||||
|
||||
void setOutputFile(const QString &fileName);
|
||||
void setOutputFile(FILE *fileHandle);
|
||||
|
||||
void setBackgroundRole(Theme::EditorColorRole bgRole);
|
||||
|
||||
protected:
|
||||
void applyFormat(int offset, int length, const Format &format) override;
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE(AnsiHighlighter)
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(KSyntaxHighlighting::AnsiHighlighter::Options)
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_ANSIHIGHLIGHTER_H
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "context_p.h"
|
||||
#include "definition_p.h"
|
||||
#include "format.h"
|
||||
#include "ksyntaxhighlighting_logging.h"
|
||||
#include "rule_p.h"
|
||||
#include "xml_p.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
Context::Context(const DefinitionData &def, const HighlightingContextData &data)
|
||||
: m_name(data.name)
|
||||
, m_attributeFormat(data.attribute.isEmpty() ? Format() : def.formatByName(data.attribute))
|
||||
, m_indentationBasedFolding(!data.noIndentationBasedFolding && def.indentationBasedFolding)
|
||||
{
|
||||
if (!data.attribute.isEmpty() && !m_attributeFormat.isValid()) {
|
||||
qCWarning(Log) << "Context: Unknown format" << data.attribute << "in context" << m_name << "of definition" << def.name;
|
||||
}
|
||||
}
|
||||
|
||||
bool Context::indentationBasedFoldingEnabled() const
|
||||
{
|
||||
return m_indentationBasedFolding;
|
||||
}
|
||||
|
||||
void Context::resolveContexts(DefinitionData &def, const HighlightingContextData &data)
|
||||
{
|
||||
m_lineEndContext.resolve(def, data.lineEndContext);
|
||||
m_lineEmptyContext.resolve(def, data.lineEmptyContext);
|
||||
m_fallthroughContext.resolve(def, data.fallthroughContext);
|
||||
m_stopEmptyLineContextSwitchLoop = data.stopEmptyLineContextSwitchLoop;
|
||||
|
||||
/**
|
||||
* line end context switches only when lineEmptyContext is #stay. This avoids
|
||||
* skipping empty lines after a line continuation character (see bug 405903)
|
||||
*/
|
||||
if (m_lineEmptyContext.isStay()) {
|
||||
m_lineEmptyContext = m_lineEndContext;
|
||||
}
|
||||
|
||||
m_rules.reserve(data.rules.size());
|
||||
for (const auto &ruleData : data.rules) {
|
||||
m_rules.push_back(Rule::create(def, ruleData, m_name));
|
||||
if (!m_rules.back()) {
|
||||
m_rules.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Context::resolveIncludes(DefinitionData &def)
|
||||
{
|
||||
if (m_resolveState == Resolved) {
|
||||
return;
|
||||
}
|
||||
if (m_resolveState == Resolving) {
|
||||
qCWarning(Log) << "Cyclic dependency!";
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(m_resolveState == Unresolved);
|
||||
m_resolveState = Resolving; // cycle guard
|
||||
|
||||
for (auto it = m_rules.begin(); it != m_rules.end();) {
|
||||
const IncludeRules *includeRules = it->get()->castToIncludeRules();
|
||||
if (!includeRules) {
|
||||
m_hasDynamicRule = m_hasDynamicRule || it->get()->isDynamic();
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
const QStringView includeContext = includeRules->contextName();
|
||||
const qsizetype idx = includeContext.indexOf(QLatin1String("##"));
|
||||
|
||||
Context *context = nullptr;
|
||||
DefinitionData *defData = &def;
|
||||
|
||||
if (idx <= -1) { // local include
|
||||
context = def.contextByName(includeContext);
|
||||
} else {
|
||||
const auto definitionName = includeContext.sliced(idx + 2);
|
||||
const auto contextName = includeContext.sliced(0, idx);
|
||||
auto resolvedContext = def.resolveIncludedContext(definitionName, contextName);
|
||||
defData = resolvedContext.def;
|
||||
context = resolvedContext.context;
|
||||
if (!defData) {
|
||||
qCWarning(Log) << "Unable to resolve external include rule for definition" << definitionName << "in" << def.name;
|
||||
}
|
||||
}
|
||||
|
||||
if (!context) {
|
||||
qCWarning(Log) << "Unable to resolve include rule for definition" << includeContext << "in" << def.name;
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (context == this) {
|
||||
qCWarning(Log) << "Unable to resolve self include rule for definition" << includeContext << "in" << def.name;
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (context->m_resolveState != Resolved) {
|
||||
context->resolveIncludes(*defData);
|
||||
}
|
||||
|
||||
m_hasDynamicRule = m_hasDynamicRule || context->m_hasDynamicRule;
|
||||
|
||||
/**
|
||||
* handle included attribute
|
||||
* transitive closure: we might include attributes included from somewhere else
|
||||
*/
|
||||
if (includeRules->includeAttribute()) {
|
||||
m_attributeFormat = context->m_attributeFormat;
|
||||
}
|
||||
|
||||
it = m_rules.erase(it);
|
||||
it = m_rules.insert(it, context->rules().begin(), context->rules().end());
|
||||
it += context->rules().size();
|
||||
}
|
||||
|
||||
m_resolveState = Resolved;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_CONTEXT_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_CONTEXT_P_H
|
||||
|
||||
#include "contextswitch_p.h"
|
||||
#include "format.h"
|
||||
#include "highlightingdata_p.hpp"
|
||||
#include "rule_p.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class DefinitionData;
|
||||
|
||||
class Context
|
||||
{
|
||||
public:
|
||||
Q_DISABLE_COPY(Context)
|
||||
|
||||
Context(Context &&) = default;
|
||||
Context &operator=(Context &&) = default;
|
||||
|
||||
Context(const DefinitionData &def, const HighlightingContextData &data);
|
||||
~Context() = default;
|
||||
|
||||
const QString &name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
const ContextSwitch &lineEndContext() const
|
||||
{
|
||||
return m_lineEndContext;
|
||||
}
|
||||
|
||||
const ContextSwitch &lineEmptyContext() const
|
||||
{
|
||||
return m_lineEmptyContext;
|
||||
}
|
||||
|
||||
bool fallthrough() const
|
||||
{
|
||||
return !m_fallthroughContext.isStay();
|
||||
}
|
||||
|
||||
bool hasDynamicRule() const
|
||||
{
|
||||
return m_hasDynamicRule;
|
||||
}
|
||||
|
||||
bool stopEmptyLineContextSwitchLoop() const
|
||||
{
|
||||
return m_stopEmptyLineContextSwitchLoop;
|
||||
}
|
||||
|
||||
const ContextSwitch &fallthroughContext() const
|
||||
{
|
||||
return m_fallthroughContext;
|
||||
}
|
||||
|
||||
const Format &attributeFormat() const
|
||||
{
|
||||
return m_attributeFormat;
|
||||
}
|
||||
|
||||
const std::vector<Rule::Ptr> &rules() const
|
||||
{
|
||||
return m_rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns @c true, when indentationBasedFolding is enabled for the
|
||||
* associated Definition and when "noIndentationBasedFolding" is NOT set.
|
||||
*/
|
||||
bool indentationBasedFoldingEnabled() const;
|
||||
|
||||
void resolveContexts(DefinitionData &def, const HighlightingContextData &data);
|
||||
void resolveIncludes(DefinitionData &def);
|
||||
|
||||
private:
|
||||
enum ResolveState : quint8 { Unresolved, Resolving, Resolved };
|
||||
|
||||
std::vector<Rule::Ptr> m_rules;
|
||||
|
||||
QString m_name;
|
||||
|
||||
ContextSwitch m_lineEndContext;
|
||||
ContextSwitch m_lineEmptyContext;
|
||||
ContextSwitch m_fallthroughContext;
|
||||
|
||||
/**
|
||||
* resolved format for our attribute, done in constructor and resolveIncludes
|
||||
*/
|
||||
Format m_attributeFormat;
|
||||
|
||||
ResolveState m_resolveState = Unresolved;
|
||||
bool m_hasDynamicRule = false;
|
||||
bool m_stopEmptyLineContextSwitchLoop = true;
|
||||
bool m_indentationBasedFolding;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_CONTEXT_P_H
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2024 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "contextswitch_p.h"
|
||||
#include "definition_p.h"
|
||||
#include "ksyntaxhighlighting_logging.h"
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
void ContextSwitch::resolve(DefinitionData &def, QStringView context)
|
||||
{
|
||||
if (context.isEmpty() || context == QStringLiteral("#stay")) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (context.startsWith(QStringLiteral("#pop"))) {
|
||||
++m_popCount;
|
||||
if (context.size() > 4 && context.at(4) == QLatin1Char('!')) {
|
||||
context = context.sliced(5);
|
||||
break;
|
||||
}
|
||||
context = context.sliced(4);
|
||||
}
|
||||
|
||||
m_isStay = !m_popCount;
|
||||
|
||||
if (context.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const qsizetype defNameIndex = context.indexOf(QStringLiteral("##"));
|
||||
auto defName = (defNameIndex <= -1) ? QStringView() : context.sliced(defNameIndex + 2);
|
||||
auto contextName = (defNameIndex <= -1) ? context : context.sliced(0, defNameIndex);
|
||||
|
||||
m_context = def.resolveIncludedContext(defName, contextName).context;
|
||||
|
||||
if (m_context) {
|
||||
m_isStay = false;
|
||||
} else {
|
||||
qCWarning(Log) << "cannot find context" << contextName << "in" << def.name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_CONTEXTSWITCH_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_CONTEXTSWITCH_P_H
|
||||
|
||||
#include <QStringView>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class Context;
|
||||
class DefinitionData;
|
||||
|
||||
class ContextSwitch
|
||||
{
|
||||
public:
|
||||
ContextSwitch() = default;
|
||||
~ContextSwitch() = default;
|
||||
|
||||
bool isStay() const
|
||||
{
|
||||
return m_isStay;
|
||||
}
|
||||
|
||||
int popCount() const
|
||||
{
|
||||
return m_popCount;
|
||||
}
|
||||
|
||||
Context *context() const
|
||||
{
|
||||
return m_context;
|
||||
}
|
||||
|
||||
void resolve(DefinitionData &def, QStringView context);
|
||||
|
||||
private:
|
||||
Context *m_context = nullptr;
|
||||
int m_popCount = 0;
|
||||
bool m_isStay = true;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_CONTEXTSWITCH_P_H
|
||||
@@ -0,0 +1,878 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2018 Dominik Haumann <dhaumann@kde.org>
|
||||
SPDX-FileCopyrightText: 2018 Christoph Cullmann <cullmann@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "definition.h"
|
||||
#include "definition_p.h"
|
||||
|
||||
#include "context_p.h"
|
||||
#include "format.h"
|
||||
#include "format_p.h"
|
||||
#include "highlightingdata_p.hpp"
|
||||
#include "ksyntaxhighlighting_logging.h"
|
||||
#include "ksyntaxhighlighting_version.h"
|
||||
#include "repository.h"
|
||||
#include "repository_p.h"
|
||||
#include "rule_p.h"
|
||||
#include "worddelimiters_p.h"
|
||||
#include "xml_p.h"
|
||||
|
||||
#include <QCborMap>
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QStringTokenizer>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
DefinitionData::DefinitionData()
|
||||
: wordDelimiters()
|
||||
, wordWrapDelimiters(wordDelimiters)
|
||||
{
|
||||
}
|
||||
|
||||
DefinitionData::~DefinitionData() = default;
|
||||
|
||||
Definition::Definition()
|
||||
: d(std::make_shared<DefinitionData>())
|
||||
{
|
||||
d->q = d;
|
||||
}
|
||||
|
||||
Definition::Definition(Definition &&other) noexcept = default;
|
||||
Definition::Definition(const Definition &) = default;
|
||||
Definition::~Definition() = default;
|
||||
Definition &Definition::operator=(Definition &&other) noexcept = default;
|
||||
Definition &Definition::operator=(const Definition &) = default;
|
||||
|
||||
Definition::Definition(const DefinitionData &defData)
|
||||
: d(defData.q.lock())
|
||||
{
|
||||
}
|
||||
|
||||
bool Definition::operator==(const Definition &other) const
|
||||
{
|
||||
return d->fileName == other.d->fileName;
|
||||
}
|
||||
|
||||
bool Definition::operator!=(const Definition &other) const
|
||||
{
|
||||
return d->fileName != other.d->fileName;
|
||||
}
|
||||
|
||||
bool Definition::isValid() const
|
||||
{
|
||||
return d->repo && !d->fileName.isEmpty() && !d->name.isEmpty();
|
||||
}
|
||||
|
||||
QString Definition::filePath() const
|
||||
{
|
||||
return d->fileName;
|
||||
}
|
||||
|
||||
QString Definition::name() const
|
||||
{
|
||||
return d->name;
|
||||
}
|
||||
|
||||
QStringList Definition::alternativeNames() const
|
||||
{
|
||||
return d->alternativeNames;
|
||||
}
|
||||
|
||||
QString Definition::translatedName() const
|
||||
{
|
||||
if (d->translatedName.isEmpty()) {
|
||||
d->translatedName = QCoreApplication::instance()->translate("Language", d->nameUtf8.isEmpty() ? d->name.toUtf8().constData() : d->nameUtf8.constData());
|
||||
}
|
||||
return d->translatedName;
|
||||
}
|
||||
|
||||
QString Definition::section() const
|
||||
{
|
||||
return d->section;
|
||||
}
|
||||
|
||||
QString Definition::translatedSection() const
|
||||
{
|
||||
if (d->translatedSection.isEmpty()) {
|
||||
d->translatedSection = QCoreApplication::instance()->translate("Language Section",
|
||||
d->sectionUtf8.isEmpty() ? d->section.toUtf8().constData() : d->sectionUtf8.constData());
|
||||
}
|
||||
return d->translatedSection;
|
||||
}
|
||||
|
||||
QList<QString> Definition::mimeTypes() const
|
||||
{
|
||||
return d->mimetypes;
|
||||
}
|
||||
|
||||
QList<QString> Definition::extensions() const
|
||||
{
|
||||
return d->extensions;
|
||||
}
|
||||
|
||||
int Definition::version() const
|
||||
{
|
||||
return d->version;
|
||||
}
|
||||
|
||||
int Definition::priority() const
|
||||
{
|
||||
return d->priority;
|
||||
}
|
||||
|
||||
bool Definition::isHidden() const
|
||||
{
|
||||
return d->hidden;
|
||||
}
|
||||
|
||||
QString Definition::style() const
|
||||
{
|
||||
return d->style;
|
||||
}
|
||||
|
||||
QString Definition::indenter() const
|
||||
{
|
||||
return d->indenter;
|
||||
}
|
||||
|
||||
QString Definition::author() const
|
||||
{
|
||||
return d->author;
|
||||
}
|
||||
|
||||
QString Definition::license() const
|
||||
{
|
||||
return d->license;
|
||||
}
|
||||
|
||||
bool Definition::isWordDelimiter(QChar c) const
|
||||
{
|
||||
d->load();
|
||||
return d->wordDelimiters.contains(c);
|
||||
}
|
||||
|
||||
bool Definition::isWordWrapDelimiter(QChar c) const
|
||||
{
|
||||
d->load();
|
||||
return d->wordWrapDelimiters.contains(c);
|
||||
}
|
||||
|
||||
bool Definition::foldingEnabled() const
|
||||
{
|
||||
if (d->foldingRegionsState == DefinitionData::FoldingRegionsState::Undetermined) {
|
||||
d->load();
|
||||
}
|
||||
|
||||
return d->foldingRegionsState == DefinitionData::FoldingRegionsState::ContainsFoldingRegions || d->indentationBasedFolding;
|
||||
}
|
||||
|
||||
bool Definition::indentationBasedFoldingEnabled() const
|
||||
{
|
||||
d->load();
|
||||
return d->indentationBasedFolding;
|
||||
}
|
||||
|
||||
QStringList Definition::foldingIgnoreList() const
|
||||
{
|
||||
d->load();
|
||||
return d->foldingIgnoreList;
|
||||
}
|
||||
|
||||
QStringList Definition::keywordLists() const
|
||||
{
|
||||
d->load(DefinitionData::OnlyKeywords(true));
|
||||
return d->keywordLists.keys();
|
||||
}
|
||||
|
||||
QStringList Definition::keywordList(const QString &name) const
|
||||
{
|
||||
d->load(DefinitionData::OnlyKeywords(true));
|
||||
const auto list = d->keywordList(name);
|
||||
return list ? list->keywords() : QStringList();
|
||||
}
|
||||
|
||||
bool Definition::setKeywordList(const QString &name, const QStringList &content)
|
||||
{
|
||||
d->load(DefinitionData::OnlyKeywords(true));
|
||||
KeywordList *list = d->keywordList(name);
|
||||
if (list) {
|
||||
list->setKeywordList(content);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QList<Format> Definition::formats() const
|
||||
{
|
||||
d->load();
|
||||
|
||||
// sort formats so that the order matches the order of the itemDatas in the xml files.
|
||||
auto formatList = d->formats.values();
|
||||
std::sort(formatList.begin(), formatList.end(), [](const KSyntaxHighlighting::Format &lhs, const KSyntaxHighlighting::Format &rhs) {
|
||||
return lhs.id() < rhs.id();
|
||||
});
|
||||
|
||||
return formatList;
|
||||
}
|
||||
|
||||
QList<Definition> Definition::includedDefinitions() const
|
||||
{
|
||||
QList<Definition> definitions;
|
||||
|
||||
if (isValid()) {
|
||||
d->load();
|
||||
|
||||
// init worklist and result used as guard with this definition
|
||||
QVarLengthArray<const DefinitionData *, 4> queue{d.get()};
|
||||
definitions.push_back(*this);
|
||||
while (!queue.empty()) {
|
||||
const auto *def = queue.back();
|
||||
queue.pop_back();
|
||||
for (const auto *defData : def->immediateIncludedDefinitions) {
|
||||
auto pred = [defData](const Definition &def) {
|
||||
return DefinitionData::get(def) == defData;
|
||||
};
|
||||
if (std::find_if(definitions.begin(), definitions.end(), pred) == definitions.end()) {
|
||||
definitions.push_back(Definition(*defData));
|
||||
queue.push_back(defData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove the 1st entry, since it is this Definition
|
||||
definitions.front() = std::move(definitions.back());
|
||||
definitions.pop_back();
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
QString Definition::singleLineCommentMarker() const
|
||||
{
|
||||
d->load();
|
||||
return d->singleLineCommentMarker;
|
||||
}
|
||||
|
||||
CommentPosition Definition::singleLineCommentPosition() const
|
||||
{
|
||||
d->load();
|
||||
return d->singleLineCommentPosition;
|
||||
}
|
||||
|
||||
QPair<QString, QString> Definition::multiLineCommentMarker() const
|
||||
{
|
||||
d->load();
|
||||
return {d->multiLineCommentStartMarker, d->multiLineCommentEndMarker};
|
||||
}
|
||||
|
||||
QList<QPair<QChar, QString>> Definition::characterEncodings() const
|
||||
{
|
||||
d->load();
|
||||
return d->characterEncodings;
|
||||
}
|
||||
|
||||
Context *DefinitionData::initialContext()
|
||||
{
|
||||
Q_ASSERT(!contexts.empty());
|
||||
return &contexts.front();
|
||||
}
|
||||
|
||||
Context *DefinitionData::contextByName(QStringView wantedName)
|
||||
{
|
||||
for (auto &context : contexts) {
|
||||
if (context.name() == wantedName) {
|
||||
return &context;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KeywordList *DefinitionData::keywordList(const QString &wantedName)
|
||||
{
|
||||
auto it = keywordLists.find(wantedName);
|
||||
return (it == keywordLists.end()) ? nullptr : &it.value();
|
||||
}
|
||||
|
||||
Format DefinitionData::formatByName(QStringView wantedName) const
|
||||
{
|
||||
const auto it = formats.constFind(wantedName);
|
||||
if (it != formats.constEnd()) {
|
||||
return it.value();
|
||||
}
|
||||
|
||||
return Format();
|
||||
}
|
||||
|
||||
bool DefinitionData::isLoaded() const
|
||||
{
|
||||
return !contexts.empty();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
std::atomic<uint64_t> definitionId{1};
|
||||
}
|
||||
|
||||
bool DefinitionData::load(OnlyKeywords onlyKeywords)
|
||||
{
|
||||
if (isLoaded()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fileName.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bool(onlyKeywords) && keywordIsLoaded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QFile file(fileName);
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamReader reader(&file);
|
||||
while (!reader.atEnd()) {
|
||||
const auto token = reader.readNext();
|
||||
if (token != QXmlStreamReader::StartElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (reader.name() == QLatin1String("highlighting")) {
|
||||
loadHighlighting(reader, onlyKeywords);
|
||||
if (bool(onlyKeywords)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
else if (reader.name() == QLatin1String("general")) {
|
||||
loadGeneral(reader);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &kw : keywordLists) {
|
||||
kw.setCaseSensitivity(caseSensitive);
|
||||
}
|
||||
|
||||
resolveContexts();
|
||||
|
||||
id = definitionId.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DefinitionData::clear()
|
||||
{
|
||||
// keep only name and repo, so we can re-lookup to make references persist over repo reloads
|
||||
// see AbstractHighlighterPrivate::ensureDefinitionLoaded()
|
||||
id = 0;
|
||||
keywordLists.clear();
|
||||
contexts.clear();
|
||||
formats.clear();
|
||||
contextDatas.clear();
|
||||
immediateIncludedDefinitions.clear();
|
||||
wordDelimiters = WordDelimiters();
|
||||
wordWrapDelimiters = wordDelimiters;
|
||||
keywordIsLoaded = false;
|
||||
foldingRegionsState = FoldingRegionsState::Undetermined;
|
||||
indentationBasedFolding = false;
|
||||
foldingIgnoreList.clear();
|
||||
singleLineCommentMarker.clear();
|
||||
singleLineCommentPosition = CommentPosition::StartOfLine;
|
||||
multiLineCommentStartMarker.clear();
|
||||
multiLineCommentEndMarker.clear();
|
||||
characterEncodings.clear();
|
||||
|
||||
fileName.clear();
|
||||
nameUtf8.clear();
|
||||
translatedName.clear();
|
||||
section.clear();
|
||||
sectionUtf8.clear();
|
||||
translatedSection.clear();
|
||||
style.clear();
|
||||
indenter.clear();
|
||||
author.clear();
|
||||
license.clear();
|
||||
mimetypes.clear();
|
||||
extensions.clear();
|
||||
caseSensitive = Qt::CaseSensitive;
|
||||
version = 0.0f;
|
||||
priority = 0;
|
||||
hidden = false;
|
||||
|
||||
// purge our cache that is used to unify states
|
||||
unify.clear();
|
||||
}
|
||||
|
||||
bool DefinitionData::loadMetaData(const QString &definitionFileName)
|
||||
{
|
||||
fileName = definitionFileName;
|
||||
|
||||
QFile file(definitionFileName);
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QXmlStreamReader reader(&file);
|
||||
while (!reader.atEnd()) {
|
||||
const auto token = reader.readNext();
|
||||
if (token != QXmlStreamReader::StartElement) {
|
||||
continue;
|
||||
}
|
||||
if (reader.name() == QLatin1String("language")) {
|
||||
return loadLanguage(reader);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DefinitionData::loadMetaData(const QString &file, const QCborMap &obj)
|
||||
{
|
||||
name = obj.value(QLatin1String("name")).toString();
|
||||
if (name.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
nameUtf8 = obj.value(QLatin1String("name")).toByteArray();
|
||||
section = obj.value(QLatin1String("section")).toString();
|
||||
sectionUtf8 = obj.value(QLatin1String("section")).toByteArray();
|
||||
version = obj.value(QLatin1String("version")).toInteger();
|
||||
priority = obj.value(QLatin1String("priority")).toInteger();
|
||||
style = obj.value(QLatin1String("style")).toString();
|
||||
author = obj.value(QLatin1String("author")).toString();
|
||||
license = obj.value(QLatin1String("license")).toString();
|
||||
indenter = obj.value(QLatin1String("indenter")).toString();
|
||||
hidden = obj.value(QLatin1String("hidden")).toBool();
|
||||
fileName = file;
|
||||
|
||||
const auto names = obj.value(QLatin1String("alternativeNames")).toString();
|
||||
alternativeNames = names.split(QLatin1Char(';'), Qt::SkipEmptyParts);
|
||||
|
||||
const auto exts = obj.value(QLatin1String("extensions")).toString();
|
||||
extensions = exts.split(QLatin1Char(';'), Qt::SkipEmptyParts);
|
||||
|
||||
const auto mts = obj.value(QLatin1String("mimetype")).toString();
|
||||
mimetypes = mts.split(QLatin1Char(';'), Qt::SkipEmptyParts);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DefinitionData::loadLanguage(QXmlStreamReader &reader)
|
||||
{
|
||||
Q_ASSERT(reader.name() == QLatin1String("language"));
|
||||
Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
|
||||
|
||||
if (!checkKateVersion(reader.attributes().value(QLatin1String("kateversion")))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
name = reader.attributes().value(QLatin1String("name")).toString();
|
||||
if (name.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const auto names = reader.attributes().value(QLatin1String("alternativeNames"));
|
||||
for (const auto &n : QStringTokenizer(names, u';', Qt::SkipEmptyParts)) {
|
||||
alternativeNames.push_back(n.toString());
|
||||
}
|
||||
section = reader.attributes().value(QLatin1String("section")).toString();
|
||||
// toFloat instead of toInt for backward compatibility with old Kate files
|
||||
version = reader.attributes().value(QLatin1String("version")).toFloat();
|
||||
priority = reader.attributes().value(QLatin1String("priority")).toInt();
|
||||
hidden = Xml::attrToBool(reader.attributes().value(QLatin1String("hidden")));
|
||||
style = reader.attributes().value(QLatin1String("style")).toString();
|
||||
indenter = reader.attributes().value(QLatin1String("indenter")).toString();
|
||||
author = reader.attributes().value(QLatin1String("author")).toString();
|
||||
license = reader.attributes().value(QLatin1String("license")).toString();
|
||||
const auto exts = reader.attributes().value(QLatin1String("extensions"));
|
||||
for (const auto &ext : QStringTokenizer(exts, u';', Qt::SkipEmptyParts)) {
|
||||
extensions.push_back(ext.toString());
|
||||
}
|
||||
const auto mts = reader.attributes().value(QLatin1String("mimetype"));
|
||||
for (const auto &mt : QStringTokenizer(mts, u';', Qt::SkipEmptyParts)) {
|
||||
mimetypes.push_back(mt.toString());
|
||||
}
|
||||
if (reader.attributes().hasAttribute(QLatin1String("casesensitive"))) {
|
||||
caseSensitive = Xml::attrToBool(reader.attributes().value(QLatin1String("casesensitive"))) ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DefinitionData::loadHighlighting(QXmlStreamReader &reader, OnlyKeywords onlyKeywords)
|
||||
{
|
||||
Q_ASSERT(reader.name() == QLatin1String("highlighting"));
|
||||
Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
|
||||
|
||||
// skip highlighting
|
||||
reader.readNext();
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
switch (reader.tokenType()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
if (reader.name() == QLatin1String("list")) {
|
||||
if (!keywordIsLoaded) {
|
||||
KeywordList keywords;
|
||||
keywords.load(reader);
|
||||
keywordLists.insert(keywords.name(), keywords);
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
reader.readNext(); // Skip </list>
|
||||
}
|
||||
} else if (bool(onlyKeywords)) {
|
||||
resolveIncludeKeywords();
|
||||
return;
|
||||
} else if (reader.name() == QLatin1String("contexts")) {
|
||||
resolveIncludeKeywords();
|
||||
loadContexts(reader);
|
||||
reader.readNext();
|
||||
} else if (reader.name() == QLatin1String("itemDatas")) {
|
||||
loadItemData(reader);
|
||||
} else {
|
||||
reader.readNext();
|
||||
}
|
||||
break;
|
||||
case QXmlStreamReader::EndElement:
|
||||
return;
|
||||
default:
|
||||
reader.readNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DefinitionData::resolveIncludeKeywords()
|
||||
{
|
||||
if (keywordIsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
keywordIsLoaded = true;
|
||||
|
||||
for (auto &kw : keywordLists) {
|
||||
kw.resolveIncludeKeywords(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void DefinitionData::loadContexts(QXmlStreamReader &reader)
|
||||
{
|
||||
Q_ASSERT(reader.name() == QLatin1String("contexts"));
|
||||
Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
|
||||
|
||||
contextDatas.reserve(32);
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
switch (reader.tokenType()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
if (reader.name() == QLatin1String("context")) {
|
||||
contextDatas.emplace_back().load(name, reader);
|
||||
}
|
||||
reader.readNext();
|
||||
break;
|
||||
case QXmlStreamReader::EndElement:
|
||||
return;
|
||||
default:
|
||||
reader.readNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DefinitionData::resolveContexts()
|
||||
{
|
||||
contexts.reserve(contextDatas.size());
|
||||
|
||||
/**
|
||||
* Transform all HighlightingContextData to Context.
|
||||
* This is necessary so that Context::resolveContexts() can find the referenced contexts.
|
||||
*/
|
||||
for (const auto &contextData : std::as_const(contextDatas)) {
|
||||
contexts.emplace_back(*this, contextData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves contexts and rules.
|
||||
*/
|
||||
auto ctxIt = contexts.begin();
|
||||
for (const auto &contextData : std::as_const(contextDatas)) {
|
||||
ctxIt->resolveContexts(*this, contextData);
|
||||
++ctxIt;
|
||||
}
|
||||
|
||||
/**
|
||||
* To free the memory, constDatas is emptied because it is no longer used.
|
||||
*/
|
||||
contextDatas.clear();
|
||||
contextDatas.shrink_to_fit();
|
||||
|
||||
/**
|
||||
* Resolved includeRules.
|
||||
*/
|
||||
for (auto &context : contexts) {
|
||||
context.resolveIncludes(*this);
|
||||
}
|
||||
|
||||
// when a context includes a folding region this value is Yes, otherwise it remains undetermined
|
||||
if (foldingRegionsState == FoldingRegionsState::Undetermined) {
|
||||
foldingRegionsState = FoldingRegionsState::NoFoldingRegions;
|
||||
}
|
||||
}
|
||||
|
||||
void DefinitionData::loadItemData(QXmlStreamReader &reader)
|
||||
{
|
||||
Q_ASSERT(reader.name() == QLatin1String("itemDatas"));
|
||||
Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
switch (reader.tokenType()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
if (reader.name() == QLatin1String("itemData")) {
|
||||
Format f;
|
||||
auto formatData = FormatPrivate::detachAndGet(f);
|
||||
formatData->definitionName = name;
|
||||
formatData->load(reader);
|
||||
formatData->id = RepositoryPrivate::get(repo)->nextFormatId();
|
||||
formats.insert(f.name(), f);
|
||||
reader.readNext();
|
||||
}
|
||||
reader.readNext();
|
||||
break;
|
||||
case QXmlStreamReader::EndElement:
|
||||
return;
|
||||
default:
|
||||
reader.readNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DefinitionData::loadGeneral(QXmlStreamReader &reader)
|
||||
{
|
||||
Q_ASSERT(reader.name() == QLatin1String("general"));
|
||||
Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
|
||||
reader.readNext();
|
||||
|
||||
// reference counter to count XML child elements, to not return too early
|
||||
int elementRefCounter = 1;
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
switch (reader.tokenType()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
++elementRefCounter;
|
||||
|
||||
if (reader.name() == QLatin1String("keywords")) {
|
||||
if (reader.attributes().hasAttribute(QLatin1String("casesensitive"))) {
|
||||
caseSensitive = Xml::attrToBool(reader.attributes().value(QLatin1String("casesensitive"))) ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
||||
}
|
||||
|
||||
// adapt wordDelimiters
|
||||
wordDelimiters.append(reader.attributes().value(QLatin1String("additionalDeliminator")));
|
||||
wordDelimiters.remove(reader.attributes().value(QLatin1String("weakDeliminator")));
|
||||
|
||||
// adapt WordWrapDelimiters
|
||||
auto wordWrapDeliminatorAttr = reader.attributes().value(QLatin1String("wordWrapDeliminator"));
|
||||
if (wordWrapDeliminatorAttr.isEmpty()) {
|
||||
wordWrapDelimiters = wordDelimiters;
|
||||
} else {
|
||||
wordWrapDelimiters.append(wordWrapDeliminatorAttr);
|
||||
}
|
||||
} else if (reader.name() == QLatin1String("folding")) {
|
||||
if (reader.attributes().hasAttribute(QLatin1String("indentationsensitive"))) {
|
||||
indentationBasedFolding = Xml::attrToBool(reader.attributes().value(QLatin1String("indentationsensitive")));
|
||||
}
|
||||
} else if (reader.name() == QLatin1String("emptyLines")) {
|
||||
loadFoldingIgnoreList(reader);
|
||||
} else if (reader.name() == QLatin1String("comments")) {
|
||||
loadComments(reader);
|
||||
} else if (reader.name() == QLatin1String("spellchecking")) {
|
||||
loadSpellchecking(reader);
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
reader.readNext();
|
||||
break;
|
||||
case QXmlStreamReader::EndElement:
|
||||
--elementRefCounter;
|
||||
if (elementRefCounter == 0) {
|
||||
return;
|
||||
}
|
||||
reader.readNext();
|
||||
break;
|
||||
default:
|
||||
reader.readNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DefinitionData::loadComments(QXmlStreamReader &reader)
|
||||
{
|
||||
Q_ASSERT(reader.name() == QLatin1String("comments"));
|
||||
Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
|
||||
reader.readNext();
|
||||
|
||||
// reference counter to count XML child elements, to not return too early
|
||||
int elementRefCounter = 1;
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
switch (reader.tokenType()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
++elementRefCounter;
|
||||
if (reader.name() == QLatin1String("comment")) {
|
||||
const bool isSingleLine = reader.attributes().value(QLatin1String("name")) == QLatin1String("singleLine");
|
||||
if (isSingleLine) {
|
||||
singleLineCommentMarker = reader.attributes().value(QLatin1String("start")).toString();
|
||||
const bool afterWhiteSpace = reader.attributes().value(QLatin1String("position")) == QLatin1String("afterwhitespace");
|
||||
singleLineCommentPosition = afterWhiteSpace ? CommentPosition::AfterWhitespace : CommentPosition::StartOfLine;
|
||||
} else {
|
||||
multiLineCommentStartMarker = reader.attributes().value(QLatin1String("start")).toString();
|
||||
multiLineCommentEndMarker = reader.attributes().value(QLatin1String("end")).toString();
|
||||
}
|
||||
}
|
||||
reader.readNext();
|
||||
break;
|
||||
case QXmlStreamReader::EndElement:
|
||||
--elementRefCounter;
|
||||
if (elementRefCounter == 0) {
|
||||
return;
|
||||
}
|
||||
reader.readNext();
|
||||
break;
|
||||
default:
|
||||
reader.readNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DefinitionData::loadFoldingIgnoreList(QXmlStreamReader &reader)
|
||||
{
|
||||
Q_ASSERT(reader.name() == QLatin1String("emptyLines"));
|
||||
Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
|
||||
reader.readNext();
|
||||
|
||||
// reference counter to count XML child elements, to not return too early
|
||||
int elementRefCounter = 1;
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
switch (reader.tokenType()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
++elementRefCounter;
|
||||
if (reader.name() == QLatin1String("emptyLine")) {
|
||||
foldingIgnoreList << reader.attributes().value(QLatin1String("regexpr")).toString();
|
||||
}
|
||||
reader.readNext();
|
||||
break;
|
||||
case QXmlStreamReader::EndElement:
|
||||
--elementRefCounter;
|
||||
if (elementRefCounter == 0) {
|
||||
return;
|
||||
}
|
||||
reader.readNext();
|
||||
break;
|
||||
default:
|
||||
reader.readNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DefinitionData::loadSpellchecking(QXmlStreamReader &reader)
|
||||
{
|
||||
Q_ASSERT(reader.name() == QLatin1String("spellchecking"));
|
||||
Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
|
||||
reader.readNext();
|
||||
|
||||
// reference counter to count XML child elements, to not return too early
|
||||
int elementRefCounter = 1;
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
switch (reader.tokenType()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
++elementRefCounter;
|
||||
if (reader.name() == QLatin1String("encoding")) {
|
||||
const auto charRef = reader.attributes().value(QLatin1String("char"));
|
||||
if (!charRef.isEmpty()) {
|
||||
const auto str = reader.attributes().value(QLatin1String("string"));
|
||||
characterEncodings.push_back({charRef[0], str.toString()});
|
||||
}
|
||||
}
|
||||
reader.readNext();
|
||||
break;
|
||||
case QXmlStreamReader::EndElement:
|
||||
--elementRefCounter;
|
||||
if (elementRefCounter == 0) {
|
||||
return;
|
||||
}
|
||||
reader.readNext();
|
||||
break;
|
||||
default:
|
||||
reader.readNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DefinitionData::checkKateVersion(QStringView verStr)
|
||||
{
|
||||
const auto idx = verStr.indexOf(QLatin1Char('.'));
|
||||
if (idx <= 0) {
|
||||
qCWarning(Log) << "Skipping" << fileName << "due to having no valid kateversion attribute:" << verStr;
|
||||
return false;
|
||||
}
|
||||
const auto major = verStr.sliced(0, idx).toInt();
|
||||
const auto minor = verStr.sliced(idx + 1).toInt();
|
||||
|
||||
if (major > KSYNTAXHIGHLIGHTING_VERSION_MAJOR || (major == KSYNTAXHIGHLIGHTING_VERSION_MAJOR && minor > KSYNTAXHIGHLIGHTING_VERSION_MINOR)) {
|
||||
qCWarning(Log) << "Skipping" << fileName << "due to being too new, version:" << verStr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
quint16 DefinitionData::foldingRegionId(const QString &foldName)
|
||||
{
|
||||
foldingRegionsState = FoldingRegionsState::ContainsFoldingRegions;
|
||||
return RepositoryPrivate::get(repo)->foldingRegionId(name, foldName);
|
||||
}
|
||||
|
||||
DefinitionData::ResolvedContext DefinitionData::resolveIncludedContext(QStringView defName, QStringView contextName)
|
||||
{
|
||||
if (defName.isEmpty()) {
|
||||
return {this, contextByName(contextName)};
|
||||
}
|
||||
|
||||
auto d = repo->definitionForName(defName.toString());
|
||||
if (d.isValid()) {
|
||||
auto *resolvedDef = get(d);
|
||||
if (resolvedDef != this) {
|
||||
if (std::find(immediateIncludedDefinitions.begin(), immediateIncludedDefinitions.end(), resolvedDef) == immediateIncludedDefinitions.end()) {
|
||||
immediateIncludedDefinitions.push_back(resolvedDef);
|
||||
resolvedDef->load();
|
||||
if (resolvedDef->foldingRegionsState == FoldingRegionsState::ContainsFoldingRegions) {
|
||||
foldingRegionsState = FoldingRegionsState::ContainsFoldingRegions;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (contextName.isEmpty()) {
|
||||
return {resolvedDef, resolvedDef->initialContext()};
|
||||
} else {
|
||||
return {resolvedDef, resolvedDef->contextByName(contextName)};
|
||||
}
|
||||
}
|
||||
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
#include "moc_definition.cpp"
|
||||
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_DEFINITION_H
|
||||
#define KSYNTAXHIGHLIGHTING_DEFINITION_H
|
||||
|
||||
#include "ksyntaxhighlighting_export.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include <qobjectdefs.h>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class Context;
|
||||
class Format;
|
||||
class KeywordList;
|
||||
|
||||
class DefinitionData;
|
||||
|
||||
/**
|
||||
* Defines the insert position when commenting code.
|
||||
* @since 5.50
|
||||
* @see Definition::singleLineCommentPosition()
|
||||
*/
|
||||
enum class CommentPosition {
|
||||
//! The comment marker is inserted at the beginning of a line at column 0
|
||||
StartOfLine = 0,
|
||||
//! The comment marker is inserted after leading whitespaces right befire
|
||||
//! the first non-whitespace character.
|
||||
AfterWhitespace
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a syntax definition.
|
||||
*
|
||||
* @section def_intro Introduction to Definitions
|
||||
*
|
||||
* A Definition is the short term for a syntax highlighting definition. It
|
||||
* typically is defined in terms of an XML syntax highlighting file, containing
|
||||
* all information about a particular syntax highlighting. This includes the
|
||||
* highlighting of keywords, information about code folding regions, and
|
||||
* indentation preferences.
|
||||
*
|
||||
* @section def_info General Header Data
|
||||
*
|
||||
* Each Definition contains a non-translated unique name() and a section().
|
||||
* In addition, for putting this information e.g. into menus, the functions
|
||||
* translatedName() and translatedSection() are provided. However, if isHidden()
|
||||
* returns @e true, the Definition should not be visible in the UI. The location
|
||||
* of the Definition can be obtained through filePath(), which either is the
|
||||
* location on disk or a path to a compiled-in Qt resource.
|
||||
*
|
||||
* The supported files of a Definition are defined by the list of extensions(),
|
||||
* and additionally by the list of mimeTypes(). Note, that extensions() returns
|
||||
* wildcards that need to be matched against the filename of the file that
|
||||
* requires highlighting. If multiple Definition%s match the file, then the one
|
||||
* with higher priority() wins.
|
||||
*
|
||||
* @section def_metadata Advanced Definition Data
|
||||
*
|
||||
* Advanced text editors such as Kate require additional information from a
|
||||
* Definition. For instance, foldingEnabled() defines whether a Definition has
|
||||
* code folding regions that can be shown in a code folding pane. Or
|
||||
* singleLineCommentMarker() and multiLineCommentMarker() provide comment
|
||||
* markers that can be used for commenting/uncommenting code. Similarly,
|
||||
* formats() returns a list of Format items defined by this Definition (which
|
||||
* equal the itemDatas of a highlighting definition file). includedDefinitions()
|
||||
* returns a list of all included Definition%s referenced by this Definition via
|
||||
* the rule IncludeRules, which is useful for displaying all Format items for
|
||||
* color configuration in the user interface.
|
||||
*
|
||||
* @see Repository
|
||||
* @since 5.28
|
||||
*/
|
||||
class KSYNTAXHIGHLIGHTING_EXPORT Definition
|
||||
{
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QString name READ name)
|
||||
Q_PROPERTY(QString translatedName READ translatedName)
|
||||
Q_PROPERTY(QString section READ section)
|
||||
Q_PROPERTY(QString translatedSection READ translatedSection)
|
||||
Q_PROPERTY(QString author READ author)
|
||||
Q_PROPERTY(QString license READ license)
|
||||
public:
|
||||
/**
|
||||
* Default constructor, creating an empty (invalid) Definition instance.
|
||||
* isValid() for this instance returns @e false.
|
||||
*
|
||||
* Use the Repository instead to obtain valid instances.
|
||||
*/
|
||||
Definition();
|
||||
|
||||
/**
|
||||
* Move constructor.
|
||||
* This definition takes the Definition data from @p other.
|
||||
* @note @p other may only be assigned to or destroyed afterwards.
|
||||
* @since 5.86
|
||||
*/
|
||||
Definition(Definition &&other) noexcept;
|
||||
|
||||
/**
|
||||
* Copy constructor.
|
||||
* Both this definition as well as @p other share the Definition data.
|
||||
*/
|
||||
Definition(const Definition &other);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~Definition();
|
||||
|
||||
/**
|
||||
* Move assignment operator.
|
||||
* This definition takes the Definition data from @p other.
|
||||
* @note @p other may only be assigned to or destroyed afterwards.
|
||||
* @since 5.86
|
||||
*/
|
||||
Definition &operator=(Definition &&other) noexcept;
|
||||
|
||||
/**
|
||||
* Copy assignment operator.
|
||||
* Both this definition as well as @p rhs share the Definition data.
|
||||
*/
|
||||
Definition &operator=(const Definition &rhs);
|
||||
|
||||
/**
|
||||
* Checks two definitions for equality.
|
||||
*/
|
||||
bool operator==(const Definition &other) const;
|
||||
|
||||
/**
|
||||
* Checks two definitions for inequality.
|
||||
*/
|
||||
bool operator!=(const Definition &other) const;
|
||||
|
||||
/**
|
||||
* @name General Header Data
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks whether this object refers to a valid syntax definition.
|
||||
*/
|
||||
bool isValid() const;
|
||||
|
||||
/**
|
||||
* Returns the full path to the definition XML file containing
|
||||
* the syntax definition. Note that this can be a path to QRC content.
|
||||
*/
|
||||
QString filePath() const;
|
||||
|
||||
/** Name of the syntax.
|
||||
* Used for internal references, prefer translatedName() for display.
|
||||
*/
|
||||
QString name() const;
|
||||
|
||||
/**
|
||||
* Alternate names the syntax can be referred to by.
|
||||
*
|
||||
* @since 6.1
|
||||
*/
|
||||
QStringList alternativeNames() const;
|
||||
|
||||
/**
|
||||
* Translated name for display.
|
||||
*/
|
||||
QString translatedName() const;
|
||||
|
||||
/**
|
||||
* The group this syntax definition belongs to.
|
||||
* For display, consider translatedSection().
|
||||
*/
|
||||
QString section() const;
|
||||
|
||||
/**
|
||||
* Translated group name for display.
|
||||
*/
|
||||
QString translatedSection() const;
|
||||
|
||||
/**
|
||||
* Mime types associated with this syntax definition.
|
||||
*/
|
||||
QList<QString> mimeTypes() const;
|
||||
|
||||
/**
|
||||
* File extensions associated with this syntax definition.
|
||||
* The returned list contains wildcards.
|
||||
*/
|
||||
QList<QString> extensions() const;
|
||||
|
||||
/**
|
||||
* Returns the definition version.
|
||||
*/
|
||||
int version() const;
|
||||
|
||||
/**
|
||||
* Returns the definition priority.
|
||||
* A Definition with higher priority wins over Definitions with lower priorities.
|
||||
*/
|
||||
int priority() const;
|
||||
|
||||
/**
|
||||
* Returns @c true if this is an internal definition that should not be
|
||||
* displayed to the user.
|
||||
*/
|
||||
bool isHidden() const;
|
||||
|
||||
/**
|
||||
* Generalized language style, used for indentation.
|
||||
*/
|
||||
QString style() const;
|
||||
|
||||
/**
|
||||
* Indentation style to be used for this syntax.
|
||||
*/
|
||||
QString indenter() const;
|
||||
|
||||
/**
|
||||
* Name and email of the author of this syntax definition.
|
||||
*/
|
||||
QString author() const;
|
||||
|
||||
/**
|
||||
* License of this syntax definition.
|
||||
*/
|
||||
QString license() const;
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
* @name Advanced Definition Data
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns whether the character @p c is a word delimiter.
|
||||
* A delimiter defines whether a characters is a word boundary. Internally,
|
||||
* delimiters are used for matching keyword lists. As example, typically the
|
||||
* dot '.' is a word delimiter. However, if you have a keyword in a keyword
|
||||
* list that contains a dot, you have to add the dot to the
|
||||
* @e weakDeliminator attribute of the @e general section in your
|
||||
* highlighting definition. Similarly, sometimes additional delimiters are
|
||||
* required, which can be specified in @e additionalDeliminator.
|
||||
*
|
||||
* Checking whether a character is a delimiter is useful for instance if
|
||||
* text is selected with double click. Typically, the whole word should be
|
||||
* selected in this case. Similarly to the example above, the dot '.'
|
||||
* usually acts as word delimiter. However, using this function you can
|
||||
* implement text selection in such a way that keyword lists are correctly
|
||||
* selected.
|
||||
*
|
||||
* @note By default, the list of delimiters contains the following
|
||||
* characters: \\t !%&()*+,-./:;<=>?[\\]^{|}~
|
||||
*
|
||||
* @since 5.50
|
||||
* @see isWordWrapDelimiter()
|
||||
*/
|
||||
bool isWordDelimiter(QChar c) const;
|
||||
|
||||
/**
|
||||
* Returns whether it is safe to break a line at before the character @c.
|
||||
* This is useful when wrapping a line e.g. by applying static word wrap.
|
||||
*
|
||||
* As example, consider the LaTeX code
|
||||
* @code
|
||||
* \command1\command2
|
||||
* @endcode
|
||||
* Applying static word wrap could lead to the following code:
|
||||
* @code
|
||||
* \command1\
|
||||
* command2
|
||||
* @endcode
|
||||
* command2 without a leading backslash is invalid in LaTeX. If '\\' is set
|
||||
* as word wrap delimiter, isWordWrapDelimiter('\\') then returns true,
|
||||
* meaning that it is safe to break the line before @c. The resulting code
|
||||
* then would be
|
||||
* @code
|
||||
* \command1
|
||||
* \command2
|
||||
* @endcode
|
||||
*
|
||||
* @note By default, the word wrap delimiters are equal to the word
|
||||
* delimiters in isWordDelimiter().
|
||||
*
|
||||
* @since 5.50
|
||||
* @see isWordDelimiter()
|
||||
*/
|
||||
bool isWordWrapDelimiter(QChar c) const;
|
||||
|
||||
/**
|
||||
* Returns whether the highlighting supports code folding.
|
||||
* Code folding is supported either if the highlighting defines code folding
|
||||
* regions or if indentationBasedFoldingEnabled() returns @e true.
|
||||
* @since 5.50
|
||||
* @see indentationBasedFoldingEnabled()
|
||||
*/
|
||||
bool foldingEnabled() const;
|
||||
|
||||
/**
|
||||
* Returns whether indentation-based folding is enabled.
|
||||
* An example for indentation-based folding is Python.
|
||||
* When indentation-based folding is enabled, make sure to also check
|
||||
* foldingIgnoreList() for lines that should be treated as empty.
|
||||
*
|
||||
* @see foldingIgnoreList(), State::indentationBasedFoldingEnabled()
|
||||
*/
|
||||
bool indentationBasedFoldingEnabled() const;
|
||||
|
||||
/**
|
||||
* If indentationBasedFoldingEnabled() returns @c true, this function returns
|
||||
* a list of regular expressions that represent empty lines. That is, all
|
||||
* lines matching entirely one of the regular expressions should be treated
|
||||
* as empty lines when calculating the indentation-based folding ranges.
|
||||
*
|
||||
* @note This list is only of relevance, if indentationBasedFoldingEnabled()
|
||||
* returns @c true.
|
||||
*
|
||||
* @see indentationBasedFoldingEnabled()
|
||||
*/
|
||||
QStringList foldingIgnoreList() const;
|
||||
|
||||
/**
|
||||
* Returns the section names of keywords.
|
||||
* @since 5.49
|
||||
* @see keywordList()
|
||||
*/
|
||||
QStringList keywordLists() const;
|
||||
|
||||
/**
|
||||
* Returns the list of keywords for the keyword list @p name.
|
||||
* @since 5.49
|
||||
* @see keywordLists(), setKeywordList()
|
||||
*/
|
||||
QStringList keywordList(const QString &name) const;
|
||||
|
||||
/**
|
||||
* Set the contents of the keyword list @p name to @p content.
|
||||
* Only existing keywordLists() can be changed. For non-existent keyword lists,
|
||||
* false is returned.
|
||||
*
|
||||
* Whenever you change a keyword list, make sure to trigger a rehighlight of
|
||||
* your documents. In case you are using QSyntaxHighlighter via SyntaxHighlighter,
|
||||
* this can be done by calling SyntaxHighlighter::rehighlight().
|
||||
*
|
||||
* @note In general, changing keyword lists via setKeywordList() is discouraged,
|
||||
* since if a keyword list name in the syntax highlighting definition
|
||||
* file changes, the call setKeywordList() may suddenly fail.
|
||||
*
|
||||
* @see keywordList(), keywordLists()
|
||||
* @since 5.62
|
||||
*/
|
||||
bool setKeywordList(const QString &name, const QStringList &content);
|
||||
|
||||
/**
|
||||
* Returns a list of all Format items used by this definition.
|
||||
* The order of the Format items equals the order of the itemDatas in the xml file.
|
||||
* @since 5.49
|
||||
*/
|
||||
QList<Format> formats() const;
|
||||
|
||||
/**
|
||||
* Returns a list of Definitions that are referenced with the IncludeRules rule.
|
||||
* The returned list does not include this Definition. In case no other
|
||||
* Definitions are referenced via IncludeRules, the returned list is empty.
|
||||
*
|
||||
* @since 5.49
|
||||
*/
|
||||
QList<Definition> includedDefinitions() const;
|
||||
|
||||
/**
|
||||
* Returns the marker that starts a single line comment.
|
||||
* For instance, in C++ the single line comment marker is "//".
|
||||
* @since 5.50
|
||||
* @see singleLineCommentPosition();
|
||||
*/
|
||||
QString singleLineCommentMarker() const;
|
||||
|
||||
/**
|
||||
* Returns the insert position of the comment marker for sinle line
|
||||
* comments.
|
||||
* @since 5.50
|
||||
* @see singleLineCommentMarker();
|
||||
*/
|
||||
CommentPosition singleLineCommentPosition() const;
|
||||
|
||||
/**
|
||||
* Returns the markers that start and end multiline comments.
|
||||
* For instance, in XML this is defined as "<!--" and "-->".
|
||||
* @since 5.50
|
||||
*/
|
||||
QPair<QString, QString> multiLineCommentMarker() const;
|
||||
|
||||
/**
|
||||
* Returns a list of character/string mapping that can be used for spell
|
||||
* checking. This is useful for instance when spell checking LaTeX, where
|
||||
* the string \"{A} represents the character Ä.
|
||||
* @since 5.50
|
||||
*/
|
||||
QList<QPair<QChar, QString>> characterEncodings() const;
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
private:
|
||||
friend class DefinitionData;
|
||||
KSYNTAXHIGHLIGHTING_NO_EXPORT explicit Definition(const DefinitionData &defData);
|
||||
std::shared_ptr<DefinitionData> d;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
Q_DECLARE_TYPEINFO(KSyntaxHighlighting::Definition, Q_RELOCATABLE_TYPE);
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_DEFINITION_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_DEFINITION_P_H
|
||||
|
||||
#include "definition.h"
|
||||
#include "highlightingdata_p.hpp"
|
||||
#include "state.h"
|
||||
#include "worddelimiters_p.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QVarLengthArray>
|
||||
|
||||
#include <vector>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QCborMap;
|
||||
class QXmlStreamReader;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class Repository;
|
||||
|
||||
class DefinitionData
|
||||
{
|
||||
public:
|
||||
DefinitionData();
|
||||
~DefinitionData();
|
||||
|
||||
DefinitionData(const DefinitionData &) = delete;
|
||||
DefinitionData &operator=(const DefinitionData &) = delete;
|
||||
|
||||
static DefinitionData *get(const Definition &def)
|
||||
{
|
||||
return def.d.get();
|
||||
}
|
||||
|
||||
bool isLoaded() const;
|
||||
bool loadMetaData(const QString &definitionFileName);
|
||||
bool loadMetaData(const QString &fileName, const QCborMap &obj);
|
||||
|
||||
void clear();
|
||||
|
||||
enum class OnlyKeywords : bool;
|
||||
|
||||
bool load(OnlyKeywords onlyKeywords = OnlyKeywords(false));
|
||||
bool loadLanguage(QXmlStreamReader &reader);
|
||||
void loadHighlighting(QXmlStreamReader &reader, OnlyKeywords onlyKeywords);
|
||||
void loadContexts(QXmlStreamReader &reader);
|
||||
void loadItemData(QXmlStreamReader &reader);
|
||||
void loadGeneral(QXmlStreamReader &reader);
|
||||
void loadComments(QXmlStreamReader &reader);
|
||||
void loadFoldingIgnoreList(QXmlStreamReader &reader);
|
||||
void loadSpellchecking(QXmlStreamReader &reader);
|
||||
bool checkKateVersion(QStringView verStr);
|
||||
|
||||
void resolveContexts();
|
||||
|
||||
void resolveIncludeKeywords();
|
||||
|
||||
KeywordList *keywordList(const QString &name);
|
||||
|
||||
Context *initialContext();
|
||||
Context *contextByName(QStringView name);
|
||||
|
||||
Format formatByName(QStringView name) const;
|
||||
|
||||
quint16 foldingRegionId(const QString &foldName);
|
||||
|
||||
struct ResolvedContext {
|
||||
DefinitionData *def;
|
||||
Context *context;
|
||||
};
|
||||
|
||||
ResolvedContext resolveIncludedContext(QStringView defName, QStringView contextName);
|
||||
|
||||
enum class FoldingRegionsState : uint8_t {
|
||||
Undetermined,
|
||||
ContainsFoldingRegions,
|
||||
NoFoldingRegions,
|
||||
};
|
||||
|
||||
std::weak_ptr<DefinitionData> q;
|
||||
uint64_t id = 0;
|
||||
|
||||
Repository *repo = nullptr;
|
||||
QHash<QString, KeywordList> keywordLists;
|
||||
std::vector<Context> contexts;
|
||||
QHash<QStringView, Format> formats;
|
||||
// data loaded from xml file and emptied after loading contexts
|
||||
std::vector<HighlightingContextData> contextDatas;
|
||||
// Definition referenced by IncludeRules and ContextSwitch
|
||||
QVarLengthArray<const DefinitionData *, 4> immediateIncludedDefinitions;
|
||||
WordDelimiters wordDelimiters;
|
||||
WordDelimiters wordWrapDelimiters;
|
||||
bool keywordIsLoaded = false;
|
||||
FoldingRegionsState foldingRegionsState = FoldingRegionsState::Undetermined;
|
||||
bool indentationBasedFolding = false;
|
||||
QStringList foldingIgnoreList;
|
||||
QString singleLineCommentMarker;
|
||||
CommentPosition singleLineCommentPosition = CommentPosition::StartOfLine;
|
||||
QString multiLineCommentStartMarker;
|
||||
QString multiLineCommentEndMarker;
|
||||
QList<QPair<QChar, QString>> characterEncodings;
|
||||
|
||||
QString fileName;
|
||||
QString name = QStringLiteral(QT_TRANSLATE_NOOP("Language", "None"));
|
||||
QStringList alternativeNames;
|
||||
QByteArray nameUtf8;
|
||||
mutable QString translatedName;
|
||||
QString section;
|
||||
QByteArray sectionUtf8;
|
||||
mutable QString translatedSection;
|
||||
QString style;
|
||||
QString indenter;
|
||||
QString author;
|
||||
QString license;
|
||||
QList<QString> mimetypes;
|
||||
QList<QString> extensions;
|
||||
Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive;
|
||||
int version = 0;
|
||||
int priority = 0;
|
||||
bool hidden = false;
|
||||
|
||||
// cache that is used to unify states in AbstractHighlighter::highlightLine
|
||||
mutable QSet<State> unify;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "definitiondownloader.h"
|
||||
#include "definition.h"
|
||||
#include "ksyntaxhighlighting_logging.h"
|
||||
#include "ksyntaxhighlighting_version.h"
|
||||
#include "repository.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
class KSyntaxHighlighting::DefinitionDownloaderPrivate
|
||||
{
|
||||
public:
|
||||
DefinitionDownloader *q;
|
||||
Repository *repo;
|
||||
QNetworkAccessManager *nam;
|
||||
QString downloadLocation;
|
||||
int pendingDownloads;
|
||||
bool needsReload;
|
||||
|
||||
void definitionListDownloadFinished(QNetworkReply *reply);
|
||||
void updateDefinition(QXmlStreamReader &parser);
|
||||
void downloadDefinition(const QUrl &url);
|
||||
void downloadDefinitionFinished(QNetworkReply *reply);
|
||||
void checkDone();
|
||||
};
|
||||
|
||||
void DefinitionDownloaderPrivate::definitionListDownloadFinished(QNetworkReply *reply)
|
||||
{
|
||||
const auto networkError = reply->error();
|
||||
if (networkError != QNetworkReply::NoError) {
|
||||
qCWarning(Log) << networkError;
|
||||
Q_EMIT q->done(); // TODO return error
|
||||
return;
|
||||
}
|
||||
|
||||
QXmlStreamReader parser(reply);
|
||||
while (!parser.atEnd()) {
|
||||
switch (parser.readNext()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
if (parser.name() == QLatin1String("Definition")) {
|
||||
updateDefinition(parser);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingDownloads == 0) {
|
||||
Q_EMIT q->informationMessage(QObject::tr("All syntax definitions are up-to-date."));
|
||||
}
|
||||
checkDone();
|
||||
}
|
||||
|
||||
void DefinitionDownloaderPrivate::updateDefinition(QXmlStreamReader &parser)
|
||||
{
|
||||
const auto name = parser.attributes().value(QLatin1String("name"));
|
||||
if (name.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto localDef = repo->definitionForName(name.toString());
|
||||
if (!localDef.isValid()) {
|
||||
Q_EMIT q->informationMessage(QObject::tr("Downloading new syntax definition for '%1'…", "@info").arg(name));
|
||||
downloadDefinition(QUrl(parser.attributes().value(QLatin1String("url")).toString()));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto version = parser.attributes().value(QLatin1String("version"));
|
||||
if (localDef.version() < version.toFloat()) {
|
||||
Q_EMIT q->informationMessage(QObject::tr("Updating syntax definition for '%1' to version %2…", "@info").arg(name, version));
|
||||
downloadDefinition(QUrl(parser.attributes().value(QLatin1String("url")).toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void DefinitionDownloaderPrivate::downloadDefinition(const QUrl &downloadUrl)
|
||||
{
|
||||
if (!downloadUrl.isValid()) {
|
||||
return;
|
||||
}
|
||||
auto url = downloadUrl;
|
||||
if (url.scheme() == QLatin1String("http")) {
|
||||
url.setScheme(QStringLiteral("https"));
|
||||
}
|
||||
|
||||
QNetworkRequest req(url);
|
||||
auto reply = nam->get(req);
|
||||
QObject::connect(reply, &QNetworkReply::finished, q, [this, reply]() {
|
||||
downloadDefinitionFinished(reply);
|
||||
});
|
||||
++pendingDownloads;
|
||||
needsReload = true;
|
||||
}
|
||||
|
||||
void DefinitionDownloaderPrivate::downloadDefinitionFinished(QNetworkReply *reply)
|
||||
{
|
||||
--pendingDownloads;
|
||||
|
||||
const auto networkError = reply->error();
|
||||
if (networkError != QNetworkReply::NoError) {
|
||||
qCWarning(Log) << "Failed to download definition file" << reply->url() << networkError;
|
||||
checkDone();
|
||||
return;
|
||||
}
|
||||
|
||||
// handle redirects
|
||||
// needs to be done manually, download server redirects to unsafe http links
|
||||
const auto redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
|
||||
if (!redirectUrl.isEmpty()) {
|
||||
downloadDefinition(reply->url().resolved(redirectUrl));
|
||||
checkDone();
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file(downloadLocation + QLatin1Char('/') + reply->url().fileName());
|
||||
if (!file.open(QFile::WriteOnly)) {
|
||||
qCWarning(Log) << "Failed to open" << file.fileName() << file.error();
|
||||
} else {
|
||||
file.write(reply->readAll());
|
||||
}
|
||||
checkDone();
|
||||
}
|
||||
|
||||
void DefinitionDownloaderPrivate::checkDone()
|
||||
{
|
||||
if (pendingDownloads == 0) {
|
||||
if (needsReload) {
|
||||
repo->reload();
|
||||
}
|
||||
|
||||
Q_EMIT QTimer::singleShot(0, q, &DefinitionDownloader::done);
|
||||
}
|
||||
}
|
||||
|
||||
DefinitionDownloader::DefinitionDownloader(Repository *repo, QObject *parent)
|
||||
: QObject(parent)
|
||||
, d(new DefinitionDownloaderPrivate())
|
||||
{
|
||||
Q_ASSERT(repo);
|
||||
|
||||
d->q = this;
|
||||
d->repo = repo;
|
||||
d->nam = new QNetworkAccessManager(this);
|
||||
d->pendingDownloads = 0;
|
||||
d->needsReload = false;
|
||||
|
||||
d->downloadLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/org.kde.syntax-highlighting/syntax");
|
||||
QDir().mkpath(d->downloadLocation);
|
||||
Q_ASSERT(QFile::exists(d->downloadLocation));
|
||||
}
|
||||
|
||||
DefinitionDownloader::~DefinitionDownloader()
|
||||
{
|
||||
}
|
||||
|
||||
void DefinitionDownloader::start()
|
||||
{
|
||||
const QString url = QLatin1String("https://www.kate-editor.org/syntax/update-") + QString::number(KSYNTAXHIGHLIGHTING_VERSION_MAJOR) + QLatin1Char('.')
|
||||
+ QString::number(KSYNTAXHIGHLIGHTING_VERSION_MINOR) + QLatin1String(".xml");
|
||||
auto req = QNetworkRequest(QUrl(url));
|
||||
req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
auto reply = d->nam->get(req);
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
d->definitionListDownloadFinished(reply);
|
||||
});
|
||||
}
|
||||
|
||||
#include "moc_definitiondownloader.cpp"
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_DEFINITIONDOWNLOADER_H
|
||||
#define KSYNTAXHIGHLIGHTING_DEFINITIONDOWNLOADER_H
|
||||
|
||||
#include "ksyntaxhighlighting_export.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <memory>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class DefinitionDownloaderPrivate;
|
||||
class Repository;
|
||||
|
||||
/**
|
||||
* Helper class to download definition file updates.
|
||||
*
|
||||
* With the DefinitionDownloader you can download new and update existing
|
||||
* syntax highlighting definition files (xml files).
|
||||
*
|
||||
* An example that updates the highlighting Definition%s and prints the current
|
||||
* update progress to the console may look as follows:
|
||||
*
|
||||
* @code
|
||||
* auto downloader = new DefinitionDownloader(repo); // repo is a pointer to a Repository
|
||||
*
|
||||
* // print update progress to console
|
||||
* QObject::connect(downloader, &DefinitionDownloader::informationMessage, [](const QString &msg) {
|
||||
* std::cout << qPrintable(msg) << std::endl;
|
||||
* });
|
||||
*
|
||||
* // connect to signal done to delete the downloader later
|
||||
* QObject::connect(downloader, &DefinitionDownloader::done,
|
||||
* downloader, &DefinitionDownloader::deleteLater);
|
||||
* downloader->start();
|
||||
* @endcode
|
||||
*
|
||||
* @see Repository, Definition
|
||||
* @since 5.28
|
||||
*/
|
||||
class KSYNTAXHIGHLIGHTING_EXPORT DefinitionDownloader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
* The Repository @p repo is used as reference to compare the versions of
|
||||
* the existing Definition%s with the ones that are available online.
|
||||
*
|
||||
* Optionally, @p parent is a pointer to the owner of this instance.
|
||||
*/
|
||||
explicit DefinitionDownloader(Repository *repo, QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~DefinitionDownloader() override;
|
||||
|
||||
/**
|
||||
* Starts the update procedure.
|
||||
* Once no more updates are available (i.e. either the local definition files
|
||||
* are up-to-date, or all updates have been downloaded), the signal done()
|
||||
* is emitted.
|
||||
*
|
||||
* During the update process, the signal informationMessage() can be used
|
||||
* to display the current update progress to the user.
|
||||
*
|
||||
* @see done(), informationMessage()
|
||||
*/
|
||||
void start();
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* Prints the information about the current state of the definition files.
|
||||
* If all files are up-to-date, this signal is emitted informing you that
|
||||
* all highlighting files are up-to-date. If there are updates, this signal
|
||||
* is emitted for each update being downloaded.
|
||||
*/
|
||||
void informationMessage(const QString &msg);
|
||||
|
||||
/**
|
||||
* This signal is emitted when there are no pending downloads anymore.
|
||||
*/
|
||||
void done();
|
||||
|
||||
private:
|
||||
std::unique_ptr<DefinitionDownloaderPrivate> d;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_DEFINITIONDOWNLOADER_H
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2023 Jonathan Poelen <jonathan.poelen+kde@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_DYNAMICREGEXPCACHE_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_DYNAMICREGEXPCACHE_P_H
|
||||
|
||||
#include <QCache>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
|
||||
class DynamicRegexpCache
|
||||
{
|
||||
public:
|
||||
const QRegularExpression &compileRegexp(QString &&pattern, QRegularExpression::PatternOptions patternOptions)
|
||||
{
|
||||
const auto key = std::pair{std::move(pattern), patternOptions};
|
||||
if (const auto regexp = m_cache.object(key)) {
|
||||
return *regexp;
|
||||
}
|
||||
auto regexp = new QRegularExpression(key.first, patternOptions);
|
||||
m_cache.insert(key, regexp);
|
||||
return *regexp;
|
||||
}
|
||||
|
||||
private:
|
||||
QCache<std::pair<QString, QRegularExpression::PatternOptions>, QRegularExpression> m_cache;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "foldingregion.h"
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
static_assert(sizeof(FoldingRegion) == sizeof(int), "FoldingRegion is size-sensitive to frequent use in KTextEditor!");
|
||||
|
||||
FoldingRegion::FoldingRegion() = default;
|
||||
|
||||
FoldingRegion::FoldingRegion(Type type, int id)
|
||||
: m_idWithType((type == End) ? -id : id)
|
||||
{
|
||||
}
|
||||
|
||||
bool FoldingRegion::operator==(const FoldingRegion &other) const
|
||||
{
|
||||
return m_idWithType == other.m_idWithType;
|
||||
}
|
||||
|
||||
bool FoldingRegion::isValid() const
|
||||
{
|
||||
return m_idWithType != 0;
|
||||
}
|
||||
|
||||
int FoldingRegion::id() const
|
||||
{
|
||||
return (m_idWithType < 0) ? -m_idWithType : m_idWithType;
|
||||
}
|
||||
|
||||
FoldingRegion::Type FoldingRegion::type() const
|
||||
{
|
||||
if (isValid()) {
|
||||
return (m_idWithType < 0) ? End : Begin;
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
FoldingRegion FoldingRegion::sibling() const
|
||||
{
|
||||
return isValid() ? FoldingRegion(type() ? End : Begin, id()) : FoldingRegion();
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_FOLDINGREGION_H
|
||||
#define KSYNTAXHIGHLIGHTING_FOLDINGREGION_H
|
||||
|
||||
#include "ksyntaxhighlighting_export.h"
|
||||
|
||||
#include <QTypeInfo>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
/** Represents a begin or end of a folding region.
|
||||
* @since 5.28 */
|
||||
class KSYNTAXHIGHLIGHTING_EXPORT FoldingRegion
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Defines whether a FoldingRegion starts or ends a folding region.
|
||||
*/
|
||||
enum Type {
|
||||
//! Used internally as indicator for an invalid FoldingRegion.
|
||||
None,
|
||||
//! Indicates the start of a FoldingRegion.
|
||||
Begin,
|
||||
//! Indicates the end of a FoldingRegion.
|
||||
End
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs an invalid folding region, meaning that isValid() returns @e false.
|
||||
* To obtain valid instances, see AbstractHighlighter::applyFolding().
|
||||
*/
|
||||
FoldingRegion();
|
||||
|
||||
/** Compares two FoldingRegion instances for equality. */
|
||||
bool operator==(const FoldingRegion &other) const;
|
||||
|
||||
/**
|
||||
* Returns @c true if this is a valid folding region.
|
||||
* A valid FoldingRegion is defined by a type() other than Type::None.
|
||||
*
|
||||
* @note The FoldingRegion%s passed in AbstractHighlighter::applyFolding()
|
||||
* are always valid.
|
||||
*/
|
||||
bool isValid() const;
|
||||
|
||||
/**
|
||||
* Returns a unique identifier for this folding region.
|
||||
*
|
||||
* As example, the C/C++ highlighter starts and ends a folding region for
|
||||
* scopes, e.g.:
|
||||
* \code
|
||||
* void foo() { // '{' starts a folding region
|
||||
* if (bar()) { // '{' starts a (nested) folding region
|
||||
* } // '}' ends the (nested) folding region
|
||||
* } // '}' ends the outer folding region
|
||||
* \endcode
|
||||
* In this example, all braces '{' and '}' have the same id(), meaning that
|
||||
* if you want to find the matching closing region for the first opening
|
||||
* brace, you need to do kind of a reference counting to find the correct
|
||||
* closing brace.
|
||||
*/
|
||||
int id() const;
|
||||
|
||||
/**
|
||||
* Returns whether this is the begin or end of a region.
|
||||
*
|
||||
* @note The FoldingRegion%s passed in AbstractHighlighter::applyFolding()
|
||||
* are always valid, i.e. either Type::Begin or Type::End.
|
||||
*/
|
||||
Type type() const;
|
||||
|
||||
/**
|
||||
* Returns the matching start or end region.
|
||||
*
|
||||
* @note Will return invalid region for an invalid region.
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
FoldingRegion sibling() const;
|
||||
|
||||
private:
|
||||
friend class Rule;
|
||||
KSYNTAXHIGHLIGHTING_NO_EXPORT FoldingRegion(Type type, int id);
|
||||
|
||||
// 0 is invalid, positive begin, negative end
|
||||
int m_idWithType = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
Q_DECLARE_TYPEINFO(KSyntaxHighlighting::FoldingRegion, Q_PRIMITIVE_TYPE);
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "format.h"
|
||||
#include "definition.h"
|
||||
#include "format_p.h"
|
||||
#include "textstyledata_p.h"
|
||||
#include "themedata_p.h"
|
||||
#include "xml_p.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QMetaEnum>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
static Theme::TextStyle stringToDefaultFormat(QStringView str)
|
||||
{
|
||||
if (!str.startsWith(QLatin1String("ds"))) {
|
||||
return Theme::Normal;
|
||||
}
|
||||
|
||||
const auto metaEnum = QMetaEnum::fromType<Theme::TextStyle>();
|
||||
|
||||
bool ok = false;
|
||||
const auto value = metaEnum.keyToValue(str.sliced(2).toLatin1().constData(), &ok);
|
||||
if (!ok || value < 0) {
|
||||
return Theme::Normal;
|
||||
}
|
||||
return static_cast<Theme::TextStyle>(value);
|
||||
}
|
||||
|
||||
FormatPrivate *FormatPrivate::detachAndGet(Format &format)
|
||||
{
|
||||
format.d.detach();
|
||||
return format.d.data();
|
||||
}
|
||||
|
||||
TextStyleData FormatPrivate::styleOverride(const Theme &theme) const
|
||||
{
|
||||
return ThemeData::get(theme)->textStyleOverride(definitionName, name);
|
||||
}
|
||||
|
||||
QColor FormatPrivate::color(const Theme &theme, StyleColor styleColor, ThemeColor themeColor) const
|
||||
{
|
||||
const auto overrideStyle = styleOverride(theme);
|
||||
if (overrideStyle.*styleColor) {
|
||||
return QColor::fromRgb(overrideStyle.*styleColor);
|
||||
}
|
||||
// use QColor::fromRgba for QRgb => QColor conversion to avoid unset colors == black!
|
||||
return QColor::fromRgba(style.*styleColor ? style.*styleColor : (theme.*themeColor)(defaultStyle));
|
||||
}
|
||||
|
||||
bool FormatPrivate::hasColor(const Theme &theme, StyleColor styleColor, ThemeColor themeColor) const
|
||||
{
|
||||
// use QColor::fromRgba for background QRgb => QColor conversion to avoid unset colors == black!
|
||||
return color(theme, styleColor, themeColor) != QColor::fromRgba((theme.*themeColor)(Theme::Normal)) && (style.*styleColor || (theme.*themeColor)(defaultStyle) || styleOverride(theme).*styleColor);
|
||||
}
|
||||
|
||||
static QExplicitlySharedDataPointer<FormatPrivate> &sharedDefaultPrivate()
|
||||
{
|
||||
static QExplicitlySharedDataPointer<FormatPrivate> def(new FormatPrivate);
|
||||
return def;
|
||||
}
|
||||
|
||||
Format::Format()
|
||||
: d(sharedDefaultPrivate())
|
||||
{
|
||||
}
|
||||
|
||||
Format::Format(const Format &other)
|
||||
: d(other.d)
|
||||
{
|
||||
}
|
||||
|
||||
Format::~Format()
|
||||
{
|
||||
}
|
||||
|
||||
Format &Format::operator=(const Format &other)
|
||||
{
|
||||
d = other.d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Format::isValid() const
|
||||
{
|
||||
return !d->name.isEmpty();
|
||||
}
|
||||
|
||||
QString Format::name() const
|
||||
{
|
||||
return d->name;
|
||||
}
|
||||
|
||||
int Format::id() const
|
||||
{
|
||||
return d->id;
|
||||
}
|
||||
|
||||
Theme::TextStyle Format::textStyle() const
|
||||
{
|
||||
return d->defaultStyle;
|
||||
}
|
||||
|
||||
bool Format::isDefaultTextStyle(const Theme &theme) const
|
||||
{
|
||||
// use QColor::fromRgba for background QRgb => QColor conversion to avoid unset colors == black!
|
||||
return (!hasTextColor(theme)) && (!hasBackgroundColor(theme)) && (selectedTextColor(theme).rgba() == theme.selectedTextColor(Theme::Normal))
|
||||
&& (selectedBackgroundColor(theme).rgba() == (theme.selectedBackgroundColor(Theme::Normal))) && (isBold(theme) == theme.isBold(Theme::Normal))
|
||||
&& (isItalic(theme) == theme.isItalic(Theme::Normal)) && (isUnderline(theme) == theme.isUnderline(Theme::Normal))
|
||||
&& (isStrikeThrough(theme) == theme.isStrikeThrough(Theme::Normal));
|
||||
}
|
||||
|
||||
bool Format::hasTextColor(const Theme &theme) const
|
||||
{
|
||||
return d->hasColor(theme, &TextStyleData::textColor, &Theme::textColor);
|
||||
}
|
||||
|
||||
QColor Format::textColor(const Theme &theme) const
|
||||
{
|
||||
return d->color(theme, &TextStyleData::textColor, &Theme::textColor);
|
||||
}
|
||||
|
||||
QColor Format::selectedTextColor(const Theme &theme) const
|
||||
{
|
||||
return d->color(theme, &TextStyleData::selectedTextColor, &Theme::selectedTextColor);
|
||||
}
|
||||
|
||||
bool Format::hasBackgroundColor(const Theme &theme) const
|
||||
{
|
||||
return d->hasColor(theme, &TextStyleData::backgroundColor, &Theme::backgroundColor);
|
||||
}
|
||||
|
||||
QColor Format::backgroundColor(const Theme &theme) const
|
||||
{
|
||||
return d->color(theme, &TextStyleData::backgroundColor, &Theme::backgroundColor);
|
||||
}
|
||||
|
||||
QColor Format::selectedBackgroundColor(const Theme &theme) const
|
||||
{
|
||||
return d->color(theme, &TextStyleData::selectedBackgroundColor, &Theme::selectedBackgroundColor);
|
||||
}
|
||||
|
||||
bool Format::isBold(const Theme &theme) const
|
||||
{
|
||||
const auto overrideStyle = d->styleOverride(theme);
|
||||
if (overrideStyle.hasBold) {
|
||||
return overrideStyle.bold;
|
||||
}
|
||||
return d->style.hasBold ? d->style.bold : theme.isBold(d->defaultStyle);
|
||||
}
|
||||
|
||||
bool Format::isItalic(const Theme &theme) const
|
||||
{
|
||||
const auto overrideStyle = d->styleOverride(theme);
|
||||
if (overrideStyle.hasItalic) {
|
||||
return overrideStyle.italic;
|
||||
}
|
||||
return d->style.hasItalic ? d->style.italic : theme.isItalic(d->defaultStyle);
|
||||
}
|
||||
|
||||
bool Format::isUnderline(const Theme &theme) const
|
||||
{
|
||||
const auto overrideStyle = d->styleOverride(theme);
|
||||
if (overrideStyle.hasUnderline) {
|
||||
return overrideStyle.underline;
|
||||
}
|
||||
return d->style.hasUnderline ? d->style.underline : theme.isUnderline(d->defaultStyle);
|
||||
}
|
||||
|
||||
bool Format::isStrikeThrough(const Theme &theme) const
|
||||
{
|
||||
const auto overrideStyle = d->styleOverride(theme);
|
||||
if (overrideStyle.hasStrikeThrough) {
|
||||
return overrideStyle.strikeThrough;
|
||||
}
|
||||
return d->style.hasStrikeThrough ? d->style.strikeThrough : theme.isStrikeThrough(d->defaultStyle);
|
||||
}
|
||||
|
||||
bool Format::spellCheck() const
|
||||
{
|
||||
return d->spellCheck;
|
||||
}
|
||||
|
||||
bool Format::hasBoldOverride() const
|
||||
{
|
||||
return d->style.hasBold;
|
||||
}
|
||||
|
||||
bool Format::hasItalicOverride() const
|
||||
{
|
||||
return d->style.hasItalic;
|
||||
}
|
||||
|
||||
bool Format::hasUnderlineOverride() const
|
||||
{
|
||||
return d->style.hasUnderline;
|
||||
}
|
||||
|
||||
bool Format::hasStrikeThroughOverride() const
|
||||
{
|
||||
return d->style.hasStrikeThrough;
|
||||
}
|
||||
|
||||
bool Format::hasTextColorOverride() const
|
||||
{
|
||||
return d->style.textColor;
|
||||
}
|
||||
|
||||
bool Format::hasBackgroundColorOverride() const
|
||||
{
|
||||
return d->style.backgroundColor;
|
||||
}
|
||||
|
||||
bool Format::hasSelectedTextColorOverride() const
|
||||
{
|
||||
return d->style.selectedTextColor;
|
||||
}
|
||||
|
||||
bool Format::hasSelectedBackgroundColorOverride() const
|
||||
{
|
||||
return d->style.selectedBackgroundColor;
|
||||
}
|
||||
|
||||
void FormatPrivate::load(QXmlStreamReader &reader)
|
||||
{
|
||||
name = reader.attributes().value(QLatin1String("name")).toString();
|
||||
defaultStyle = stringToDefaultFormat(reader.attributes().value(QLatin1String("defStyleNum")));
|
||||
|
||||
QStringView attribute = reader.attributes().value(QLatin1String("color"));
|
||||
if (!attribute.isEmpty()) {
|
||||
style.textColor = QColor(attribute).rgba();
|
||||
}
|
||||
|
||||
attribute = reader.attributes().value(QLatin1String("selColor"));
|
||||
if (!attribute.isEmpty()) {
|
||||
style.selectedTextColor = QColor(attribute).rgba();
|
||||
}
|
||||
|
||||
attribute = reader.attributes().value(QLatin1String("backgroundColor"));
|
||||
if (!attribute.isEmpty()) {
|
||||
style.backgroundColor = QColor(attribute).rgba();
|
||||
}
|
||||
|
||||
attribute = reader.attributes().value(QLatin1String("selBackgroundColor"));
|
||||
if (!attribute.isEmpty()) {
|
||||
style.selectedBackgroundColor = QColor(attribute).rgba();
|
||||
}
|
||||
|
||||
attribute = reader.attributes().value(QLatin1String("italic"));
|
||||
if (!attribute.isEmpty()) {
|
||||
style.hasItalic = true;
|
||||
style.italic = Xml::attrToBool(attribute);
|
||||
}
|
||||
|
||||
attribute = reader.attributes().value(QLatin1String("bold"));
|
||||
if (!attribute.isEmpty()) {
|
||||
style.hasBold = true;
|
||||
style.bold = Xml::attrToBool(attribute);
|
||||
}
|
||||
|
||||
attribute = reader.attributes().value(QLatin1String("underline"));
|
||||
if (!attribute.isEmpty()) {
|
||||
style.hasUnderline = true;
|
||||
style.underline = Xml::attrToBool(attribute);
|
||||
}
|
||||
|
||||
attribute = reader.attributes().value(QLatin1String("strikeOut"));
|
||||
if (!attribute.isEmpty()) {
|
||||
style.hasStrikeThrough = true;
|
||||
style.strikeThrough = Xml::attrToBool(attribute);
|
||||
}
|
||||
|
||||
attribute = reader.attributes().value(QLatin1String("spellChecking"));
|
||||
if (!attribute.isEmpty()) {
|
||||
spellCheck = Xml::attrToBool(attribute);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_FORMAT_H
|
||||
#define KSYNTAXHIGHLIGHTING_FORMAT_H
|
||||
|
||||
#include "ksyntaxhighlighting_export.h"
|
||||
#include "theme.h"
|
||||
|
||||
#include <QExplicitlySharedDataPointer>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class FormatPrivate;
|
||||
|
||||
/** Describes the format to be used for a specific text fragment.
|
||||
* The actual format used for displaying is merged from the format information
|
||||
* in the syntax definition file, and a theme.
|
||||
*
|
||||
* @see Theme
|
||||
* @since 5.28
|
||||
*/
|
||||
class KSYNTAXHIGHLIGHTING_EXPORT Format
|
||||
{
|
||||
public:
|
||||
/** Creates an empty/invalid format. */
|
||||
Format();
|
||||
Format(const Format &other);
|
||||
~Format();
|
||||
|
||||
Format &operator=(const Format &other);
|
||||
|
||||
/** Returns @c true if this is a valid format, ie. one that
|
||||
* was read from a syntax definition file.
|
||||
*/
|
||||
bool isValid() const;
|
||||
|
||||
/** The name of this format as used in the syntax definition file. */
|
||||
QString name() const;
|
||||
|
||||
/** Returns a unique identifier of this format.
|
||||
* This is useful for efficient storing of formats in a text line. The
|
||||
* identifier is unique per Repository instance, but will change when
|
||||
* the repository is reloaded (which also invalidatess the corresponding
|
||||
* Definition anyway).
|
||||
*/
|
||||
int id() const;
|
||||
|
||||
/** Returns the underlying TextStyle of this Format.
|
||||
* Every Theme::TextStyle is visually defined by a Theme. A Format uses one
|
||||
* of the Theme::TextStyle%s and on top allows modifications such as setting
|
||||
* a different foreground color etc.
|
||||
* @see Theme::TextStyle
|
||||
* @since 5.49
|
||||
*/
|
||||
Theme::TextStyle textStyle() const;
|
||||
|
||||
/** Returns @c true if the combination of this format and the theme @p theme
|
||||
* do not change the default text format in any way.
|
||||
* This is useful for output formats where changing formatting implies cost,
|
||||
* and thus benefit from optimizing the default case of not having any format
|
||||
* applied. If you make use of this, make sure to set the default text style
|
||||
* to what the corresponding theme sets for Theme::Normal.
|
||||
*/
|
||||
bool isDefaultTextStyle(const Theme &theme) const;
|
||||
|
||||
/** Returns @c true if the combination of this format and the theme @p theme
|
||||
* change the foreground color compared to the default format.
|
||||
*/
|
||||
bool hasTextColor(const Theme &theme) const;
|
||||
/** Returns the foreground color of the combination of this format and the
|
||||
* given theme.
|
||||
*/
|
||||
QColor textColor(const Theme &theme) const;
|
||||
/** Returns the foreground color for selected text of the combination of
|
||||
* this format and the given theme.
|
||||
*/
|
||||
QColor selectedTextColor(const Theme &theme) const;
|
||||
/** Returns @c true if the combination of this format and the theme @p theme
|
||||
* change the background color compared to the default format.
|
||||
*/
|
||||
bool hasBackgroundColor(const Theme &theme) const;
|
||||
/** Returns the background color of the combination of this format and the
|
||||
* given theme.
|
||||
*/
|
||||
QColor backgroundColor(const Theme &theme) const;
|
||||
/** Returns the background color of selected text of the combination of
|
||||
* this format and the given theme.
|
||||
*/
|
||||
QColor selectedBackgroundColor(const Theme &theme) const;
|
||||
|
||||
/** Returns @c true if the combination of this format and the given theme
|
||||
* results in bold text formatting.
|
||||
*/
|
||||
bool isBold(const Theme &theme) const;
|
||||
/** Returns @c true if the combination of this format and the given theme
|
||||
* results in italic text formatting.
|
||||
*/
|
||||
bool isItalic(const Theme &theme) const;
|
||||
/** Returns @c true if the combination of this format and the given theme
|
||||
* results in underlined text.
|
||||
*/
|
||||
bool isUnderline(const Theme &theme) const;
|
||||
/** Returns @c true if the combination of this format and the given theme
|
||||
* results in struck through text.
|
||||
*/
|
||||
bool isStrikeThrough(const Theme &theme) const;
|
||||
|
||||
/**
|
||||
* Returns whether characters with this format should be spell checked.
|
||||
*/
|
||||
bool spellCheck() const;
|
||||
|
||||
/** Returns @c true if the syntax definition file sets a value for the bold text
|
||||
* attribute and, therefore, overrides the theme and the default formatting
|
||||
* style. If the return is @p true, this value is obtained by isBold().
|
||||
* @see isBold()
|
||||
* @since 5.62
|
||||
*/
|
||||
bool hasBoldOverride() const;
|
||||
|
||||
/** Returns @c true if the syntax definition file sets a value for the italic text
|
||||
* attribute and, therefore, overrides the theme and the default formatting style.
|
||||
* If the return is @p true, this value is obtained by isItalic().
|
||||
* @see isItalic()
|
||||
* @since 5.62
|
||||
*/
|
||||
bool hasItalicOverride() const;
|
||||
|
||||
/** Returns @c true if the syntax definition file sets a value for the underlined
|
||||
* text attribute and, therefore, overrides the theme and the default formatting
|
||||
* style. If the return is @p true, this value is obtained by isUnderline().
|
||||
* @see isUnderline()
|
||||
* @since 5.62
|
||||
*/
|
||||
bool hasUnderlineOverride() const;
|
||||
|
||||
/** Returns @c true if the syntax definition file specifies a value for the
|
||||
* struck through text attribute. If the return is @p true, this value
|
||||
* is obtained by isStrikeThrough().
|
||||
* @see isStrikeThrough()
|
||||
* @since 5.62
|
||||
*/
|
||||
bool hasStrikeThroughOverride() const;
|
||||
|
||||
/** Returns @c true if the syntax definition file sets a value for the foreground
|
||||
* text color attribute and, therefore, overrides the theme and the default formatting
|
||||
* style. If the return is @p true, this value is obtained by textColor().
|
||||
* @see textColor(), hasTextColor()
|
||||
* @since 5.62
|
||||
*/
|
||||
bool hasTextColorOverride() const;
|
||||
|
||||
/** Returns @c true if the syntax definition file sets a value for the background
|
||||
* color attribute and, therefore, overrides the theme and the default formatting
|
||||
* style. If the return is @p true, this value is obtained by backgroundColor().
|
||||
* @see backgroundColor(), hasBackgroundColor()
|
||||
* @since 5.62
|
||||
*/
|
||||
bool hasBackgroundColorOverride() const;
|
||||
|
||||
/** Returns @c true if the syntax definition file specifies a value for the
|
||||
* selected text color attribute. If the return is @p true, this value is
|
||||
* obtained by selectedTextColor().
|
||||
* @see selectedTextColor()
|
||||
* @since 5.62
|
||||
*/
|
||||
bool hasSelectedTextColorOverride() const;
|
||||
|
||||
/** Returns @c true if the syntax definition file specifies a value for the
|
||||
* selected background color attribute. If the return is @p true, this
|
||||
* value is obtained by selectedBackgroundColor().
|
||||
* @see selectedBackgroundColor()
|
||||
* @since 5.62
|
||||
*/
|
||||
bool hasSelectedBackgroundColorOverride() const;
|
||||
|
||||
private:
|
||||
friend class FormatPrivate;
|
||||
QExplicitlySharedDataPointer<FormatPrivate> d;
|
||||
};
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
Q_DECLARE_TYPEINFO(KSyntaxHighlighting::Format, Q_RELOCATABLE_TYPE);
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_FORMAT_H
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_FORMAT_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_FORMAT_P_H
|
||||
|
||||
#include "textstyledata_p.h"
|
||||
#include "theme.h"
|
||||
|
||||
#include <QSharedData>
|
||||
#include <QString>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QXmlStreamReader;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class FormatPrivate : public QSharedData
|
||||
{
|
||||
public:
|
||||
FormatPrivate() = default;
|
||||
static FormatPrivate *detachAndGet(Format &format);
|
||||
|
||||
static std::intptr_t ptrId(const Format &format)
|
||||
{
|
||||
return std::intptr_t(format.d.data());
|
||||
}
|
||||
|
||||
TextStyleData styleOverride(const Theme &theme) const;
|
||||
void load(QXmlStreamReader &reader);
|
||||
|
||||
using StyleColor = QRgb(TextStyleData::*);
|
||||
using ThemeColor = QRgb (Theme::*)(Theme::TextStyle) const;
|
||||
bool hasColor(const Theme &theme, StyleColor styleColor, ThemeColor themeColor) const;
|
||||
QColor color(const Theme &theme, StyleColor styleColor, ThemeColor themeColor) const;
|
||||
|
||||
QString definitionName;
|
||||
QString name;
|
||||
TextStyleData style;
|
||||
Theme::TextStyle defaultStyle = Theme::Normal;
|
||||
int id = 0;
|
||||
bool spellCheck = true;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2021 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "highlightingdata_p.hpp"
|
||||
#include "ksyntaxhighlighting_logging.h"
|
||||
#include "xml_p.h"
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
#include <QStringView>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
template<class Data, class... Args>
|
||||
static void initRuleData(Data &data, Args &&...args)
|
||||
{
|
||||
new (&data) Data{std::move(args)...};
|
||||
}
|
||||
|
||||
static Qt::CaseSensitivity attrToCaseSensitivity(QStringView str)
|
||||
{
|
||||
return Xml::attrToBool(str) ? Qt::CaseInsensitive : Qt::CaseSensitive;
|
||||
}
|
||||
|
||||
static HighlightingContextData::Rule::WordDelimiters loadAdditionalWordDelimiters(QXmlStreamReader &reader)
|
||||
{
|
||||
return HighlightingContextData::Rule::WordDelimiters{
|
||||
reader.attributes().value(QLatin1String("additionalDeliminator")).toString(),
|
||||
reader.attributes().value(QLatin1String("weakDeliminator")).toString(),
|
||||
};
|
||||
}
|
||||
|
||||
static bool checkIsNotEmpty(QStringView str, const char *attrName, const QString &defName, QXmlStreamReader &reader)
|
||||
{
|
||||
if (!str.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
qCWarning(Log) << defName << "at line" << reader.lineNumber() << ": " << attrName << "attribute is empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool checkIsChar(QStringView str, const char *attrName, const QString &defName, QXmlStreamReader &reader)
|
||||
{
|
||||
if (str.size() == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
qCWarning(Log) << defName << "at line" << reader.lineNumber() << ": " << attrName << "attribute must contain exactly 1 character";
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool loadRule(const QString &defName, HighlightingContextData::Rule &rule, QXmlStreamReader &reader)
|
||||
{
|
||||
using Rule = HighlightingContextData::Rule;
|
||||
|
||||
QStringView name = reader.name();
|
||||
const auto attrs = reader.attributes();
|
||||
bool isIncludeRules = false;
|
||||
|
||||
if (name == QLatin1String("DetectChar")) {
|
||||
const auto s = attrs.value(QLatin1String("char"));
|
||||
if (!checkIsChar(s, "char", defName, reader)) {
|
||||
return false;
|
||||
}
|
||||
const QChar c = s.at(0);
|
||||
const bool dynamic = Xml::attrToBool(attrs.value(QLatin1String("dynamic")));
|
||||
|
||||
initRuleData(rule.data.detectChar, c, dynamic);
|
||||
rule.type = Rule::Type::DetectChar;
|
||||
} else if (name == QLatin1String("RegExpr")) {
|
||||
const auto pattern = attrs.value(QLatin1String("String"));
|
||||
if (!checkIsNotEmpty(pattern, "String", defName, reader)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto isCaseInsensitive = attrToCaseSensitivity(attrs.value(QLatin1String("insensitive")));
|
||||
const auto isMinimal = Xml::attrToBool(attrs.value(QLatin1String("minimal")));
|
||||
const auto dynamic = Xml::attrToBool(attrs.value(QLatin1String("dynamic")));
|
||||
|
||||
initRuleData(rule.data.regExpr, pattern.toString(), isCaseInsensitive, isMinimal, dynamic);
|
||||
rule.type = Rule::Type::RegExpr;
|
||||
} else if (name == QLatin1String("IncludeRules")) {
|
||||
const auto context = attrs.value(QLatin1String("context"));
|
||||
if (!checkIsNotEmpty(context, "context", defName, reader)) {
|
||||
return false;
|
||||
}
|
||||
const bool includeAttribute = Xml::attrToBool(attrs.value(QLatin1String("includeAttrib")));
|
||||
|
||||
initRuleData(rule.data.includeRules, context.toString(), includeAttribute);
|
||||
rule.type = Rule::Type::IncludeRules;
|
||||
isIncludeRules = true;
|
||||
} else if (name == QLatin1String("Detect2Chars")) {
|
||||
const auto s1 = attrs.value(QLatin1String("char"));
|
||||
const auto s2 = attrs.value(QLatin1String("char1"));
|
||||
if (!checkIsChar(s1, "char", defName, reader)) {
|
||||
return false;
|
||||
}
|
||||
if (!checkIsChar(s2, "char1", defName, reader)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
initRuleData(rule.data.detect2Chars, s1.at(0), s2.at(0));
|
||||
rule.type = Rule::Type::Detect2Chars;
|
||||
} else if (name == QLatin1String("keyword")) {
|
||||
const auto s = attrs.value(QLatin1String("String"));
|
||||
if (!checkIsNotEmpty(s, "String", defName, reader)) {
|
||||
return false;
|
||||
}
|
||||
Qt::CaseSensitivity caseSensitivityOverride = Qt::CaseInsensitive;
|
||||
bool hasCaseSensitivityOverride = false;
|
||||
|
||||
/**
|
||||
* we might overwrite the case sensitivity
|
||||
* then we need to init the list for lookup of that sensitivity setting
|
||||
*/
|
||||
if (attrs.hasAttribute(QLatin1String("insensitive"))) {
|
||||
hasCaseSensitivityOverride = true;
|
||||
caseSensitivityOverride = attrToCaseSensitivity(attrs.value(QLatin1String("insensitive")));
|
||||
}
|
||||
|
||||
initRuleData(rule.data.keyword, s.toString(), loadAdditionalWordDelimiters(reader), caseSensitivityOverride, hasCaseSensitivityOverride);
|
||||
rule.type = Rule::Type::Keyword;
|
||||
} else if (name == QLatin1String("DetectSpaces")) {
|
||||
rule.type = Rule::Type::DetectSpaces;
|
||||
} else if (name == QLatin1String("StringDetect")) {
|
||||
const auto string = attrs.value(QLatin1String("String"));
|
||||
if (!checkIsNotEmpty(string, "String", defName, reader)) {
|
||||
return false;
|
||||
}
|
||||
const auto caseSensitivity = attrToCaseSensitivity(attrs.value(QLatin1String("insensitive")));
|
||||
const auto dynamic = Xml::attrToBool(attrs.value(QLatin1String("dynamic")));
|
||||
const bool isSensitive = (caseSensitivity == Qt::CaseSensitive);
|
||||
|
||||
// String can be replaced with DetectChar or AnyChar
|
||||
if (!dynamic && string.size() == 1) {
|
||||
QChar c = string.at(0);
|
||||
if (isSensitive || c.toLower() == c.toUpper()) {
|
||||
initRuleData(rule.data.detectChar, c, dynamic);
|
||||
rule.type = Rule::Type::DetectChar;
|
||||
} else {
|
||||
initRuleData(rule.data.anyChar, c.toLower() + c.toUpper());
|
||||
rule.type = Rule::Type::AnyChar;
|
||||
}
|
||||
}
|
||||
// String can be replaced with Detect2Chars
|
||||
else if (isSensitive && !dynamic && string.size() == 2) {
|
||||
initRuleData(rule.data.detect2Chars, string.at(0), string.at(1));
|
||||
rule.type = Rule::Type::Detect2Chars;
|
||||
} else {
|
||||
initRuleData(rule.data.stringDetect, string.toString(), caseSensitivity, dynamic);
|
||||
rule.type = Rule::Type::StringDetect;
|
||||
}
|
||||
} else if (name == QLatin1String("WordDetect")) {
|
||||
const auto word = attrs.value(QLatin1String("String"));
|
||||
if (!checkIsNotEmpty(word, "String", defName, reader)) {
|
||||
return false;
|
||||
}
|
||||
const auto caseSensitivity = attrToCaseSensitivity(attrs.value(QLatin1String("insensitive")));
|
||||
|
||||
initRuleData(rule.data.wordDetect, word.toString(), loadAdditionalWordDelimiters(reader), caseSensitivity);
|
||||
rule.type = Rule::Type::WordDetect;
|
||||
} else if (name == QLatin1String("AnyChar")) {
|
||||
const auto chars = attrs.value(QLatin1String("String"));
|
||||
if (!checkIsNotEmpty(chars, "String", defName, reader)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// AnyChar can be replaced with DetectChar
|
||||
if (chars.size() == 1) {
|
||||
initRuleData(rule.data.detectChar, chars.at(0), false);
|
||||
rule.type = Rule::Type::DetectChar;
|
||||
} else {
|
||||
initRuleData(rule.data.anyChar, chars.toString());
|
||||
rule.type = Rule::Type::AnyChar;
|
||||
}
|
||||
} else if (name == QLatin1String("DetectIdentifier")) {
|
||||
rule.type = Rule::Type::DetectIdentifier;
|
||||
} else if (name == QLatin1String("LineContinue")) {
|
||||
const auto s = attrs.value(QLatin1String("char"));
|
||||
const QChar c = s.isEmpty() ? QLatin1Char('\\') : s.at(0);
|
||||
|
||||
initRuleData(rule.data.lineContinue, c);
|
||||
rule.type = Rule::Type::LineContinue;
|
||||
} else if (name == QLatin1String("Int")) {
|
||||
initRuleData(rule.data.detectInt, loadAdditionalWordDelimiters(reader));
|
||||
rule.type = Rule::Type::Int;
|
||||
} else if (name == QLatin1String("Float")) {
|
||||
initRuleData(rule.data.detectFloat, loadAdditionalWordDelimiters(reader));
|
||||
rule.type = Rule::Type::Float;
|
||||
} else if (name == QLatin1String("HlCStringChar")) {
|
||||
rule.type = Rule::Type::HlCStringChar;
|
||||
} else if (name == QLatin1String("RangeDetect")) {
|
||||
const auto s1 = attrs.value(QLatin1String("char"));
|
||||
const auto s2 = attrs.value(QLatin1String("char1"));
|
||||
if (!checkIsChar(s1, "char", defName, reader)) {
|
||||
return false;
|
||||
}
|
||||
if (!checkIsChar(s2, "char1", defName, reader)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
initRuleData(rule.data.rangeDetect, s1.at(0), s2.at(0));
|
||||
rule.type = Rule::Type::RangeDetect;
|
||||
} else if (name == QLatin1String("HlCHex")) {
|
||||
initRuleData(rule.data.hlCHex, loadAdditionalWordDelimiters(reader));
|
||||
rule.type = Rule::Type::HlCHex;
|
||||
} else if (name == QLatin1String("HlCChar")) {
|
||||
rule.type = Rule::Type::HlCChar;
|
||||
} else if (name == QLatin1String("HlCOct")) {
|
||||
initRuleData(rule.data.hlCOct, loadAdditionalWordDelimiters(reader));
|
||||
rule.type = Rule::Type::HlCOct;
|
||||
} else {
|
||||
qCWarning(Log) << "Unknown rule type:" << name;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isIncludeRules) {
|
||||
rule.common.contextName = attrs.value(QLatin1String("context")).toString();
|
||||
rule.common.beginRegionName = attrs.value(QLatin1String("beginRegion")).toString();
|
||||
rule.common.endRegionName = attrs.value(QLatin1String("endRegion")).toString();
|
||||
rule.common.firstNonSpace = Xml::attrToBool(attrs.value(QLatin1String("firstNonSpace")));
|
||||
rule.common.lookAhead = Xml::attrToBool(attrs.value(QLatin1String("lookAhead")));
|
||||
// attribute is only used when lookAhead is false
|
||||
if (!rule.common.lookAhead) {
|
||||
rule.common.attributeName = attrs.value(QLatin1String("attribute")).toString();
|
||||
}
|
||||
bool colOk = false;
|
||||
rule.common.column = attrs.value(QLatin1String("column")).toInt(&colOk);
|
||||
if (!colOk) {
|
||||
rule.common.column = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class Data1, class Data2, class Visitor>
|
||||
static void dataRuleVisit(HighlightingContextData::Rule::Type type, Data1 &&data1, Data2 &&data2, Visitor &&visitor)
|
||||
{
|
||||
using Rule = HighlightingContextData::Rule;
|
||||
using Type = Rule::Type;
|
||||
switch (type) {
|
||||
case Type::AnyChar:
|
||||
visitor(data1.anyChar, data2.anyChar);
|
||||
break;
|
||||
case Type::DetectChar:
|
||||
visitor(data1.detectChar, data2.detectChar);
|
||||
break;
|
||||
case Type::Detect2Chars:
|
||||
visitor(data1.detect2Chars, data2.detect2Chars);
|
||||
break;
|
||||
case Type::HlCOct:
|
||||
visitor(data1.hlCOct, data2.hlCOct);
|
||||
break;
|
||||
case Type::IncludeRules:
|
||||
visitor(data1.includeRules, data2.includeRules);
|
||||
break;
|
||||
case Type::Int:
|
||||
visitor(data1.detectInt, data2.detectInt);
|
||||
break;
|
||||
case Type::Keyword:
|
||||
visitor(data1.keyword, data2.keyword);
|
||||
break;
|
||||
case Type::LineContinue:
|
||||
visitor(data1.lineContinue, data2.lineContinue);
|
||||
break;
|
||||
case Type::RangeDetect:
|
||||
visitor(data1.rangeDetect, data2.rangeDetect);
|
||||
break;
|
||||
case Type::RegExpr:
|
||||
visitor(data1.regExpr, data2.regExpr);
|
||||
break;
|
||||
case Type::StringDetect:
|
||||
visitor(data1.stringDetect, data2.stringDetect);
|
||||
break;
|
||||
case Type::WordDetect:
|
||||
visitor(data1.wordDetect, data2.wordDetect);
|
||||
break;
|
||||
case Type::Float:
|
||||
visitor(data1.detectFloat, data2.detectFloat);
|
||||
break;
|
||||
case Type::HlCHex:
|
||||
visitor(data1.hlCHex, data2.hlCHex);
|
||||
break;
|
||||
|
||||
case Type::HlCStringChar:
|
||||
case Type::DetectIdentifier:
|
||||
case Type::DetectSpaces:
|
||||
case Type::HlCChar:
|
||||
case Type::Unknown:;
|
||||
}
|
||||
}
|
||||
|
||||
HighlightingContextData::Rule::Rule() noexcept = default;
|
||||
|
||||
HighlightingContextData::Rule::Rule(Rule &&other) noexcept
|
||||
: common(std::move(other.common))
|
||||
{
|
||||
dataRuleVisit(other.type, data, other.data, [](auto &data1, auto &data2) {
|
||||
using Data = std::remove_reference_t<decltype(data1)>;
|
||||
new (&data1) Data(std::move(data2));
|
||||
});
|
||||
type = other.type;
|
||||
}
|
||||
|
||||
HighlightingContextData::Rule::Rule(const Rule &other)
|
||||
: common(other.common)
|
||||
{
|
||||
dataRuleVisit(other.type, data, other.data, [](auto &data1, auto &data2) {
|
||||
using Data = std::remove_reference_t<decltype(data1)>;
|
||||
new (&data1) Data(data2);
|
||||
});
|
||||
type = other.type;
|
||||
}
|
||||
|
||||
HighlightingContextData::Rule::~Rule()
|
||||
{
|
||||
dataRuleVisit(type, data, data, [](auto &data, auto &) {
|
||||
using Data = std::remove_reference_t<decltype(data)>;
|
||||
data.~Data();
|
||||
});
|
||||
}
|
||||
|
||||
void HighlightingContextData::load(const QString &defName, QXmlStreamReader &reader)
|
||||
{
|
||||
Q_ASSERT(reader.name() == QLatin1String("context"));
|
||||
Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
|
||||
|
||||
name = reader.attributes().value(QLatin1String("name")).toString();
|
||||
attribute = reader.attributes().value(QLatin1String("attribute")).toString();
|
||||
lineEndContext = reader.attributes().value(QLatin1String("lineEndContext")).toString();
|
||||
lineEmptyContext = reader.attributes().value(QLatin1String("lineEmptyContext")).toString();
|
||||
fallthroughContext = reader.attributes().value(QLatin1String("fallthroughContext")).toString();
|
||||
noIndentationBasedFolding = Xml::attrToBool(reader.attributes().value(QLatin1String("noIndentationBasedFolding")));
|
||||
stopEmptyLineContextSwitchLoop = Xml::attrToBool(reader.attributes().value(QLatin1String("stopEmptyLineContextSwitchLoop")));
|
||||
|
||||
rules.reserve(8);
|
||||
|
||||
reader.readNext();
|
||||
while (!reader.atEnd()) {
|
||||
switch (reader.tokenType()) {
|
||||
case QXmlStreamReader::StartElement: {
|
||||
auto &rule = rules.emplace_back();
|
||||
if (!loadRule(defName, rule, reader)) {
|
||||
rules.pop_back();
|
||||
}
|
||||
// be done with this rule, skip all subelements, e.g. no longer supported sub-rules
|
||||
reader.skipCurrentElement();
|
||||
reader.readNext();
|
||||
break;
|
||||
}
|
||||
case QXmlStreamReader::EndElement:
|
||||
return;
|
||||
default:
|
||||
reader.readNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2021 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_HIGHLIGHTING_DATA_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_HIGHLIGHTING_DATA_P_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <vector>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QXmlStreamReader;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
/**
|
||||
* Represents the raw xml data of a context and its rules.
|
||||
* After resolving contexts, members of this class are no longer
|
||||
* use and the instance can be freed to recover used memory.
|
||||
*/
|
||||
class HighlightingContextData
|
||||
{
|
||||
public:
|
||||
void load(const QString &defName, QXmlStreamReader &reader);
|
||||
|
||||
struct Rule {
|
||||
enum class Type : quint8 {
|
||||
Unknown,
|
||||
AnyChar,
|
||||
Detect2Chars,
|
||||
DetectChar,
|
||||
HlCOct,
|
||||
IncludeRules,
|
||||
Int,
|
||||
Keyword,
|
||||
LineContinue,
|
||||
RangeDetect,
|
||||
RegExpr,
|
||||
StringDetect,
|
||||
WordDetect,
|
||||
Float,
|
||||
HlCStringChar,
|
||||
DetectIdentifier,
|
||||
DetectSpaces,
|
||||
HlCChar,
|
||||
HlCHex,
|
||||
};
|
||||
|
||||
struct AnyChar {
|
||||
QString chars;
|
||||
};
|
||||
|
||||
struct Detect2Chars {
|
||||
QChar char1;
|
||||
QChar char2;
|
||||
};
|
||||
|
||||
struct DetectChar {
|
||||
QChar char1;
|
||||
bool dynamic;
|
||||
};
|
||||
|
||||
struct WordDelimiters {
|
||||
QString additionalDeliminator;
|
||||
QString weakDeliminator;
|
||||
};
|
||||
|
||||
struct Float {
|
||||
WordDelimiters wordDelimiters;
|
||||
};
|
||||
|
||||
struct HlCHex {
|
||||
WordDelimiters wordDelimiters;
|
||||
};
|
||||
|
||||
struct HlCOct {
|
||||
WordDelimiters wordDelimiters;
|
||||
};
|
||||
|
||||
struct IncludeRules {
|
||||
QString contextName;
|
||||
bool includeAttribute;
|
||||
};
|
||||
|
||||
struct Int {
|
||||
WordDelimiters wordDelimiters;
|
||||
};
|
||||
|
||||
struct Keyword {
|
||||
QString name;
|
||||
WordDelimiters wordDelimiters;
|
||||
Qt::CaseSensitivity caseSensitivityOverride;
|
||||
bool hasCaseSensitivityOverride;
|
||||
};
|
||||
|
||||
struct LineContinue {
|
||||
QChar char1;
|
||||
};
|
||||
|
||||
struct RangeDetect {
|
||||
QChar begin;
|
||||
QChar end;
|
||||
};
|
||||
|
||||
struct RegExpr {
|
||||
QString pattern;
|
||||
Qt::CaseSensitivity caseSensitivity;
|
||||
bool isMinimal;
|
||||
bool dynamic;
|
||||
};
|
||||
|
||||
struct StringDetect {
|
||||
QString string;
|
||||
Qt::CaseSensitivity caseSensitivity;
|
||||
bool dynamic;
|
||||
};
|
||||
|
||||
struct WordDetect {
|
||||
QString word;
|
||||
WordDelimiters wordDelimiters;
|
||||
Qt::CaseSensitivity caseSensitivity;
|
||||
};
|
||||
|
||||
union Data {
|
||||
AnyChar anyChar;
|
||||
Detect2Chars detect2Chars;
|
||||
DetectChar detectChar;
|
||||
HlCOct hlCOct;
|
||||
IncludeRules includeRules;
|
||||
Int detectInt;
|
||||
Keyword keyword;
|
||||
LineContinue lineContinue;
|
||||
RangeDetect rangeDetect;
|
||||
RegExpr regExpr;
|
||||
StringDetect stringDetect;
|
||||
WordDetect wordDetect;
|
||||
Float detectFloat;
|
||||
HlCHex hlCHex;
|
||||
|
||||
Data() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
~Data()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct Common {
|
||||
QString contextName;
|
||||
QString attributeName;
|
||||
QString beginRegionName;
|
||||
QString endRegionName;
|
||||
int column = -1;
|
||||
bool firstNonSpace = false;
|
||||
bool lookAhead = false;
|
||||
};
|
||||
|
||||
Type type = Type::Unknown;
|
||||
Common common;
|
||||
Data data;
|
||||
|
||||
Rule() noexcept;
|
||||
Rule(Rule &&other) noexcept;
|
||||
Rule(const Rule &other);
|
||||
~Rule();
|
||||
|
||||
// since nothing is deleted in the rules vector, these functions do not need to be implemented
|
||||
Rule &operator=(Rule &&other) = delete;
|
||||
Rule &operator=(const Rule &other) = delete;
|
||||
};
|
||||
|
||||
QString name;
|
||||
|
||||
/**
|
||||
* attribute name, to lookup our format
|
||||
*/
|
||||
QString attribute;
|
||||
|
||||
QString lineEndContext;
|
||||
QString lineEmptyContext;
|
||||
QString fallthroughContext;
|
||||
|
||||
std::vector<Rule> rules;
|
||||
|
||||
bool stopEmptyLineContextSwitchLoop = false;
|
||||
bool noIndentationBasedFolding = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2018 Christoph Cullmann <cullmann@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "htmlhighlighter.h"
|
||||
#include "abstracthighlighter_p.h"
|
||||
#include "definition.h"
|
||||
#include "definition_p.h"
|
||||
#include "format.h"
|
||||
#include "ksyntaxhighlighting_logging.h"
|
||||
#include "state.h"
|
||||
#include "theme.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QIODevice>
|
||||
#include <QTextStream>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
class KSyntaxHighlighting::HtmlHighlighterPrivate : public AbstractHighlighterPrivate
|
||||
{
|
||||
public:
|
||||
std::unique_ptr<QTextStream> out;
|
||||
std::unique_ptr<QFile> file;
|
||||
QString currentLine;
|
||||
std::vector<QString> htmlStyles;
|
||||
Theme::EditorColorRole bgRole = Theme::BackgroundColor;
|
||||
};
|
||||
|
||||
HtmlHighlighter::HtmlHighlighter()
|
||||
: AbstractHighlighter(new HtmlHighlighterPrivate())
|
||||
{
|
||||
}
|
||||
|
||||
HtmlHighlighter::~HtmlHighlighter()
|
||||
{
|
||||
}
|
||||
|
||||
void HtmlHighlighter::setBackgroundRole(Theme::EditorColorRole bgRole)
|
||||
{
|
||||
Q_D(HtmlHighlighter);
|
||||
d->bgRole = bgRole;
|
||||
}
|
||||
|
||||
void HtmlHighlighter::setOutputFile(const QString &fileName)
|
||||
{
|
||||
Q_D(HtmlHighlighter);
|
||||
d->file.reset(new QFile(fileName));
|
||||
if (!d->file->open(QFile::WriteOnly | QFile::Truncate)) {
|
||||
qCWarning(Log) << "Failed to open output file" << fileName << ":" << d->file->errorString();
|
||||
return;
|
||||
}
|
||||
d->out.reset(new QTextStream(d->file.get()));
|
||||
d->out->setEncoding(QStringConverter::Utf8);
|
||||
}
|
||||
|
||||
void HtmlHighlighter::setOutputFile(FILE *fileHandle)
|
||||
{
|
||||
Q_D(HtmlHighlighter);
|
||||
d->out.reset(new QTextStream(fileHandle, QIODevice::WriteOnly));
|
||||
d->out->setEncoding(QStringConverter::Utf8);
|
||||
}
|
||||
|
||||
void HtmlHighlighter::highlightFile(const QString &fileName, const QString &title)
|
||||
{
|
||||
QFile f(fileName);
|
||||
if (!f.open(QFile::ReadOnly)) {
|
||||
qCWarning(Log) << "Failed to open input file" << fileName << ":" << f.errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
if (title.isEmpty()) {
|
||||
QFileInfo fi(fileName);
|
||||
highlightData(&f, fi.fileName());
|
||||
} else {
|
||||
highlightData(&f, title);
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
/**
|
||||
* @brief toHtmlRgba
|
||||
* Converts QRgb -> #RRGGBBAA if there is an alpha channel
|
||||
* otherwise it will just return the hexcode. This is because QColor
|
||||
* outputs #AARRGGBB, whereas browser support #RRGGBBAA.
|
||||
*/
|
||||
struct HtmlColor {
|
||||
HtmlColor(QRgb argb)
|
||||
{
|
||||
static const char16_t *digits = u"0123456789abcdef";
|
||||
|
||||
hexcode[0] = u'#';
|
||||
hexcode[1] = digits[qRed(argb) >> 4];
|
||||
hexcode[2] = digits[qRed(argb) & 0xf];
|
||||
hexcode[3] = digits[qGreen(argb) >> 4];
|
||||
hexcode[4] = digits[qGreen(argb) & 0xf];
|
||||
hexcode[5] = digits[qBlue(argb) >> 4];
|
||||
hexcode[6] = digits[qBlue(argb) & 0xf];
|
||||
if (qAlpha(argb) == 0xff) {
|
||||
len = 7;
|
||||
} else {
|
||||
hexcode[7] = digits[qAlpha(argb) >> 4];
|
||||
hexcode[8] = digits[qAlpha(argb) & 0xf];
|
||||
len = 9;
|
||||
}
|
||||
}
|
||||
|
||||
QStringView sv() const
|
||||
{
|
||||
return QStringView(hexcode, len);
|
||||
}
|
||||
|
||||
private:
|
||||
QChar hexcode[9];
|
||||
qsizetype len;
|
||||
};
|
||||
}
|
||||
|
||||
void HtmlHighlighter::highlightData(QIODevice *dev, const QString &title)
|
||||
{
|
||||
Q_D(HtmlHighlighter);
|
||||
|
||||
if (!d->out) {
|
||||
qCWarning(Log) << "No output stream defined!";
|
||||
return;
|
||||
}
|
||||
|
||||
QString htmlTitle;
|
||||
if (title.isEmpty()) {
|
||||
htmlTitle = QStringLiteral("KSyntaxHighlighter");
|
||||
} else {
|
||||
htmlTitle = title.toHtmlEscaped();
|
||||
}
|
||||
|
||||
const auto &theme = d->m_theme;
|
||||
const auto &definition = d->m_definition;
|
||||
const bool useSelectedText = d->bgRole == Theme::TextSelection;
|
||||
|
||||
auto definitions = definition.includedDefinitions();
|
||||
definitions.append(definition);
|
||||
|
||||
const auto mainTextColor = [&] {
|
||||
if (useSelectedText) {
|
||||
const auto fg = theme.selectedTextColor(Theme::Normal);
|
||||
if (fg) {
|
||||
return fg;
|
||||
}
|
||||
}
|
||||
return theme.textColor(Theme::Normal);
|
||||
}();
|
||||
const auto mainBgColor = theme.editorColor(d->bgRole);
|
||||
|
||||
int maxId = 0;
|
||||
for (const auto &definition : std::as_const(definitions)) {
|
||||
for (const auto &format : std::as_const(DefinitionData::get(definition)->formats)) {
|
||||
maxId = qMax(maxId, format.id());
|
||||
}
|
||||
}
|
||||
d->htmlStyles.clear();
|
||||
// htmlStyles must not be empty for applyFormat to work even with a definition without any context
|
||||
d->htmlStyles.resize(maxId + 1);
|
||||
|
||||
// initialize htmlStyles
|
||||
for (const auto &definition : std::as_const(definitions)) {
|
||||
for (const auto &format : std::as_const(DefinitionData::get(definition)->formats)) {
|
||||
auto &buffer = d->htmlStyles[format.id()];
|
||||
|
||||
const auto textColor = useSelectedText ? format.selectedTextColor(theme).rgba() : format.textColor(theme).rgba();
|
||||
if (textColor && textColor != mainTextColor) {
|
||||
buffer += QStringLiteral("color:") + HtmlColor(textColor).sv() + u';';
|
||||
}
|
||||
const auto bgColor = useSelectedText ? format.selectedBackgroundColor(theme).rgba() : format.backgroundColor(theme).rgba();
|
||||
if (bgColor && bgColor != mainBgColor) {
|
||||
buffer += QStringLiteral("background-color:") + HtmlColor(bgColor).sv() + u';';
|
||||
}
|
||||
if (format.isBold(theme)) {
|
||||
buffer += QStringLiteral("font-weight:bold;");
|
||||
}
|
||||
if (format.isItalic(theme)) {
|
||||
buffer += QStringLiteral("font-style:italic;");
|
||||
}
|
||||
if (format.isUnderline(theme)) {
|
||||
buffer += QStringLiteral("text-decoration:underline;");
|
||||
}
|
||||
if (format.isStrikeThrough(theme)) {
|
||||
buffer += QStringLiteral("text-decoration:line-through;");
|
||||
}
|
||||
|
||||
if (!buffer.isEmpty()) {
|
||||
buffer.insert(0, QStringLiteral("<span style=\""));
|
||||
// replace last ';'
|
||||
buffer.back() = u'"';
|
||||
buffer += u'>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
State state;
|
||||
*d->out << "<!DOCTYPE html>\n";
|
||||
*d->out << "<html><head>\n";
|
||||
*d->out << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n";
|
||||
*d->out << "<title>" << htmlTitle << "</title>\n";
|
||||
*d->out << "<meta name=\"generator\" content=\"KF5::SyntaxHighlighting - Definition (" << definition.name() << ") - Theme (" << theme.name() << ")\"/>\n";
|
||||
*d->out << "</head><body";
|
||||
*d->out << " style=\"background-color:" << HtmlColor(mainBgColor).sv();
|
||||
*d->out << ";color:" << HtmlColor(mainTextColor).sv();
|
||||
*d->out << "\"><pre>\n";
|
||||
|
||||
QTextStream in(dev);
|
||||
while (in.readLineInto(&d->currentLine)) {
|
||||
state = highlightLine(d->currentLine, state);
|
||||
*d->out << "\n";
|
||||
}
|
||||
|
||||
*d->out << "</pre></body></html>\n";
|
||||
d->out->flush();
|
||||
|
||||
d->out.reset();
|
||||
d->file.reset();
|
||||
}
|
||||
|
||||
void HtmlHighlighter::applyFormat(int offset, int length, const Format &format)
|
||||
{
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Q_D(HtmlHighlighter);
|
||||
|
||||
auto const &htmlStyle = d->htmlStyles[format.id()];
|
||||
|
||||
if (!htmlStyle.isEmpty()) {
|
||||
*d->out << htmlStyle;
|
||||
}
|
||||
|
||||
for (QChar ch : QStringView(d->currentLine).sliced(offset, length)) {
|
||||
if (ch == u'<')
|
||||
*d->out << QStringLiteral("<");
|
||||
else if (ch == u'&')
|
||||
*d->out << QStringLiteral("&");
|
||||
else
|
||||
*d->out << ch;
|
||||
}
|
||||
|
||||
if (!htmlStyle.isEmpty()) {
|
||||
*d->out << QStringLiteral("</span>");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_HTMLHIGHLIGHTER_H
|
||||
#define KSYNTAXHIGHLIGHTING_HTMLHIGHLIGHTER_H
|
||||
|
||||
#include "abstracthighlighter.h"
|
||||
#include "ksyntaxhighlighting_export.h"
|
||||
#include "theme.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QIODevice;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class HtmlHighlighterPrivate;
|
||||
|
||||
class KSYNTAXHIGHLIGHTING_EXPORT HtmlHighlighter : public AbstractHighlighter
|
||||
{
|
||||
public:
|
||||
HtmlHighlighter();
|
||||
~HtmlHighlighter() override;
|
||||
|
||||
void highlightFile(const QString &fileName, const QString &title = QString());
|
||||
void highlightData(QIODevice *device, const QString &title = QString());
|
||||
|
||||
void setOutputFile(const QString &fileName);
|
||||
void setOutputFile(FILE *fileHandle);
|
||||
|
||||
void setBackgroundRole(Theme::EditorColorRole bgRole);
|
||||
|
||||
protected:
|
||||
void applyFormat(int offset, int length, const Format &format) override;
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE(HtmlHighlighter)
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_HTMLHIGHLIGHTER_H
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "definition_p.h"
|
||||
#include "keywordlist_p.h"
|
||||
#include "ksyntaxhighlighting_logging.h"
|
||||
#include "repository.h"
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct KeywordComparator {
|
||||
Qt::CaseSensitivity caseSensitive;
|
||||
|
||||
bool operator()(QStringView a, QStringView b) const
|
||||
{
|
||||
if (a.size() < b.size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a.size() > b.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return a.compare(b, caseSensitive) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
bool KeywordList::contains(QStringView str, Qt::CaseSensitivity caseSensitive) const
|
||||
{
|
||||
/**
|
||||
* get right vector to search in
|
||||
*/
|
||||
const auto &vectorToSearch = (caseSensitive == Qt::CaseSensitive) ? m_keywordsSortedCaseSensitive : m_keywordsSortedCaseInsensitive;
|
||||
|
||||
/**
|
||||
* search with right predicate
|
||||
*/
|
||||
return std::binary_search(vectorToSearch.begin(), vectorToSearch.end(), str, KeywordComparator{caseSensitive});
|
||||
}
|
||||
|
||||
void KeywordList::load(QXmlStreamReader &reader)
|
||||
{
|
||||
Q_ASSERT(reader.name() == QLatin1String("list"));
|
||||
Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
|
||||
|
||||
m_name = reader.attributes().value(QLatin1String("name")).toString();
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
switch (reader.tokenType()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
if (reader.name() == QLatin1String("item")) {
|
||||
m_keywords.append(reader.readElementText().trimmed());
|
||||
reader.readNextStartElement();
|
||||
break;
|
||||
} else if (reader.name() == QLatin1String("include")) {
|
||||
m_includes.append(reader.readElementText().trimmed());
|
||||
reader.readNextStartElement();
|
||||
break;
|
||||
}
|
||||
reader.readNext();
|
||||
break;
|
||||
case QXmlStreamReader::EndElement:
|
||||
reader.readNext();
|
||||
return;
|
||||
default:
|
||||
reader.readNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeywordList::setCaseSensitivity(Qt::CaseSensitivity caseSensitive)
|
||||
{
|
||||
/**
|
||||
* remember default case-sensitivity and init lookup for it
|
||||
*/
|
||||
m_caseSensitive = caseSensitive;
|
||||
initLookupForCaseSensitivity(m_caseSensitive);
|
||||
}
|
||||
|
||||
void KeywordList::initLookupForCaseSensitivity(Qt::CaseSensitivity caseSensitive)
|
||||
{
|
||||
/**
|
||||
* get right vector to sort, if non-empty, we are done
|
||||
*/
|
||||
auto &vectorToSort = (caseSensitive == Qt::CaseSensitive) ? m_keywordsSortedCaseSensitive : m_keywordsSortedCaseInsensitive;
|
||||
if (!vectorToSort.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* fill vector with refs to keywords
|
||||
*/
|
||||
vectorToSort.assign(m_keywords.constBegin(), m_keywords.constEnd());
|
||||
|
||||
/**
|
||||
* sort with right predicate
|
||||
*/
|
||||
std::sort(vectorToSort.begin(), vectorToSort.end(), KeywordComparator{caseSensitive});
|
||||
}
|
||||
|
||||
void KeywordList::resolveIncludeKeywords(DefinitionData &def)
|
||||
{
|
||||
while (!m_includes.isEmpty()) {
|
||||
const auto kw_include = std::move(m_includes.back());
|
||||
m_includes.pop_back();
|
||||
|
||||
const auto idx = kw_include.indexOf(QLatin1String("##"));
|
||||
KeywordList *keywords = nullptr;
|
||||
|
||||
if (idx >= 0) {
|
||||
auto defName = kw_include.sliced(idx + 2);
|
||||
auto includeDef = def.repo->definitionForName(defName);
|
||||
if (includeDef.isValid()) {
|
||||
auto listName = kw_include.sliced(0, idx);
|
||||
auto defData = DefinitionData::get(includeDef);
|
||||
defData->load(DefinitionData::OnlyKeywords(true));
|
||||
keywords = defData->keywordList(listName);
|
||||
} else {
|
||||
qCWarning(Log) << "Unable to resolve external include keyword for definition" << defName << "in" << def.name;
|
||||
}
|
||||
} else {
|
||||
keywords = def.keywordList(kw_include);
|
||||
}
|
||||
|
||||
if (keywords) {
|
||||
if (this != keywords) {
|
||||
keywords->resolveIncludeKeywords(def);
|
||||
}
|
||||
m_keywords += keywords->m_keywords;
|
||||
} else {
|
||||
qCWarning(Log) << "Unresolved include keyword" << kw_include << "in" << def.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_KEYWORDLIST_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_KEYWORDLIST_P_H
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QStringView>
|
||||
|
||||
#include <vector>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QXmlStreamReader;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class Repository;
|
||||
class DefinitionData;
|
||||
|
||||
class KeywordList
|
||||
{
|
||||
public:
|
||||
KeywordList() = default;
|
||||
~KeywordList() = default;
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return m_keywords.isEmpty();
|
||||
}
|
||||
|
||||
const QString &name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
const QStringList &keywords() const
|
||||
{
|
||||
return m_keywords;
|
||||
}
|
||||
|
||||
Qt::CaseSensitivity caseSensitivity() const
|
||||
{
|
||||
return m_caseSensitive;
|
||||
}
|
||||
|
||||
void setKeywordList(const QStringList &keywords)
|
||||
{
|
||||
m_keywords = keywords;
|
||||
m_keywordsSortedCaseSensitive.clear();
|
||||
m_keywordsSortedCaseInsensitive.clear();
|
||||
initLookupForCaseSensitivity(m_caseSensitive);
|
||||
}
|
||||
|
||||
/** Checks if @p str is a keyword in this list. */
|
||||
bool contains(QStringView str) const
|
||||
{
|
||||
return contains(str, m_caseSensitive);
|
||||
}
|
||||
|
||||
/** Checks if @p str is a keyword in this list, overriding the global case-sensitivity setting. */
|
||||
bool contains(QStringView str, Qt::CaseSensitivity caseSensitive) const;
|
||||
|
||||
void load(QXmlStreamReader &reader);
|
||||
void setCaseSensitivity(Qt::CaseSensitivity caseSensitive);
|
||||
void initLookupForCaseSensitivity(Qt::CaseSensitivity caseSensitive);
|
||||
void resolveIncludeKeywords(DefinitionData &def);
|
||||
|
||||
private:
|
||||
/**
|
||||
* name of keyword list as in XML
|
||||
*/
|
||||
QString m_name;
|
||||
|
||||
/**
|
||||
* raw list of keywords, as seen in XML (but trimmed)
|
||||
*/
|
||||
QStringList m_keywords;
|
||||
|
||||
/**
|
||||
* raw list of include keywords, as seen in XML (but trimmed)
|
||||
*/
|
||||
QStringList m_includes;
|
||||
|
||||
/**
|
||||
* default case-sensitivity setting
|
||||
*/
|
||||
Qt::CaseSensitivity m_caseSensitive = Qt::CaseSensitive;
|
||||
|
||||
/**
|
||||
* case-sensitive sorted string references to m_keywords for lookup
|
||||
*/
|
||||
std::vector<QStringView> m_keywordsSortedCaseSensitive;
|
||||
|
||||
/**
|
||||
* case-insensitive sorted string references to m_keywords for lookup
|
||||
*/
|
||||
std::vector<QStringView> m_keywordsSortedCaseInsensitive;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_KEYWORDLIST_P_H
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_MATCHRESULT_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_MATCHRESULT_P_H
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
/**
|
||||
* Storage for match result of a Rule.
|
||||
* Heavily used internally during highlightLine, therefore completely inline.
|
||||
*/
|
||||
class MatchResult
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Match at given offset found.
|
||||
* @param offset offset of match
|
||||
*/
|
||||
MatchResult(const int offset)
|
||||
: m_offset(offset)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Match at given offset found with additional skip offset.
|
||||
*/
|
||||
explicit MatchResult(const int offset, const int skipOffset)
|
||||
: m_offset(offset)
|
||||
, m_skipOffset(skipOffset)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Match at given offset found with additional captures.
|
||||
* @param offset offset of match
|
||||
* @param captures captures of the match
|
||||
*/
|
||||
explicit MatchResult(const int offset, QStringList &&captures)
|
||||
: m_offset(offset)
|
||||
, m_captures(std::move(captures))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset of the match
|
||||
* @return offset of the match
|
||||
*/
|
||||
int offset() const
|
||||
{
|
||||
return m_offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip offset of the match
|
||||
* @return skip offset of the match, no match possible until this offset is reached
|
||||
*/
|
||||
int skipOffset() const
|
||||
{
|
||||
return m_skipOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures of the match.
|
||||
* @return captured text of this match
|
||||
*/
|
||||
QStringList &captures()
|
||||
{
|
||||
return m_captures;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* match offset, filled in all constructors
|
||||
*/
|
||||
int m_offset;
|
||||
|
||||
/**
|
||||
* skip offset, optional
|
||||
*/
|
||||
int m_skipOffset = 0;
|
||||
|
||||
/**
|
||||
* captures, optional
|
||||
*/
|
||||
QStringList m_captures;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_MATCHRESULT_P_H
|
||||
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "repository.h"
|
||||
#include "definition.h"
|
||||
#include "definition_p.h"
|
||||
#include "repository_p.h"
|
||||
#include "theme.h"
|
||||
#include "themedata_p.h"
|
||||
#include "wildcardmatcher.h"
|
||||
|
||||
#include <QCborMap>
|
||||
#include <QCborValue>
|
||||
#include <QDirIterator>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QPalette>
|
||||
#include <QString>
|
||||
#include <QStringView>
|
||||
|
||||
#ifndef NO_STANDARD_PATHS
|
||||
#include <QStandardPaths>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
namespace
|
||||
{
|
||||
QString fileNameFromFilePath(const QString &filePath)
|
||||
{
|
||||
return QFileInfo{filePath}.fileName();
|
||||
}
|
||||
|
||||
auto anyWildcardMatches(QStringView str)
|
||||
{
|
||||
return [str](const Definition &def) {
|
||||
const auto strings = def.extensions();
|
||||
return std::any_of(strings.cbegin(), strings.cend(), [str](QStringView wildcard) {
|
||||
return WildcardMatcher::exactMatch(str, wildcard);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
auto anyMimeTypeEquals(QStringView mimeTypeName)
|
||||
{
|
||||
return [mimeTypeName](const Definition &def) {
|
||||
const auto strings = def.mimeTypes();
|
||||
return std::any_of(strings.cbegin(), strings.cend(), [mimeTypeName](QStringView name) {
|
||||
return mimeTypeName == name;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// The two function templates below take defs - a map sorted by highlighting name - to be deterministic and independent of translations.
|
||||
|
||||
template<typename UnaryPredicate>
|
||||
Definition findHighestPriorityDefinitionIf(const QList<Definition> &defs, UnaryPredicate predicate)
|
||||
{
|
||||
const Definition *match = nullptr;
|
||||
auto matchPriority = std::numeric_limits<int>::lowest();
|
||||
for (const Definition &def : defs) {
|
||||
const auto defPriority = def.priority();
|
||||
if (defPriority > matchPriority && predicate(def)) {
|
||||
match = &def;
|
||||
matchPriority = defPriority;
|
||||
}
|
||||
}
|
||||
return match == nullptr ? Definition{} : *match;
|
||||
}
|
||||
|
||||
template<typename UnaryPredicate>
|
||||
QList<Definition> findDefinitionsIf(const QList<Definition> &defs, UnaryPredicate predicate)
|
||||
{
|
||||
QList<Definition> matches;
|
||||
std::copy_if(defs.cbegin(), defs.cend(), std::back_inserter(matches), predicate);
|
||||
std::stable_sort(matches.begin(), matches.end(), [](const Definition &lhs, const Definition &rhs) {
|
||||
return lhs.priority() > rhs.priority();
|
||||
});
|
||||
return matches;
|
||||
}
|
||||
} // unnamed namespace
|
||||
|
||||
static void initResource()
|
||||
{
|
||||
#ifdef HAS_SYNTAX_RESOURCE
|
||||
Q_INIT_RESOURCE(syntax_data);
|
||||
#endif
|
||||
Q_INIT_RESOURCE(theme_data);
|
||||
}
|
||||
|
||||
RepositoryPrivate *RepositoryPrivate::get(Repository *repo)
|
||||
{
|
||||
return repo->d.get();
|
||||
}
|
||||
|
||||
Repository::Repository()
|
||||
: d(new RepositoryPrivate)
|
||||
{
|
||||
initResource();
|
||||
d->load(this);
|
||||
}
|
||||
|
||||
Repository::~Repository()
|
||||
{
|
||||
// reset repo so we can detect in still alive definition instances
|
||||
// that the repo was deleted
|
||||
for (const auto &def : std::as_const(d->m_flatDefs)) {
|
||||
DefinitionData::get(def)->repo = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Definition Repository::definitionForName(const QString &defName) const
|
||||
{
|
||||
return d->m_fullDefs.value(defName.toLower());
|
||||
}
|
||||
|
||||
Definition Repository::definitionForFileName(const QString &fileName) const
|
||||
{
|
||||
return findHighestPriorityDefinitionIf(d->m_flatDefs, anyWildcardMatches(fileNameFromFilePath(fileName)));
|
||||
}
|
||||
|
||||
QList<Definition> Repository::definitionsForFileName(const QString &fileName) const
|
||||
{
|
||||
return findDefinitionsIf(d->m_flatDefs, anyWildcardMatches(fileNameFromFilePath(fileName)));
|
||||
}
|
||||
|
||||
Definition Repository::definitionForMimeType(const QString &mimeType) const
|
||||
{
|
||||
return findHighestPriorityDefinitionIf(d->m_flatDefs, anyMimeTypeEquals(mimeType));
|
||||
}
|
||||
|
||||
QList<Definition> Repository::definitionsForMimeType(const QString &mimeType) const
|
||||
{
|
||||
return findDefinitionsIf(d->m_flatDefs, anyMimeTypeEquals(mimeType));
|
||||
}
|
||||
|
||||
QList<Definition> Repository::definitions() const
|
||||
{
|
||||
return d->m_sortedDefs;
|
||||
}
|
||||
|
||||
QList<Theme> Repository::themes() const
|
||||
{
|
||||
return d->m_themes;
|
||||
}
|
||||
|
||||
static auto lowerBoundTheme(const QList<KSyntaxHighlighting::Theme> &themes, QStringView themeName)
|
||||
{
|
||||
return std::lower_bound(themes.begin(), themes.end(), themeName, [](const Theme &lhs, QStringView rhs) {
|
||||
return lhs.name() < rhs;
|
||||
});
|
||||
}
|
||||
|
||||
Theme Repository::theme(const QString &themeName) const
|
||||
{
|
||||
const auto &themes = d->m_themes;
|
||||
const auto it = lowerBoundTheme(themes, themeName);
|
||||
if (it != themes.end() && (*it).name() == themeName) {
|
||||
return *it;
|
||||
}
|
||||
return Theme();
|
||||
}
|
||||
|
||||
Theme Repository::defaultTheme(Repository::DefaultTheme t) const
|
||||
{
|
||||
if (t == DarkTheme) {
|
||||
return theme(QStringLiteral("Breeze Dark"));
|
||||
}
|
||||
return theme(QStringLiteral("Breeze Light"));
|
||||
}
|
||||
|
||||
Theme Repository::themeForPalette(const QPalette &palette) const
|
||||
{
|
||||
const auto base = palette.color(QPalette::Base);
|
||||
const auto highlight = palette.color(QPalette::Highlight).rgb();
|
||||
|
||||
// find themes with matching background and highlight colors
|
||||
const Theme *firstMatchingTheme = nullptr;
|
||||
for (const auto &theme : std::as_const(d->m_themes)) {
|
||||
const auto background = theme.editorColor(Theme::EditorColorRole::BackgroundColor);
|
||||
if (background == base.rgb()) {
|
||||
// find theme with a matching highlight color
|
||||
auto selection = theme.editorColor(Theme::EditorColorRole::TextSelection);
|
||||
if (selection == highlight) {
|
||||
return theme;
|
||||
}
|
||||
if (!firstMatchingTheme) {
|
||||
firstMatchingTheme = &theme;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (firstMatchingTheme) {
|
||||
return *firstMatchingTheme;
|
||||
}
|
||||
|
||||
// fallback to just use the default light or dark theme
|
||||
return defaultTheme((base.lightness() < 128) ? Repository::DarkTheme : Repository::LightTheme);
|
||||
}
|
||||
|
||||
void RepositoryPrivate::load(Repository *repo)
|
||||
{
|
||||
// always add invalid default "None" highlighting
|
||||
m_defs.emplace(QString(), Definition());
|
||||
|
||||
// do lookup in standard paths, if not disabled
|
||||
#ifndef NO_STANDARD_PATHS
|
||||
// do lookup in installed path when has no syntax resource
|
||||
#ifndef HAS_SYNTAX_RESOURCE
|
||||
for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
|
||||
QStringLiteral("org.kde.syntax-highlighting/syntax-bundled"),
|
||||
QStandardPaths::LocateDirectory)) {
|
||||
if (!loadSyntaxFolderFromIndex(repo, dir)) {
|
||||
loadSyntaxFolder(repo, dir);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
|
||||
QStringLiteral("org.kde.syntax-highlighting/syntax"),
|
||||
QStandardPaths::LocateDirectory)) {
|
||||
loadSyntaxFolder(repo, dir);
|
||||
}
|
||||
#endif
|
||||
|
||||
// default resources are always used, this is the one location that has a index cbor file
|
||||
loadSyntaxFolderFromIndex(repo, QStringLiteral(":/org.kde.syntax-highlighting/syntax"));
|
||||
|
||||
// extra resources provided by 3rdparty libraries/applications
|
||||
loadSyntaxFolder(repo, QStringLiteral(":/org.kde.syntax-highlighting/syntax-addons"));
|
||||
|
||||
// user given extra paths
|
||||
for (const auto &path : std::as_const(m_customSearchPaths)) {
|
||||
loadSyntaxFolder(repo, path + QStringLiteral("/syntax"));
|
||||
}
|
||||
|
||||
computeAlternativeDefLists();
|
||||
|
||||
// load themes
|
||||
|
||||
// do lookup in standard paths, if not disabled
|
||||
#ifndef NO_STANDARD_PATHS
|
||||
for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
|
||||
QStringLiteral("org.kde.syntax-highlighting/themes"),
|
||||
QStandardPaths::LocateDirectory)) {
|
||||
loadThemeFolder(dir);
|
||||
}
|
||||
#endif
|
||||
|
||||
// default resources are always used
|
||||
loadThemeFolder(QStringLiteral(":/org.kde.syntax-highlighting/themes"));
|
||||
|
||||
// extra resources provided by 3rdparty libraries/applications
|
||||
loadThemeFolder(QStringLiteral(":/org.kde.syntax-highlighting/themes-addons"));
|
||||
|
||||
// user given extra paths
|
||||
for (const auto &path : std::as_const(m_customSearchPaths)) {
|
||||
loadThemeFolder(path + QStringLiteral("/themes"));
|
||||
}
|
||||
}
|
||||
|
||||
void RepositoryPrivate::computeAlternativeDefLists()
|
||||
{
|
||||
m_flatDefs.clear();
|
||||
m_flatDefs.reserve(m_defs.size());
|
||||
for (const auto &[_, def] : m_defs) {
|
||||
m_flatDefs.push_back(def);
|
||||
}
|
||||
|
||||
m_sortedDefs = m_flatDefs;
|
||||
std::sort(m_sortedDefs.begin(), m_sortedDefs.end(), [](const Definition &left, const Definition &right) {
|
||||
auto comparison = left.translatedSection().compare(right.translatedSection(), Qt::CaseInsensitive);
|
||||
if (comparison == 0) {
|
||||
comparison = left.translatedName().compare(right.translatedName(), Qt::CaseInsensitive);
|
||||
}
|
||||
return comparison < 0;
|
||||
});
|
||||
|
||||
m_fullDefs.clear();
|
||||
for (const auto &def : std::as_const(m_sortedDefs)) {
|
||||
m_fullDefs.insert(def.name().toLower(), def);
|
||||
const auto &alternativeNames = def.alternativeNames();
|
||||
for (const auto &altName : alternativeNames) {
|
||||
m_fullDefs.insert(altName.toLower(), def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RepositoryPrivate::loadSyntaxFolder(Repository *repo, const QString &path)
|
||||
{
|
||||
QDirIterator it(path, QStringList() << QLatin1String("*.xml"), QDir::Files);
|
||||
while (it.hasNext()) {
|
||||
Definition def;
|
||||
auto defData = DefinitionData::get(def);
|
||||
defData->repo = repo;
|
||||
if (defData->loadMetaData(it.next())) {
|
||||
addDefinition(std::move(def));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RepositoryPrivate::loadSyntaxFolderFromIndex(Repository *repo, const QString &path)
|
||||
{
|
||||
QFile indexFile(path + QLatin1String("/index.katesyntax"));
|
||||
if (!indexFile.open(QFile::ReadOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto indexDoc(QCborValue::fromCbor(indexFile.readAll()));
|
||||
const auto index = indexDoc.toMap();
|
||||
for (auto it = index.begin(); it != index.end(); ++it) {
|
||||
if (!it.value().isMap()) {
|
||||
continue;
|
||||
}
|
||||
const auto fileName = QString(path + QLatin1Char('/') + it.key().toString());
|
||||
const auto defMap = it.value().toMap();
|
||||
Definition def;
|
||||
auto defData = DefinitionData::get(def);
|
||||
defData->repo = repo;
|
||||
if (defData->loadMetaData(fileName, defMap)) {
|
||||
addDefinition(std::move(def));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RepositoryPrivate::addDefinition(Definition &&def)
|
||||
{
|
||||
const auto [it, inserted] = m_defs.try_emplace(def.name(), std::move(def));
|
||||
if (inserted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (it->second.version() >= def.version()) {
|
||||
return;
|
||||
}
|
||||
it->second = std::move(def);
|
||||
}
|
||||
|
||||
void RepositoryPrivate::loadThemeFolder(const QString &path)
|
||||
{
|
||||
QDirIterator it(path, QStringList() << QLatin1String("*.theme"), QDir::Files);
|
||||
while (it.hasNext()) {
|
||||
auto themeData = std::unique_ptr<ThemeData>(new ThemeData);
|
||||
if (themeData->load(it.next())) {
|
||||
addTheme(Theme(themeData.release()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int themeRevision(const Theme &theme)
|
||||
{
|
||||
auto data = ThemeData::get(theme);
|
||||
return data->revision();
|
||||
}
|
||||
|
||||
void RepositoryPrivate::addTheme(const Theme &theme)
|
||||
{
|
||||
const auto &constThemes = m_themes;
|
||||
const auto themeName = theme.name();
|
||||
const auto constIt = lowerBoundTheme(constThemes, themeName);
|
||||
const auto index = constIt - constThemes.begin();
|
||||
if (constIt == constThemes.end() || (*constIt).name() != themeName) {
|
||||
m_themes.insert(index, theme);
|
||||
return;
|
||||
}
|
||||
if (themeRevision(*constIt) < themeRevision(theme)) {
|
||||
m_themes[index] = theme;
|
||||
}
|
||||
}
|
||||
|
||||
int RepositoryPrivate::foldingRegionId(const QString &defName, const QString &foldName)
|
||||
{
|
||||
const auto it = m_foldingRegionIds.constFind(qMakePair(defName, foldName));
|
||||
if (it != m_foldingRegionIds.constEnd()) {
|
||||
return it.value();
|
||||
}
|
||||
Q_ASSERT(m_foldingRegionId < std::numeric_limits<int>::max());
|
||||
m_foldingRegionIds.insert(qMakePair(defName, foldName), ++m_foldingRegionId);
|
||||
return m_foldingRegionId;
|
||||
}
|
||||
|
||||
int RepositoryPrivate::nextFormatId()
|
||||
{
|
||||
Q_ASSERT(m_formatId < std::numeric_limits<int>::max());
|
||||
return ++m_formatId;
|
||||
}
|
||||
|
||||
void Repository::reload()
|
||||
{
|
||||
Q_EMIT aboutToReload();
|
||||
|
||||
for (const auto &def : std::as_const(d->m_flatDefs)) {
|
||||
DefinitionData::get(def)->clear();
|
||||
}
|
||||
d->m_defs.clear();
|
||||
d->m_flatDefs.clear();
|
||||
d->m_sortedDefs.clear();
|
||||
d->m_fullDefs.clear();
|
||||
|
||||
d->m_themes.clear();
|
||||
|
||||
d->m_foldingRegionId = 0;
|
||||
d->m_foldingRegionIds.clear();
|
||||
|
||||
d->m_formatId = 0;
|
||||
|
||||
d->load(this);
|
||||
|
||||
Q_EMIT reloaded();
|
||||
}
|
||||
|
||||
void Repository::addCustomSearchPath(const QString &path)
|
||||
{
|
||||
Q_EMIT aboutToReload();
|
||||
|
||||
d->m_customSearchPaths.append(path);
|
||||
d->loadThemeFolder(path + QStringLiteral("/themes"));
|
||||
d->loadSyntaxFolder(this, path + QStringLiteral("/syntax"));
|
||||
d->computeAlternativeDefLists();
|
||||
|
||||
Q_EMIT reloaded();
|
||||
}
|
||||
|
||||
QList<QString> Repository::customSearchPaths() const
|
||||
{
|
||||
return d->m_customSearchPaths;
|
||||
}
|
||||
|
||||
#include "moc_repository.cpp"
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_REPOSITORY_H
|
||||
#define KSYNTAXHIGHLIGHTING_REPOSITORY_H
|
||||
|
||||
#include "ksyntaxhighlighting_export.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <memory>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QString;
|
||||
class QPalette;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
/**
|
||||
* @namespace KSyntaxHighlighting
|
||||
*
|
||||
* Syntax highlighting engine for Kate syntax definitions.
|
||||
* In order to access the syntax highlighting Definition files, use the
|
||||
* class Repository.
|
||||
*
|
||||
* @see Repository
|
||||
*/
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class Definition;
|
||||
class RepositoryPrivate;
|
||||
class Theme;
|
||||
|
||||
/**
|
||||
* @brief Syntax highlighting repository.
|
||||
*
|
||||
* @section repo_intro Introduction
|
||||
*
|
||||
* The Repository gives access to all syntax Definitions available on the
|
||||
* system, typically described in *.xml files. The Definition files are read
|
||||
* from the resource that is compiled into the executable, and from the file
|
||||
* system. If a Definition exists in the resource and on the file system,
|
||||
* then the one from the file system is chosen.
|
||||
*
|
||||
* @section repo_access Definitions and Themes
|
||||
*
|
||||
* Typically, only one instance of the Repository is needed. This single
|
||||
* instance can be thought of as a singleton you keep alive throughout the
|
||||
* lifetime of your application. Then, either call definitionForName() with the
|
||||
* given language name (e.g. "QML" or "Java"), or definitionForFileName() to
|
||||
* obtain a Definition based on the filename/mimetype of the file. The
|
||||
* function definitions() returns a list of all available syntax Definition%s.
|
||||
*
|
||||
* In addition to Definitions, the Repository also provides a list of Themes.
|
||||
* A Theme is defined by a set of default text style colors as well as editor
|
||||
* colors. These colors together provide all required colros for drawing all
|
||||
* primitives of a text editor. All available Theme%s can be queried through
|
||||
* themes(), and a Theme with a specific name is obtained through theme().
|
||||
* Additionally, defaultTheme() provides a way to obtain a default theme for
|
||||
* either a light or a black color theme.
|
||||
*
|
||||
* @section repo_search_paths Search Paths
|
||||
*
|
||||
* All highlighting Definition and Theme files are compiled into the shared
|
||||
* KSyntaxHighlighting library by using the Qt resource system. Loading
|
||||
* additional files from disk is supported as well.
|
||||
*
|
||||
* Loading syntax Definition files works as follows:
|
||||
*
|
||||
* -# First, all syntax highlighting files are loaded that are located in
|
||||
* QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("org.kde.syntax-highlighting/syntax"), QStandardPaths::LocateDirectory);
|
||||
* Under Unix, this uses $XDG_DATA_HOME and $XDG_DATA_DIRS, which could
|
||||
* map to $HOME/.local5/share/org.kde.syntax-highlighting/syntax and
|
||||
* /usr/share/org.kde.syntax-highlighting/syntax.
|
||||
*
|
||||
* -# Then, all files compiled into the library through resources are loaded.
|
||||
* The internal resource path is ":/org.kde.syntax-highlighting/syntax".
|
||||
* This path should never be touched by other applications.
|
||||
*
|
||||
* -# Then, all custom files compiled into resources are loaded.
|
||||
* The resource path is ":/org.kde.syntax-highlighting/syntax-addons".
|
||||
* This path can be used by other libraries/applications to bundle specialized definitions.
|
||||
* Per default this path isn't used by the framework itself.
|
||||
*
|
||||
* -# Finally, the search path can be extended by calling addCustomSearchPath().
|
||||
* A custom search path can either be a path on disk or again a path to
|
||||
* a Qt resource.
|
||||
*
|
||||
* Similarly, loading Theme files works as follows:
|
||||
*
|
||||
* -# First, all Theme files are loaded that are located in
|
||||
* QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("org.kde.syntax-highlighting/themes"), QStandardPaths::LocateDirectory);
|
||||
* Under Unix, this uses $XDG_DATA_HOME and $XDG_DATA_DIRS, which could
|
||||
* map to $HOME/.local5/share/org.kde.syntax-highlighting/themes and
|
||||
* /usr/share/org.kde.syntax-highlighting/themes.
|
||||
*
|
||||
* -# Then, all files compiled into the library through resources are loaded.
|
||||
* The internal resource path is ":/org.kde.syntax-highlighting/themes".
|
||||
* This path should never be touched by other applications.
|
||||
*
|
||||
* -# Then, all custom files compiled into resources are loaded.
|
||||
* The resource path is ":/org.kde.syntax-highlighting/themes-addons".
|
||||
* This path can be used by other libraries/applications to bundle specialized themes.
|
||||
* Per default this path isn't used by the framework itself.
|
||||
*
|
||||
* -# Finally, all Theme%s located in the paths added addCustomSearchPath()
|
||||
* are loaded.
|
||||
*
|
||||
* @note Whenever a Definition or a Theme exists twice, the variant with
|
||||
* higher version is used.
|
||||
*
|
||||
* @note The QStandardPaths lookup can be disabled by compiling the framework with the -DNO_STANDARD_PATHS define.
|
||||
*
|
||||
* @see Definition, Theme, AbstractHighlighter
|
||||
* @since 5.28
|
||||
*/
|
||||
class KSYNTAXHIGHLIGHTING_EXPORT Repository : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QList<KSyntaxHighlighting::Definition> definitions READ definitions NOTIFY reloaded)
|
||||
Q_PROPERTY(QList<KSyntaxHighlighting::Theme> themes READ themes NOTIFY reloaded)
|
||||
|
||||
public:
|
||||
/**
|
||||
* Create a new syntax definition repository.
|
||||
* This will read the meta data information of all available syntax
|
||||
* definition, which is a moderately expensive operation, it's therefore
|
||||
* recommended to keep a single instance of Repository around as long
|
||||
* as you need highlighting in your application.
|
||||
*/
|
||||
Repository();
|
||||
~Repository();
|
||||
|
||||
/**
|
||||
* Returns the Definition named @p defName.
|
||||
*
|
||||
* If no Definition is found, Definition::isValid() of the returned instance
|
||||
* returns false.
|
||||
*
|
||||
* @note The search is @e Qt::CaseInsensitive using untranslated names or
|
||||
* alternative names. For instance, the cpp.xml definition file sets
|
||||
* its name to @e C++ with an alternative name of @e CPP. Therefore, the
|
||||
* strings "C++", "c++", "CPP" and "cpp" will all return a valid
|
||||
* Definition file.
|
||||
*/
|
||||
Q_INVOKABLE KSyntaxHighlighting::Definition definitionForName(const QString &defName) const;
|
||||
|
||||
/**
|
||||
* Returns the best matching Definition for the file named @p fileName.
|
||||
* The match is performed based on the \e extensions and @e mimetype of
|
||||
* the definition files. If multiple matches are found, the one with the
|
||||
* highest priority is returned.
|
||||
*
|
||||
* If no match is found, Definition::isValid() of the returned instance
|
||||
* returns false.
|
||||
*/
|
||||
Q_INVOKABLE KSyntaxHighlighting::Definition definitionForFileName(const QString &fileName) const;
|
||||
|
||||
/**
|
||||
* Returns all Definition%s for the file named @p fileName sorted by priority.
|
||||
* The match is performed based on the \e extensions and @e mimetype of
|
||||
* the definition files.
|
||||
*
|
||||
* @since 5.56
|
||||
*/
|
||||
Q_INVOKABLE QList<KSyntaxHighlighting::Definition> definitionsForFileName(const QString &fileName) const;
|
||||
|
||||
/**
|
||||
* Returns the best matching Definition to the type named @p mimeType
|
||||
*
|
||||
* If no match is found, Definition::isValid() of the returned instance
|
||||
* returns false.
|
||||
*
|
||||
* @since 5.50
|
||||
*/
|
||||
Q_INVOKABLE KSyntaxHighlighting::Definition definitionForMimeType(const QString &mimeType) const;
|
||||
|
||||
/**
|
||||
* Returns all Definition%s to the type named @p mimeType sorted by priority
|
||||
*
|
||||
* @since 5.56
|
||||
*/
|
||||
Q_INVOKABLE QList<KSyntaxHighlighting::Definition> definitionsForMimeType(const QString &mimeType) const;
|
||||
|
||||
/**
|
||||
* Returns all available Definition%s.
|
||||
* Definition%ss are ordered by translated section and translated names,
|
||||
* for consistent displaying.
|
||||
*/
|
||||
Q_INVOKABLE QList<KSyntaxHighlighting::Definition> definitions() const;
|
||||
|
||||
/**
|
||||
* Returns all available color themes.
|
||||
* The returned list should never be empty.
|
||||
*/
|
||||
Q_INVOKABLE QList<KSyntaxHighlighting::Theme> themes() const;
|
||||
|
||||
/**
|
||||
* Returns the theme called @p themeName.
|
||||
* If the requested theme cannot be found, the retunred Theme is invalid,
|
||||
* see Theme::isValid().
|
||||
*/
|
||||
Q_INVOKABLE KSyntaxHighlighting::Theme theme(const QString &themeName) const;
|
||||
|
||||
/**
|
||||
* Built-in default theme types.
|
||||
* @see defaultTheme()
|
||||
*/
|
||||
enum DefaultTheme {
|
||||
//! Theme with a light background color.
|
||||
LightTheme,
|
||||
//! Theme with a dark background color.
|
||||
DarkTheme
|
||||
};
|
||||
Q_ENUM(DefaultTheme)
|
||||
|
||||
/**
|
||||
* Returns a default theme instance of the given type.
|
||||
* The returned Theme is guaranteed to be a valid theme.
|
||||
* @since 5.79
|
||||
*/
|
||||
Q_INVOKABLE KSyntaxHighlighting::Theme defaultTheme(DefaultTheme t = LightTheme) const;
|
||||
|
||||
/**
|
||||
* Returns the best matching theme for the given palette
|
||||
* @since 5.79
|
||||
**/
|
||||
Theme themeForPalette(const QPalette &palette) const;
|
||||
|
||||
/**
|
||||
* Reloads the repository.
|
||||
* This is a moderately expensive operations and should thus only be
|
||||
* triggered when the installed syntax definition files changed.
|
||||
*/
|
||||
void reload();
|
||||
|
||||
/**
|
||||
* Add a custom search path to the repository.
|
||||
* This path will be searched in addition to the usual locations for syntax
|
||||
* and theme definition files. Both locations on disk as well as Qt
|
||||
* resource paths are supported.
|
||||
*
|
||||
* @note Internally, the two sub-folders @p path/syntax as well as
|
||||
* @p path/themes are searched for additional Definition%s and
|
||||
* Theme%s. Do not append @e syntax or @e themes to @p path
|
||||
* yourself.
|
||||
*
|
||||
* @note Calling this triggers a reload() of the repository.
|
||||
*
|
||||
* @since 5.39
|
||||
*/
|
||||
void addCustomSearchPath(const QString &path);
|
||||
|
||||
/**
|
||||
* Returns the list of custom search paths added to the repository.
|
||||
* By default, this list is empty.
|
||||
*
|
||||
* @see addCustomSearchPath()
|
||||
* @since 5.39
|
||||
*/
|
||||
QList<QString> customSearchPaths() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* This signal is emitted before the reload is started.
|
||||
* @since 6.0
|
||||
*/
|
||||
void aboutToReload();
|
||||
|
||||
/**
|
||||
* This signal is emitted when the reload is finished.
|
||||
* @since 6.0
|
||||
*/
|
||||
void reloaded();
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(Repository)
|
||||
friend class RepositoryPrivate;
|
||||
std::unique_ptr<RepositoryPrivate> d;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_REPOSITORY_H
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_REPOSITORY_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_REPOSITORY_P_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "dynamicregexpcache_p.h"
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class Definition;
|
||||
class Repository;
|
||||
class Theme;
|
||||
|
||||
class RepositoryPrivate
|
||||
{
|
||||
public:
|
||||
RepositoryPrivate() = default;
|
||||
|
||||
static RepositoryPrivate *get(Repository *repo);
|
||||
|
||||
void load(Repository *repo);
|
||||
void loadSyntaxFolder(Repository *repo, const QString &path);
|
||||
bool loadSyntaxFolderFromIndex(Repository *repo, const QString &path);
|
||||
void computeAlternativeDefLists();
|
||||
|
||||
void addDefinition(Definition &&def);
|
||||
|
||||
void loadThemeFolder(const QString &path);
|
||||
void addTheme(const Theme &theme);
|
||||
|
||||
int foldingRegionId(const QString &defName, const QString &foldName);
|
||||
int nextFormatId();
|
||||
|
||||
QList<QString> m_customSearchPaths;
|
||||
|
||||
// sorted map to have deterministic iteration order
|
||||
std::map<QString, Definition> m_defs;
|
||||
// flat version of m_defs for speed up iterations for e.g. definitionsForFileName
|
||||
QList<Definition> m_flatDefs;
|
||||
|
||||
// map relating all names and alternative names, case insensitively to the correct definition.
|
||||
QHash<QString, Definition> m_fullDefs;
|
||||
|
||||
// this vector is sorted by translated sections/names
|
||||
QList<Definition> m_sortedDefs;
|
||||
|
||||
QList<Theme> m_themes;
|
||||
|
||||
QHash<QPair<QString, QString>, int> m_foldingRegionIds;
|
||||
int m_foldingRegionId = 0;
|
||||
int m_formatId = 0;
|
||||
|
||||
DynamicRegexpCache m_dynamicRegexpCache;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,754 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2018 Christoph Cullmann <cullmann@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen+kde@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "context_p.h"
|
||||
#include "definition_p.h"
|
||||
#include "dynamicregexpcache_p.h"
|
||||
#include "ksyntaxhighlighting_logging.h"
|
||||
#include "rule_p.h"
|
||||
#include "worddelimiters_p.h"
|
||||
#include "xml_p.h"
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
// QChar::isDigit() match any digit in unicode (romain numeral, etc)
|
||||
static bool isDigit(QChar c)
|
||||
{
|
||||
return (c <= QLatin1Char('9') && QLatin1Char('0') <= c);
|
||||
}
|
||||
|
||||
static bool isOctalChar(QChar c)
|
||||
{
|
||||
return (c <= QLatin1Char('7') && QLatin1Char('0') <= c);
|
||||
}
|
||||
|
||||
static bool isHexChar(QChar c)
|
||||
{
|
||||
return isDigit(c) || (c <= QLatin1Char('f') && QLatin1Char('a') <= c) || (c <= QLatin1Char('F') && QLatin1Char('A') <= c);
|
||||
}
|
||||
|
||||
static int matchEscapedChar(QStringView text, int offset)
|
||||
{
|
||||
if (text.at(offset) != QLatin1Char('\\') || text.size() < offset + 2) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
const auto c = text.at(offset + 1);
|
||||
switch (c.unicode()) {
|
||||
// control chars
|
||||
case 'a':
|
||||
case 'b':
|
||||
case 'e':
|
||||
case 'f':
|
||||
case 'n':
|
||||
case 'r':
|
||||
case 't':
|
||||
case 'v':
|
||||
case '"':
|
||||
case '\'':
|
||||
case '?':
|
||||
case '\\':
|
||||
return offset + 2;
|
||||
|
||||
// hex encoded character
|
||||
case 'x':
|
||||
if (offset + 2 < text.size() && isHexChar(text.at(offset + 2))) {
|
||||
if (offset + 3 < text.size() && isHexChar(text.at(offset + 3))) {
|
||||
return offset + 4;
|
||||
}
|
||||
return offset + 3;
|
||||
}
|
||||
return offset;
|
||||
|
||||
// octal encoding, simple \0 is OK, too, unlike simple \x above
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
if (offset + 2 < text.size() && isOctalChar(text.at(offset + 2))) {
|
||||
if (offset + 3 < text.size() && isOctalChar(text.at(offset + 3))) {
|
||||
return offset + 4;
|
||||
}
|
||||
return offset + 3;
|
||||
}
|
||||
return offset + 2;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static QString replaceCaptures(const QString &pattern, const QStringList &captures, bool quote)
|
||||
{
|
||||
auto result = pattern;
|
||||
for (int i = captures.size(); i >= 1; --i) {
|
||||
result.replace(QLatin1Char('%') + QString::number(i), quote ? QRegularExpression::escape(captures.at(i - 1)) : captures.at(i - 1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static MatchResult matchString(QStringView pattern, QStringView text, int offset, Qt::CaseSensitivity caseSensitivity)
|
||||
{
|
||||
if (offset + pattern.size() <= text.size() && text.sliced(offset, pattern.size()).compare(pattern, caseSensitivity) == 0) {
|
||||
return offset + pattern.size();
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
static void resolveAdditionalWordDelimiters(WordDelimiters &wordDelimiters, const HighlightingContextData::Rule::WordDelimiters &delimiters)
|
||||
{
|
||||
// cache for DefinitionData::wordDelimiters, is accessed VERY often
|
||||
if (!delimiters.additionalDeliminator.isEmpty() || !delimiters.weakDeliminator.isEmpty()) {
|
||||
wordDelimiters.append(QStringView(delimiters.additionalDeliminator));
|
||||
wordDelimiters.remove(QStringView(delimiters.weakDeliminator));
|
||||
}
|
||||
}
|
||||
|
||||
Rule::~Rule() = default;
|
||||
|
||||
const IncludeRules *Rule::castToIncludeRules() const
|
||||
{
|
||||
if (m_type != Type::IncludeRules) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<const IncludeRules *>(this);
|
||||
}
|
||||
|
||||
bool Rule::resolveCommon(DefinitionData &def, const HighlightingContextData::Rule &ruleData, QStringView lookupContextName)
|
||||
{
|
||||
switch (ruleData.type) {
|
||||
// IncludeRules uses this with a different semantic
|
||||
case HighlightingContextData::Rule::Type::IncludeRules:
|
||||
m_type = Type::IncludeRules;
|
||||
return true;
|
||||
case HighlightingContextData::Rule::Type::LineContinue:
|
||||
m_type = Type::LineContinue;
|
||||
break;
|
||||
default:
|
||||
m_type = Type::OtherRule;
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* try to get our format from the definition we stem from
|
||||
*/
|
||||
if (!ruleData.common.attributeName.isEmpty()) {
|
||||
m_attributeFormat = def.formatByName(ruleData.common.attributeName);
|
||||
if (!m_attributeFormat.isValid()) {
|
||||
qCWarning(Log) << "Rule: Unknown format" << ruleData.common.attributeName << "in context" << lookupContextName << "of definition" << def.name;
|
||||
}
|
||||
}
|
||||
|
||||
m_firstNonSpace = ruleData.common.firstNonSpace;
|
||||
m_lookAhead = ruleData.common.lookAhead;
|
||||
m_column = ruleData.common.column;
|
||||
|
||||
if (!ruleData.common.beginRegionName.isEmpty()) {
|
||||
m_beginRegion = FoldingRegion(FoldingRegion::Begin, def.foldingRegionId(ruleData.common.beginRegionName));
|
||||
}
|
||||
if (!ruleData.common.endRegionName.isEmpty()) {
|
||||
m_endRegion = FoldingRegion(FoldingRegion::End, def.foldingRegionId(ruleData.common.endRegionName));
|
||||
}
|
||||
|
||||
m_context.resolve(def, ruleData.common.contextName);
|
||||
|
||||
return !(m_lookAhead && m_context.isStay());
|
||||
}
|
||||
|
||||
static Rule::Ptr createRule(DefinitionData &def, const HighlightingContextData::Rule &ruleData, QStringView lookupContextName)
|
||||
{
|
||||
using Type = HighlightingContextData::Rule::Type;
|
||||
|
||||
switch (ruleData.type) {
|
||||
case Type::AnyChar:
|
||||
return std::make_shared<AnyChar>(ruleData.data.anyChar);
|
||||
case Type::DetectChar:
|
||||
return std::make_shared<DetectChar>(ruleData.data.detectChar);
|
||||
case Type::Detect2Chars:
|
||||
return std::make_shared<Detect2Chars>(ruleData.data.detect2Chars);
|
||||
case Type::IncludeRules:
|
||||
return std::make_shared<IncludeRules>(ruleData.data.includeRules);
|
||||
case Type::Int:
|
||||
return std::make_shared<Int>(def, ruleData.data.detectInt);
|
||||
case Type::Keyword:
|
||||
return KeywordListRule::create(def, ruleData.data.keyword, lookupContextName);
|
||||
case Type::LineContinue:
|
||||
return std::make_shared<LineContinue>(ruleData.data.lineContinue);
|
||||
case Type::RangeDetect:
|
||||
return std::make_shared<RangeDetect>(ruleData.data.rangeDetect);
|
||||
case Type::RegExpr:
|
||||
if (!ruleData.data.regExpr.dynamic) {
|
||||
return std::make_shared<RegExpr>(ruleData.data.regExpr);
|
||||
} else {
|
||||
return std::make_shared<DynamicRegExpr>(ruleData.data.regExpr);
|
||||
}
|
||||
case Type::StringDetect:
|
||||
if (ruleData.data.stringDetect.dynamic) {
|
||||
return std::make_shared<DynamicStringDetect>(ruleData.data.stringDetect);
|
||||
}
|
||||
return std::make_shared<StringDetect>(ruleData.data.stringDetect);
|
||||
case Type::WordDetect:
|
||||
return std::make_shared<WordDetect>(def, ruleData.data.wordDetect);
|
||||
case Type::Float:
|
||||
return std::make_shared<Float>(def, ruleData.data.detectFloat);
|
||||
case Type::HlCOct:
|
||||
return std::make_shared<HlCOct>(def, ruleData.data.hlCOct);
|
||||
case Type::HlCStringChar:
|
||||
return std::make_shared<HlCStringChar>();
|
||||
case Type::DetectIdentifier:
|
||||
return std::make_shared<DetectIdentifier>();
|
||||
case Type::DetectSpaces:
|
||||
return std::make_shared<DetectSpaces>();
|
||||
case Type::HlCChar:
|
||||
return std::make_shared<HlCChar>();
|
||||
case Type::HlCHex:
|
||||
return std::make_shared<HlCHex>(def, ruleData.data.hlCHex);
|
||||
|
||||
case Type::Unknown:;
|
||||
}
|
||||
|
||||
return Rule::Ptr(nullptr);
|
||||
}
|
||||
|
||||
Rule::Ptr Rule::create(DefinitionData &def, const HighlightingContextData::Rule &ruleData, QStringView lookupContextName)
|
||||
{
|
||||
auto rule = createRule(def, ruleData, lookupContextName);
|
||||
if (rule && !rule->resolveCommon(def, ruleData, lookupContextName)) {
|
||||
rule.reset();
|
||||
}
|
||||
return rule;
|
||||
}
|
||||
|
||||
AnyChar::AnyChar(const HighlightingContextData::Rule::AnyChar &data)
|
||||
: m_chars(data.chars)
|
||||
{
|
||||
}
|
||||
|
||||
MatchResult AnyChar::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
if (m_chars.contains(text.at(offset))) {
|
||||
return offset + 1;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
DetectChar::DetectChar(const HighlightingContextData::Rule::DetectChar &data)
|
||||
: m_char(data.char1)
|
||||
, m_captureIndex((data.dynamic ? data.char1.digitValue() : 0) - 1)
|
||||
{
|
||||
m_dynamic = data.dynamic;
|
||||
}
|
||||
|
||||
MatchResult DetectChar::doMatch(QStringView text, int offset, const QStringList &captures, DynamicRegexpCache &) const
|
||||
{
|
||||
if (m_dynamic) {
|
||||
if (m_captureIndex == -1 || captures.size() <= m_captureIndex || captures.at(m_captureIndex).isEmpty()) {
|
||||
return offset;
|
||||
}
|
||||
if (text.at(offset) == captures.at(m_captureIndex).at(0)) {
|
||||
return offset + 1;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (text.at(offset) == m_char) {
|
||||
return offset + 1;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
Detect2Chars::Detect2Chars(const HighlightingContextData::Rule::Detect2Chars &data)
|
||||
: m_char1(data.char1)
|
||||
, m_char2(data.char2)
|
||||
{
|
||||
}
|
||||
|
||||
MatchResult Detect2Chars::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
if (text.size() - offset < 2) {
|
||||
return offset;
|
||||
}
|
||||
if (text.at(offset) == m_char1 && text.at(offset + 1) == m_char2) {
|
||||
return offset + 2;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
MatchResult DetectIdentifier::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
if (!text.at(offset).isLetter() && text.at(offset) != QLatin1Char('_')) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
for (int i = offset + 1; i < text.size(); ++i) {
|
||||
const auto c = text.at(i);
|
||||
if (!c.isLetterOrNumber() && c != QLatin1Char('_')) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return text.size();
|
||||
}
|
||||
|
||||
MatchResult DetectSpaces::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
while (offset < text.size() && text.at(offset).isSpace()) {
|
||||
++offset;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
Float::Float(DefinitionData &def, const HighlightingContextData::Rule::Float &data)
|
||||
: m_wordDelimiters(def.wordDelimiters)
|
||||
{
|
||||
resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
|
||||
}
|
||||
|
||||
MatchResult Float::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1))) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
auto newOffset = offset;
|
||||
while (newOffset < text.size() && isDigit(text.at(newOffset))) {
|
||||
++newOffset;
|
||||
}
|
||||
|
||||
if (newOffset >= text.size() || text.at(newOffset) != QLatin1Char('.')) {
|
||||
return offset;
|
||||
}
|
||||
++newOffset;
|
||||
|
||||
while (newOffset < text.size() && isDigit(text.at(newOffset))) {
|
||||
++newOffset;
|
||||
}
|
||||
|
||||
if (newOffset == offset + 1) { // we only found a decimal point
|
||||
return offset;
|
||||
}
|
||||
|
||||
auto expOffset = newOffset;
|
||||
if (expOffset >= text.size() || (text.at(expOffset) != QLatin1Char('e') && text.at(expOffset) != QLatin1Char('E'))) {
|
||||
return newOffset;
|
||||
}
|
||||
++expOffset;
|
||||
|
||||
if (expOffset < text.size() && (text.at(expOffset) == QLatin1Char('+') || text.at(expOffset) == QLatin1Char('-'))) {
|
||||
++expOffset;
|
||||
}
|
||||
bool foundExpDigit = false;
|
||||
while (expOffset < text.size() && isDigit(text.at(expOffset))) {
|
||||
++expOffset;
|
||||
foundExpDigit = true;
|
||||
}
|
||||
|
||||
if (!foundExpDigit) {
|
||||
return newOffset;
|
||||
}
|
||||
return expOffset;
|
||||
}
|
||||
|
||||
MatchResult HlCChar::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
if (text.size() < offset + 3) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (text.at(offset) != QLatin1Char('\'') || text.at(offset + 1) == QLatin1Char('\'')) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
auto newOffset = matchEscapedChar(text, offset + 1);
|
||||
if (newOffset == offset + 1) {
|
||||
if (text.at(newOffset) == QLatin1Char('\\')) {
|
||||
return offset;
|
||||
} else {
|
||||
++newOffset;
|
||||
}
|
||||
}
|
||||
if (newOffset >= text.size()) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (text.at(newOffset) == QLatin1Char('\'')) {
|
||||
return newOffset + 1;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
HlCHex::HlCHex(DefinitionData &def, const HighlightingContextData::Rule::HlCHex &data)
|
||||
: m_wordDelimiters(def.wordDelimiters)
|
||||
{
|
||||
resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
|
||||
}
|
||||
|
||||
MatchResult HlCHex::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1))) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (text.size() < offset + 3) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (text.at(offset) != QLatin1Char('0') || (text.at(offset + 1) != QLatin1Char('x') && text.at(offset + 1) != QLatin1Char('X'))) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (!isHexChar(text.at(offset + 2))) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
offset += 3;
|
||||
while (offset < text.size() && isHexChar(text.at(offset))) {
|
||||
++offset;
|
||||
}
|
||||
|
||||
// TODO Kate matches U/L suffix, QtC does not?
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
HlCOct::HlCOct(DefinitionData &def, const HighlightingContextData::Rule::HlCOct &data)
|
||||
: m_wordDelimiters(def.wordDelimiters)
|
||||
{
|
||||
resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
|
||||
}
|
||||
|
||||
MatchResult HlCOct::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1))) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (text.size() < offset + 2) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (text.at(offset) != QLatin1Char('0')) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (!isOctalChar(text.at(offset + 1))) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
offset += 2;
|
||||
while (offset < text.size() && isOctalChar(text.at(offset))) {
|
||||
++offset;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
MatchResult HlCStringChar::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
return matchEscapedChar(text, offset);
|
||||
}
|
||||
|
||||
IncludeRules::IncludeRules(const HighlightingContextData::Rule::IncludeRules &data)
|
||||
: m_contextName(data.contextName)
|
||||
, m_includeAttribute(data.includeAttribute)
|
||||
{
|
||||
}
|
||||
|
||||
MatchResult IncludeRules::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
Q_UNUSED(text);
|
||||
qCWarning(Log) << "Unresolved include rule";
|
||||
return offset;
|
||||
}
|
||||
|
||||
Int::Int(DefinitionData &def, const HighlightingContextData::Rule::Int &data)
|
||||
: m_wordDelimiters(def.wordDelimiters)
|
||||
{
|
||||
resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
|
||||
}
|
||||
|
||||
MatchResult Int::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1))) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
while (offset < text.size() && isDigit(text.at(offset))) {
|
||||
++offset;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
Rule::Ptr KeywordListRule::create(DefinitionData &def, const HighlightingContextData::Rule::Keyword &data, QStringView lookupContextName)
|
||||
{
|
||||
/**
|
||||
* get our keyword list, if not found => bail out
|
||||
*/
|
||||
auto *keywordList = def.keywordList(data.name);
|
||||
if (!keywordList) {
|
||||
qCWarning(Log) << "Rule: Unknown keyword list" << data.name << "in context" << lookupContextName << "of definition" << def.name;
|
||||
return Rule::Ptr();
|
||||
}
|
||||
|
||||
if (keywordList->isEmpty()) {
|
||||
return Rule::Ptr();
|
||||
}
|
||||
|
||||
/**
|
||||
* we might overwrite the case sensitivity
|
||||
* then we need to init the list for lookup of that sensitivity setting
|
||||
*/
|
||||
if (data.hasCaseSensitivityOverride) {
|
||||
keywordList->initLookupForCaseSensitivity(data.caseSensitivityOverride);
|
||||
}
|
||||
|
||||
return std::make_shared<KeywordListRule>(*keywordList, def, data);
|
||||
}
|
||||
|
||||
KeywordListRule::KeywordListRule(const KeywordList &keywordList, DefinitionData &def, const HighlightingContextData::Rule::Keyword &data)
|
||||
: m_wordDelimiters(def.wordDelimiters)
|
||||
, m_keywordList(keywordList)
|
||||
, m_caseSensitivity(data.hasCaseSensitivityOverride ? data.caseSensitivityOverride : keywordList.caseSensitivity())
|
||||
{
|
||||
resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
|
||||
m_hasSkipOffset = true;
|
||||
}
|
||||
|
||||
MatchResult KeywordListRule::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
auto newOffset = offset;
|
||||
while (text.size() > newOffset && !m_wordDelimiters.contains(text.at(newOffset))) {
|
||||
++newOffset;
|
||||
}
|
||||
if (newOffset == offset) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (m_keywordList.contains(text.sliced(offset, newOffset - offset), m_caseSensitivity)) {
|
||||
return newOffset;
|
||||
}
|
||||
|
||||
// we don't match, but we can skip until newOffset as we can't start a keyword in-between
|
||||
return MatchResult(offset, newOffset);
|
||||
}
|
||||
|
||||
LineContinue::LineContinue(const HighlightingContextData::Rule::LineContinue &data)
|
||||
: m_char(data.char1)
|
||||
{
|
||||
}
|
||||
|
||||
MatchResult LineContinue::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
if (offset == text.size() - 1 && text.at(offset) == m_char) {
|
||||
return offset + 1;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
RangeDetect::RangeDetect(const HighlightingContextData::Rule::RangeDetect &data)
|
||||
: m_begin(data.begin)
|
||||
, m_end(data.end)
|
||||
{
|
||||
}
|
||||
|
||||
MatchResult RangeDetect::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
if (text.size() - offset < 2) {
|
||||
return offset;
|
||||
}
|
||||
if (text.at(offset) != m_begin) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
auto newOffset = offset + 1;
|
||||
while (newOffset < text.size()) {
|
||||
if (text.at(newOffset) == m_end) {
|
||||
return newOffset + 1;
|
||||
}
|
||||
++newOffset;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
static QRegularExpression::PatternOptions makePattenOptions(const HighlightingContextData::Rule::RegExpr &data)
|
||||
{
|
||||
return (data.isMinimal ? QRegularExpression::InvertedGreedinessOption : QRegularExpression::NoPatternOption)
|
||||
| (data.caseSensitivity == Qt::CaseInsensitive ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption)
|
||||
// DontCaptureOption is removed by resolve() when necessary
|
||||
| QRegularExpression::DontCaptureOption
|
||||
// ensure Unicode support is enabled
|
||||
| QRegularExpression::UseUnicodePropertiesOption;
|
||||
}
|
||||
|
||||
static void resolveRegex(QRegularExpression ®exp, Context *context)
|
||||
{
|
||||
bool enableCapture = context && context->hasDynamicRule();
|
||||
|
||||
// disable DontCaptureOption when reference a context with dynamic rule or
|
||||
// with invalid regex because DontCaptureOption with back reference capture is an error
|
||||
if (enableCapture || !regexp.isValid()) {
|
||||
regexp.setPatternOptions(regexp.patternOptions() & ~QRegularExpression::DontCaptureOption);
|
||||
}
|
||||
|
||||
if (!regexp.isValid()) {
|
||||
qCDebug(Log) << "Invalid regexp:" << regexp.pattern();
|
||||
}
|
||||
}
|
||||
|
||||
static MatchResult regexMatch(const QRegularExpression ®exp, QStringView text, int offset)
|
||||
{
|
||||
/**
|
||||
* match the pattern
|
||||
*/
|
||||
const auto result = regexp.matchView(text, offset, QRegularExpression::NormalMatch, QRegularExpression::DontCheckSubjectStringMatchOption);
|
||||
if (result.capturedStart() == offset) {
|
||||
/**
|
||||
* we only need to compute the captured texts if we have real capture groups
|
||||
* highlightings should only address %1..%.., see e.g. replaceCaptures
|
||||
* DetectChar ignores %0, too
|
||||
*/
|
||||
int lastCapturedIndex = result.lastCapturedIndex();
|
||||
if (lastCapturedIndex > 0) {
|
||||
QStringList captures;
|
||||
captures.reserve(lastCapturedIndex);
|
||||
// ignore the capturing group number 0
|
||||
for (int i = 1; i <= lastCapturedIndex; ++i)
|
||||
captures.push_back(result.captured(i));
|
||||
return MatchResult(offset + result.capturedLength(), std::move(captures));
|
||||
}
|
||||
|
||||
/**
|
||||
* else: ignore the implicit 0 group we always capture, no need to allocate stuff for that
|
||||
*/
|
||||
return MatchResult(offset + result.capturedLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* no match
|
||||
* we can always compute the skip offset as the highlighter will invalidate the cache for changed captures for dynamic rules!
|
||||
*/
|
||||
return MatchResult(offset, result.capturedStart());
|
||||
}
|
||||
|
||||
RegExpr::RegExpr(const HighlightingContextData::Rule::RegExpr &data)
|
||||
: m_regexp(data.pattern, makePattenOptions(data))
|
||||
{
|
||||
m_hasSkipOffset = true;
|
||||
}
|
||||
|
||||
void RegExpr::resolve()
|
||||
{
|
||||
m_isResolved = true;
|
||||
|
||||
resolveRegex(m_regexp, context().context());
|
||||
}
|
||||
|
||||
MatchResult RegExpr::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
if (Q_UNLIKELY(!m_isResolved)) {
|
||||
const_cast<RegExpr *>(this)->resolve();
|
||||
}
|
||||
|
||||
return regexMatch(m_regexp, text, offset);
|
||||
}
|
||||
|
||||
DynamicRegExpr::DynamicRegExpr(const HighlightingContextData::Rule::RegExpr &data)
|
||||
: m_pattern(data.pattern)
|
||||
, m_patternOptions(makePattenOptions(data))
|
||||
{
|
||||
m_dynamic = true;
|
||||
m_hasSkipOffset = true;
|
||||
}
|
||||
|
||||
void DynamicRegExpr::resolve()
|
||||
{
|
||||
m_isResolved = true;
|
||||
|
||||
QRegularExpression regexp(m_pattern, m_patternOptions);
|
||||
resolveRegex(regexp, context().context());
|
||||
m_patternOptions = regexp.patternOptions();
|
||||
}
|
||||
|
||||
MatchResult DynamicRegExpr::doMatch(QStringView text, int offset, const QStringList &captures, DynamicRegexpCache &dynamicRegexpCache) const
|
||||
{
|
||||
if (Q_UNLIKELY(!m_isResolved)) {
|
||||
const_cast<DynamicRegExpr *>(this)->resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* create new pattern with right instantiation
|
||||
*/
|
||||
auto pattern = replaceCaptures(m_pattern, captures, true);
|
||||
auto ®exp = dynamicRegexpCache.compileRegexp(std::move(pattern), m_patternOptions);
|
||||
return regexMatch(regexp, text, offset);
|
||||
}
|
||||
|
||||
StringDetect::StringDetect(const HighlightingContextData::Rule::StringDetect &data)
|
||||
: m_string(data.string)
|
||||
, m_caseSensitivity(data.caseSensitivity)
|
||||
{
|
||||
}
|
||||
|
||||
MatchResult StringDetect::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
return matchString(m_string, text, offset, m_caseSensitivity);
|
||||
}
|
||||
|
||||
DynamicStringDetect::DynamicStringDetect(const HighlightingContextData::Rule::StringDetect &data)
|
||||
: m_string(data.string)
|
||||
, m_caseSensitivity(data.caseSensitivity)
|
||||
{
|
||||
m_dynamic = true;
|
||||
}
|
||||
|
||||
MatchResult DynamicStringDetect::doMatch(QStringView text, int offset, const QStringList &captures, DynamicRegexpCache &) const
|
||||
{
|
||||
/**
|
||||
* for dynamic case: create new pattern with right instantiation
|
||||
*/
|
||||
const auto pattern = replaceCaptures(m_string, captures, false);
|
||||
return matchString(pattern, text, offset, m_caseSensitivity);
|
||||
}
|
||||
|
||||
WordDetect::WordDetect(DefinitionData &def, const HighlightingContextData::Rule::WordDetect &data)
|
||||
: m_wordDelimiters(def.wordDelimiters)
|
||||
, m_word(data.word)
|
||||
, m_caseSensitivity(data.caseSensitivity)
|
||||
{
|
||||
resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
|
||||
}
|
||||
|
||||
MatchResult WordDetect::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
|
||||
{
|
||||
if (text.size() - offset < m_word.size()) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* detect delimiter characters on the inner and outer boundaries of the string
|
||||
* NOTE: m_word isn't empty
|
||||
*/
|
||||
if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1)) && !m_wordDelimiters.contains(text.at(offset))) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (text.sliced(offset, m_word.size()).compare(m_word, m_caseSensitivity) != 0) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
if (text.size() == offset + m_word.size() || m_wordDelimiters.contains(text.at(offset + m_word.size()))
|
||||
|| m_wordDelimiters.contains(text.at(offset + m_word.size() - 1))) {
|
||||
return offset + m_word.size();
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_RULE_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_RULE_P_H
|
||||
|
||||
#include "contextswitch_p.h"
|
||||
#include "foldingregion.h"
|
||||
#include "format.h"
|
||||
#include "highlightingdata_p.hpp"
|
||||
#include "keywordlist_p.h"
|
||||
#include "matchresult_p.h"
|
||||
#include "worddelimiters_p.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class WordDelimiters;
|
||||
class DefinitionData;
|
||||
class IncludeRules;
|
||||
class DynamicRegexpCache;
|
||||
|
||||
class Rule
|
||||
{
|
||||
public:
|
||||
Rule() = default;
|
||||
virtual ~Rule();
|
||||
|
||||
typedef std::shared_ptr<Rule> Ptr;
|
||||
|
||||
const Format &attributeFormat() const
|
||||
{
|
||||
return m_attributeFormat;
|
||||
}
|
||||
|
||||
const ContextSwitch &context() const
|
||||
{
|
||||
return m_context;
|
||||
}
|
||||
|
||||
bool isLookAhead() const
|
||||
{
|
||||
return m_lookAhead;
|
||||
}
|
||||
|
||||
bool isDynamic() const
|
||||
{
|
||||
return m_dynamic;
|
||||
}
|
||||
|
||||
bool firstNonSpace() const
|
||||
{
|
||||
return m_firstNonSpace;
|
||||
}
|
||||
|
||||
int requiredColumn() const
|
||||
{
|
||||
return m_column;
|
||||
}
|
||||
|
||||
const FoldingRegion &beginRegion() const
|
||||
{
|
||||
return m_beginRegion;
|
||||
}
|
||||
|
||||
const FoldingRegion &endRegion() const
|
||||
{
|
||||
return m_endRegion;
|
||||
}
|
||||
|
||||
const IncludeRules *castToIncludeRules() const;
|
||||
|
||||
bool isLineContinue() const
|
||||
{
|
||||
return m_type == Type::LineContinue;
|
||||
}
|
||||
|
||||
// If true, then the rule uses the skipOffset parameter of MatchResult.
|
||||
// This is used by AbstractHighlighter::highlightLine() to look for a rule
|
||||
// in the skipOffsets cache only if it can be found there.
|
||||
bool hasSkipOffset() const
|
||||
{
|
||||
return m_hasSkipOffset;
|
||||
}
|
||||
|
||||
virtual MatchResult doMatch(QStringView text, int offset, const QStringList &captures, DynamicRegexpCache &dynamicRegexpCache) const = 0;
|
||||
|
||||
static Rule::Ptr create(DefinitionData &def, const HighlightingContextData::Rule &ruleData, QStringView lookupContextName);
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(Rule)
|
||||
|
||||
bool resolveCommon(DefinitionData &def, const HighlightingContextData::Rule &ruleData, QStringView lookupContextName);
|
||||
|
||||
enum class Type : quint8 {
|
||||
OtherRule,
|
||||
LineContinue,
|
||||
IncludeRules,
|
||||
};
|
||||
|
||||
private:
|
||||
Format m_attributeFormat;
|
||||
ContextSwitch m_context;
|
||||
int m_column = -1;
|
||||
FoldingRegion m_beginRegion;
|
||||
FoldingRegion m_endRegion;
|
||||
Type m_type;
|
||||
bool m_firstNonSpace = false;
|
||||
bool m_lookAhead = false;
|
||||
|
||||
protected:
|
||||
bool m_hasSkipOffset = false;
|
||||
bool m_dynamic = false;
|
||||
};
|
||||
|
||||
class AnyChar final : public Rule
|
||||
{
|
||||
public:
|
||||
AnyChar(const HighlightingContextData::Rule::AnyChar &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
WordDelimiters m_chars;
|
||||
};
|
||||
|
||||
class DetectChar final : public Rule
|
||||
{
|
||||
public:
|
||||
DetectChar(const HighlightingContextData::Rule::DetectChar &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
QChar m_char;
|
||||
int m_captureIndex = 0;
|
||||
};
|
||||
|
||||
class Detect2Chars final : public Rule
|
||||
{
|
||||
public:
|
||||
Detect2Chars(const HighlightingContextData::Rule::Detect2Chars &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
QChar m_char1;
|
||||
QChar m_char2;
|
||||
};
|
||||
|
||||
class DetectIdentifier final : public Rule
|
||||
{
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
};
|
||||
|
||||
class DetectSpaces final : public Rule
|
||||
{
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
};
|
||||
|
||||
class Float final : public Rule
|
||||
{
|
||||
public:
|
||||
Float(DefinitionData &def, const HighlightingContextData::Rule::Float &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
WordDelimiters m_wordDelimiters;
|
||||
};
|
||||
|
||||
class IncludeRules final : public Rule
|
||||
{
|
||||
public:
|
||||
IncludeRules(const HighlightingContextData::Rule::IncludeRules &data);
|
||||
|
||||
const QString &contextName() const
|
||||
{
|
||||
return m_contextName;
|
||||
}
|
||||
|
||||
bool includeAttribute() const
|
||||
{
|
||||
return m_includeAttribute;
|
||||
}
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
QString m_contextName;
|
||||
bool m_includeAttribute;
|
||||
};
|
||||
|
||||
class Int final : public Rule
|
||||
{
|
||||
public:
|
||||
Int(DefinitionData &def, const HighlightingContextData::Rule::Int &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
WordDelimiters m_wordDelimiters;
|
||||
};
|
||||
|
||||
class HlCChar final : public Rule
|
||||
{
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
};
|
||||
|
||||
class HlCHex final : public Rule
|
||||
{
|
||||
public:
|
||||
HlCHex(DefinitionData &def, const HighlightingContextData::Rule::HlCHex &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
WordDelimiters m_wordDelimiters;
|
||||
};
|
||||
|
||||
class HlCOct final : public Rule
|
||||
{
|
||||
public:
|
||||
HlCOct(DefinitionData &def, const HighlightingContextData::Rule::HlCOct &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
WordDelimiters m_wordDelimiters;
|
||||
};
|
||||
|
||||
class HlCStringChar final : public Rule
|
||||
{
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
};
|
||||
|
||||
class KeywordListRule final : public Rule
|
||||
{
|
||||
public:
|
||||
KeywordListRule(const KeywordList &keywordList, DefinitionData &def, const HighlightingContextData::Rule::Keyword &data);
|
||||
|
||||
static Rule::Ptr create(DefinitionData &def, const HighlightingContextData::Rule::Keyword &data, QStringView lookupContextName);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
WordDelimiters m_wordDelimiters;
|
||||
const KeywordList &m_keywordList;
|
||||
Qt::CaseSensitivity m_caseSensitivity;
|
||||
};
|
||||
|
||||
class LineContinue final : public Rule
|
||||
{
|
||||
public:
|
||||
LineContinue(const HighlightingContextData::Rule::LineContinue &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
QChar m_char;
|
||||
};
|
||||
|
||||
class RangeDetect final : public Rule
|
||||
{
|
||||
public:
|
||||
RangeDetect(const HighlightingContextData::Rule::RangeDetect &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
QChar m_begin;
|
||||
QChar m_end;
|
||||
};
|
||||
|
||||
class RegExpr final : public Rule
|
||||
{
|
||||
public:
|
||||
RegExpr(const HighlightingContextData::Rule::RegExpr &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
void resolve();
|
||||
QRegularExpression m_regexp;
|
||||
bool m_isResolved = false;
|
||||
};
|
||||
|
||||
class DynamicRegExpr final : public Rule
|
||||
{
|
||||
public:
|
||||
DynamicRegExpr(const HighlightingContextData::Rule::RegExpr &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
void resolve();
|
||||
QString m_pattern;
|
||||
QRegularExpression::PatternOptions m_patternOptions;
|
||||
bool m_isResolved = false;
|
||||
};
|
||||
|
||||
class StringDetect final : public Rule
|
||||
{
|
||||
public:
|
||||
StringDetect(const HighlightingContextData::Rule::StringDetect &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
QString m_string;
|
||||
Qt::CaseSensitivity m_caseSensitivity;
|
||||
};
|
||||
|
||||
class DynamicStringDetect final : public Rule
|
||||
{
|
||||
public:
|
||||
DynamicStringDetect(const HighlightingContextData::Rule::StringDetect &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
QString m_string;
|
||||
Qt::CaseSensitivity m_caseSensitivity;
|
||||
};
|
||||
|
||||
class WordDetect final : public Rule
|
||||
{
|
||||
public:
|
||||
WordDetect(DefinitionData &def, const HighlightingContextData::Rule::WordDetect &data);
|
||||
|
||||
protected:
|
||||
MatchResult doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const override;
|
||||
|
||||
private:
|
||||
WordDelimiters m_wordDelimiters;
|
||||
QString m_word;
|
||||
Qt::CaseSensitivity m_caseSensitivity;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_RULE_P_H
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2018 Christoph Cullmann <cullmann@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "state.h"
|
||||
#include "state_p.h"
|
||||
|
||||
#include "context_p.h"
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
StateData *StateData::reset(State &state)
|
||||
{
|
||||
auto *p = new StateData();
|
||||
state.d.reset(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
StateData *StateData::detach(State &state)
|
||||
{
|
||||
state.d.detach();
|
||||
return state.d.data();
|
||||
}
|
||||
|
||||
void StateData::push(Context *context, QStringList &&captures)
|
||||
{
|
||||
Q_ASSERT(context);
|
||||
m_contextStack.push_back(StackValue{context, std::move(captures)});
|
||||
}
|
||||
|
||||
bool StateData::pop(int popCount)
|
||||
{
|
||||
// nop if nothing to pop
|
||||
if (popCount <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// keep the initial context alive in any case
|
||||
Q_ASSERT(!m_contextStack.empty());
|
||||
const bool initialContextSurvived = int(m_contextStack.size()) > popCount;
|
||||
m_contextStack.resize(std::max(1, int(m_contextStack.size()) - popCount));
|
||||
return initialContextSurvived;
|
||||
}
|
||||
|
||||
State::State() = default;
|
||||
|
||||
State::State(State &&other) noexcept = default;
|
||||
|
||||
State::State(const State &other) noexcept = default;
|
||||
|
||||
State::~State() = default;
|
||||
|
||||
State &State::operator=(State &&other) noexcept = default;
|
||||
|
||||
State &State::operator=(const State &other) noexcept = default;
|
||||
|
||||
bool State::operator==(const State &other) const
|
||||
{
|
||||
// use pointer equal as shortcut for shared states
|
||||
return (d == other.d) || (d && other.d && d->m_contextStack == other.d->m_contextStack && d->m_defId == other.d->m_defId);
|
||||
}
|
||||
|
||||
bool State::operator!=(const State &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool State::indentationBasedFoldingEnabled() const
|
||||
{
|
||||
if (!d || d->m_contextStack.empty()) {
|
||||
return false;
|
||||
}
|
||||
return d->m_contextStack.back().context->indentationBasedFoldingEnabled();
|
||||
}
|
||||
|
||||
std::size_t KSyntaxHighlighting::qHash(const State &state, std::size_t seed)
|
||||
{
|
||||
return state.d ? qHashMulti(seed, *state.d) : 0;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_STATE_H
|
||||
#define KSYNTAXHIGHLIGHTING_STATE_H
|
||||
|
||||
#include "ksyntaxhighlighting_export.h"
|
||||
|
||||
#include <QExplicitlySharedDataPointer>
|
||||
#include <QHash>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class State;
|
||||
class StateData;
|
||||
|
||||
KSYNTAXHIGHLIGHTING_EXPORT std::size_t qHash(const State &state, std::size_t seed = 0);
|
||||
|
||||
/** Opaque handle to the state of the highlighting engine.
|
||||
* This needs to be fed into AbstractHighlighter for every line of text
|
||||
* and allows concrete highlighter implementations to store state per
|
||||
* line for fast re-highlighting of specific lines (e.g. during editing).
|
||||
*
|
||||
* @since 5.28
|
||||
*/
|
||||
class KSYNTAXHIGHLIGHTING_EXPORT State
|
||||
{
|
||||
public:
|
||||
/** Creates an initial state, ie. what should be used for the first line
|
||||
* in a document.
|
||||
*/
|
||||
State();
|
||||
State(State &&other) noexcept;
|
||||
State(const State &other) noexcept;
|
||||
~State();
|
||||
State &operator=(State &&rhs) noexcept;
|
||||
State &operator=(const State &rhs) noexcept;
|
||||
|
||||
/** Compares two states for equality.
|
||||
* For two equal states and identical text input, AbstractHighlighter
|
||||
* guarantees to produce equal results. This can be used to only
|
||||
* re-highlight as many lines as necessary during editing.
|
||||
*/
|
||||
bool operator==(const State &other) const;
|
||||
/** Compares two states for inequality.
|
||||
* This is the opposite of operator==().
|
||||
*/
|
||||
bool operator!=(const State &other) const;
|
||||
|
||||
/**
|
||||
* Returns whether or not indentation-based folding is enabled in this state.
|
||||
* When using a Definition with indentation-based folding, use
|
||||
* this method to check if indentation-based folding has been
|
||||
* suspended in the current line.
|
||||
*
|
||||
* @see Definition::indentationBasedFoldingEnabled()
|
||||
*/
|
||||
bool indentationBasedFoldingEnabled() const;
|
||||
|
||||
private:
|
||||
friend class StateData;
|
||||
friend KSYNTAXHIGHLIGHTING_EXPORT std::size_t qHash(const State &, std::size_t);
|
||||
QExplicitlySharedDataPointer<StateData> d;
|
||||
};
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
Q_DECLARE_TYPEINFO(KSyntaxHighlighting::State, Q_RELOCATABLE_TYPE);
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_STATE_H
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2018 Christoph Cullmann <cullmann@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_STATE_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_STATE_P_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QSharedData>
|
||||
#include <QStringList>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class Context;
|
||||
|
||||
class StateData : public QSharedData
|
||||
{
|
||||
friend class State;
|
||||
friend class AbstractHighlighter;
|
||||
friend std::size_t qHash(const StateData &, std::size_t);
|
||||
|
||||
public:
|
||||
StateData() = default;
|
||||
|
||||
static StateData *reset(State &state);
|
||||
static StateData *detach(State &state);
|
||||
|
||||
static StateData *get(const State &state)
|
||||
{
|
||||
return state.d.data();
|
||||
}
|
||||
|
||||
std::size_t size() const
|
||||
{
|
||||
return m_contextStack.size();
|
||||
}
|
||||
|
||||
void push(Context *context, QStringList &&captures);
|
||||
|
||||
/**
|
||||
* Pop the number of elements given from the top of the current stack.
|
||||
* Will not pop the initial element.
|
||||
* @param popCount number of elements to pop
|
||||
* @return false if one has tried to pop the initial context, else true
|
||||
*/
|
||||
bool pop(int popCount);
|
||||
|
||||
Context *topContext() const
|
||||
{
|
||||
return m_contextStack.back().context;
|
||||
}
|
||||
|
||||
const QStringList &topCaptures() const
|
||||
{
|
||||
return m_contextStack.back().captures;
|
||||
}
|
||||
|
||||
struct StackValue {
|
||||
Context *context;
|
||||
QStringList captures;
|
||||
|
||||
bool operator==(const StackValue &other) const
|
||||
{
|
||||
return context == other.context && captures == other.captures;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
/**
|
||||
* definition id to filter out invalid states
|
||||
*/
|
||||
uint64_t m_defId = 0;
|
||||
|
||||
/**
|
||||
* the context stack combines the active context + valid captures
|
||||
*/
|
||||
std::vector<StackValue> m_contextStack;
|
||||
};
|
||||
|
||||
inline std::size_t qHash(const StateData::StackValue &stackValue, std::size_t seed = 0)
|
||||
{
|
||||
return qHashMulti(seed, stackValue.context, stackValue.captures);
|
||||
}
|
||||
|
||||
inline std::size_t qHash(const StateData &k, std::size_t seed = 0)
|
||||
{
|
||||
return qHashMulti(seed, k.m_defId, qHashRange(k.m_contextStack.begin(), k.m_contextStack.end(), seed));
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "syntaxhighlighter.h"
|
||||
#include "abstracthighlighter_p.h"
|
||||
#include "definition.h"
|
||||
#include "definition_p.h"
|
||||
#include "foldingregion.h"
|
||||
#include "format.h"
|
||||
#include "format_p.h"
|
||||
#include "state.h"
|
||||
#include "theme.h"
|
||||
#include "themedata_p.h"
|
||||
|
||||
Q_DECLARE_METATYPE(QTextBlock)
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class TextBlockUserData : public QTextBlockUserData
|
||||
{
|
||||
public:
|
||||
State state;
|
||||
QList<FoldingRegion> foldingRegions;
|
||||
};
|
||||
|
||||
class SyntaxHighlighterPrivate : public AbstractHighlighterPrivate
|
||||
{
|
||||
public:
|
||||
static FoldingRegion foldingRegion(const QTextBlock &startBlock);
|
||||
void initTextFormat(QTextCharFormat &tf, const Format &format);
|
||||
void computeTextFormats();
|
||||
|
||||
struct TextFormat {
|
||||
QTextCharFormat tf;
|
||||
/**
|
||||
* id to check that the format belongs to the definition
|
||||
*/
|
||||
std::intptr_t ptrId;
|
||||
};
|
||||
|
||||
QList<FoldingRegion> foldingRegions;
|
||||
std::vector<TextFormat> tfs;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
FoldingRegion SyntaxHighlighterPrivate::foldingRegion(const QTextBlock &startBlock)
|
||||
{
|
||||
const auto data = dynamic_cast<TextBlockUserData *>(startBlock.userData());
|
||||
if (!data) {
|
||||
return FoldingRegion();
|
||||
}
|
||||
for (int i = data->foldingRegions.size() - 1; i >= 0; --i) {
|
||||
if (data->foldingRegions.at(i).type() == FoldingRegion::Begin) {
|
||||
return data->foldingRegions.at(i);
|
||||
}
|
||||
}
|
||||
return FoldingRegion();
|
||||
}
|
||||
|
||||
void SyntaxHighlighterPrivate::initTextFormat(QTextCharFormat &tf, const Format &format)
|
||||
{
|
||||
// always set the foreground color to avoid palette issues
|
||||
tf.setForeground(format.textColor(m_theme));
|
||||
|
||||
if (format.hasBackgroundColor(m_theme)) {
|
||||
tf.setBackground(format.backgroundColor(m_theme));
|
||||
}
|
||||
if (format.isBold(m_theme)) {
|
||||
tf.setFontWeight(QFont::Bold);
|
||||
}
|
||||
if (format.isItalic(m_theme)) {
|
||||
tf.setFontItalic(true);
|
||||
}
|
||||
if (format.isUnderline(m_theme)) {
|
||||
tf.setFontUnderline(true);
|
||||
}
|
||||
if (format.isStrikeThrough(m_theme)) {
|
||||
tf.setFontStrikeOut(true);
|
||||
}
|
||||
}
|
||||
|
||||
void SyntaxHighlighterPrivate::computeTextFormats()
|
||||
{
|
||||
auto definitions = m_definition.includedDefinitions();
|
||||
definitions.append(m_definition);
|
||||
|
||||
int maxId = 0;
|
||||
for (const auto &definition : std::as_const(definitions)) {
|
||||
for (const auto &format : std::as_const(DefinitionData::get(definition)->formats)) {
|
||||
maxId = qMax(maxId, format.id());
|
||||
}
|
||||
}
|
||||
tfs.clear();
|
||||
tfs.resize(maxId + 1);
|
||||
|
||||
// initialize tfs
|
||||
for (const auto &definition : std::as_const(definitions)) {
|
||||
for (const auto &format : std::as_const(DefinitionData::get(definition)->formats)) {
|
||||
auto &tf = tfs[format.id()];
|
||||
tf.ptrId = FormatPrivate::ptrId(format);
|
||||
initTextFormat(tf.tf, format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SyntaxHighlighter::SyntaxHighlighter(QObject *parent)
|
||||
: QSyntaxHighlighter(parent)
|
||||
, AbstractHighlighter(new SyntaxHighlighterPrivate)
|
||||
{
|
||||
qRegisterMetaType<QTextBlock>();
|
||||
}
|
||||
|
||||
SyntaxHighlighter::SyntaxHighlighter(QTextDocument *document)
|
||||
: QSyntaxHighlighter(document)
|
||||
, AbstractHighlighter(new SyntaxHighlighterPrivate)
|
||||
{
|
||||
qRegisterMetaType<QTextBlock>();
|
||||
}
|
||||
|
||||
SyntaxHighlighter::~SyntaxHighlighter()
|
||||
{
|
||||
}
|
||||
|
||||
void SyntaxHighlighter::setDefinition(const Definition &def)
|
||||
{
|
||||
Q_D(SyntaxHighlighter);
|
||||
|
||||
const auto needsRehighlight = d->m_definition != def;
|
||||
if (DefinitionData::get(d->m_definition) != DefinitionData::get(def)) {
|
||||
d->m_definition = def;
|
||||
d->tfs.clear();
|
||||
}
|
||||
if (needsRehighlight) {
|
||||
rehighlight();
|
||||
}
|
||||
}
|
||||
|
||||
void SyntaxHighlighter::setTheme(const Theme &theme)
|
||||
{
|
||||
Q_D(SyntaxHighlighter);
|
||||
if (ThemeData::get(d->m_theme) != ThemeData::get(theme)) {
|
||||
d->m_theme = theme;
|
||||
d->tfs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool SyntaxHighlighter::startsFoldingRegion(const QTextBlock &startBlock) const
|
||||
{
|
||||
return SyntaxHighlighterPrivate::foldingRegion(startBlock).type() == FoldingRegion::Begin;
|
||||
}
|
||||
|
||||
QTextBlock SyntaxHighlighter::findFoldingRegionEnd(const QTextBlock &startBlock) const
|
||||
{
|
||||
const auto region = SyntaxHighlighterPrivate::foldingRegion(startBlock);
|
||||
|
||||
auto block = startBlock;
|
||||
int depth = 1;
|
||||
while (block.isValid()) {
|
||||
block = block.next();
|
||||
const auto data = dynamic_cast<TextBlockUserData *>(block.userData());
|
||||
if (!data) {
|
||||
continue;
|
||||
}
|
||||
for (const auto &foldingRegion : std::as_const(data->foldingRegions)) {
|
||||
if (foldingRegion.id() != region.id()) {
|
||||
continue;
|
||||
}
|
||||
if (foldingRegion.type() == FoldingRegion::End) {
|
||||
--depth;
|
||||
} else if (foldingRegion.type() == FoldingRegion::Begin) {
|
||||
++depth;
|
||||
}
|
||||
if (depth == 0) {
|
||||
return block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QTextBlock();
|
||||
}
|
||||
|
||||
void SyntaxHighlighter::highlightBlock(const QString &text)
|
||||
{
|
||||
Q_D(SyntaxHighlighter);
|
||||
|
||||
static const State emptyState;
|
||||
const State *previousState = &emptyState;
|
||||
if (currentBlock().position() > 0) {
|
||||
const auto prevBlock = currentBlock().previous();
|
||||
const auto prevData = dynamic_cast<TextBlockUserData *>(prevBlock.userData());
|
||||
if (prevData) {
|
||||
previousState = &prevData->state;
|
||||
}
|
||||
}
|
||||
d->foldingRegions.clear();
|
||||
auto newState = highlightLine(text, *previousState);
|
||||
|
||||
auto data = dynamic_cast<TextBlockUserData *>(currentBlockUserData());
|
||||
if (!data) { // first time we highlight this
|
||||
data = new TextBlockUserData;
|
||||
data->state = std::move(newState);
|
||||
data->foldingRegions = d->foldingRegions;
|
||||
setCurrentBlockUserData(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data->state == newState && data->foldingRegions == d->foldingRegions) { // we ended up in the same state, so we are done here
|
||||
return;
|
||||
}
|
||||
data->state = std::move(newState);
|
||||
data->foldingRegions = d->foldingRegions;
|
||||
|
||||
const auto nextBlock = currentBlock().next();
|
||||
if (nextBlock.isValid()) {
|
||||
QMetaObject::invokeMethod(this, "rehighlightBlock", Qt::QueuedConnection, Q_ARG(QTextBlock, nextBlock));
|
||||
}
|
||||
}
|
||||
|
||||
void SyntaxHighlighter::applyFormat(int offset, int length, const Format &format)
|
||||
{
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Q_D(SyntaxHighlighter);
|
||||
|
||||
if (Q_UNLIKELY(d->tfs.empty())) {
|
||||
d->computeTextFormats();
|
||||
}
|
||||
|
||||
const auto id = static_cast<std::size_t>(format.id());
|
||||
// This doesn't happen when format comes from the definition.
|
||||
// But as the user can override the function to pass any format, this is a possible scenario.
|
||||
if (id < d->tfs.size() && d->tfs[id].ptrId == FormatPrivate::ptrId(format)) {
|
||||
QSyntaxHighlighter::setFormat(offset, length, d->tfs[id].tf);
|
||||
} else {
|
||||
QTextCharFormat tf;
|
||||
d->initTextFormat(tf, format);
|
||||
QSyntaxHighlighter::setFormat(offset, length, tf);
|
||||
}
|
||||
}
|
||||
|
||||
void SyntaxHighlighter::applyFolding(int offset, int length, FoldingRegion region)
|
||||
{
|
||||
Q_UNUSED(offset);
|
||||
Q_UNUSED(length);
|
||||
Q_D(SyntaxHighlighter);
|
||||
|
||||
if (region.type() == FoldingRegion::Begin) {
|
||||
d->foldingRegions.push_back(region);
|
||||
}
|
||||
|
||||
if (region.type() == FoldingRegion::End) {
|
||||
for (int i = d->foldingRegions.size() - 1; i >= 0; --i) {
|
||||
if (d->foldingRegions.at(i).id() != region.id() || d->foldingRegions.at(i).type() != FoldingRegion::Begin) {
|
||||
continue;
|
||||
}
|
||||
d->foldingRegions.remove(i);
|
||||
return;
|
||||
}
|
||||
d->foldingRegions.push_back(region);
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_syntaxhighlighter.cpp"
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_QSYNTAXHIGHLIGHTER_H
|
||||
#define KSYNTAXHIGHLIGHTING_QSYNTAXHIGHLIGHTER_H
|
||||
|
||||
#include "ksyntaxhighlighting_export.h"
|
||||
|
||||
#include "abstracthighlighter.h"
|
||||
|
||||
#include <QSyntaxHighlighter>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class SyntaxHighlighterPrivate;
|
||||
|
||||
/** A QSyntaxHighlighter implementation for use with QTextDocument.
|
||||
* This supports partial re-highlighting during editing and
|
||||
* tracks syntax-based code folding regions.
|
||||
*
|
||||
* @since 5.28
|
||||
*/
|
||||
class KSYNTAXHIGHLIGHTING_EXPORT SyntaxHighlighter : public QSyntaxHighlighter, public AbstractHighlighter
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SyntaxHighlighter(QObject *parent = nullptr);
|
||||
explicit SyntaxHighlighter(QTextDocument *document);
|
||||
~SyntaxHighlighter() override;
|
||||
|
||||
void setDefinition(const Definition &def) override;
|
||||
void setTheme(const Theme &theme) override;
|
||||
|
||||
/** Returns whether there is a folding region beginning at @p startBlock.
|
||||
* This only considers syntax-based folding regions,
|
||||
* not indention-based ones as e.g. found in Python.
|
||||
*
|
||||
* @see findFoldingRegionEnd
|
||||
*/
|
||||
bool startsFoldingRegion(const QTextBlock &startBlock) const;
|
||||
|
||||
/** Finds the end of the folding region starting at @p startBlock.
|
||||
* If multiple folding regions begin at @p startBlock, the end of
|
||||
* the last/innermost one is returned.
|
||||
* This returns an invalid block if no folding region end is found,
|
||||
* which typically indicates an unterminated region and thus folding
|
||||
* until the document end.
|
||||
* This method performs a sequential search starting at @p startBlock
|
||||
* for the matching folding region end, which is a potentially expensive
|
||||
* operation.
|
||||
*
|
||||
* @see startsFoldingRegion
|
||||
*/
|
||||
QTextBlock findFoldingRegionEnd(const QTextBlock &startBlock) const;
|
||||
|
||||
protected:
|
||||
void highlightBlock(const QString &text) override;
|
||||
void applyFormat(int offset, int length, const Format &format) override;
|
||||
void applyFolding(int offset, int length, FoldingRegion region) override;
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE_D(AbstractHighlighter::d_ptr, SyntaxHighlighter)
|
||||
};
|
||||
}
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_QSYNTAXHIGHLIGHTER_H
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_TEXTSTYLEDATA_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_TEXTSTYLEDATA_P_H
|
||||
|
||||
#include <QColor>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class TextStyleData
|
||||
{
|
||||
public:
|
||||
// Constructor initializing all data.
|
||||
TextStyleData() noexcept
|
||||
: bold(false)
|
||||
, italic(false)
|
||||
, underline(false)
|
||||
, strikeThrough(false)
|
||||
, hasBold(false)
|
||||
, hasItalic(false)
|
||||
, hasUnderline(false)
|
||||
, hasStrikeThrough(false)
|
||||
{
|
||||
}
|
||||
|
||||
QRgb textColor = 0x0;
|
||||
QRgb backgroundColor = 0x0;
|
||||
QRgb selectedTextColor = 0x0;
|
||||
QRgb selectedBackgroundColor = 0x0;
|
||||
bool bold : 1;
|
||||
bool italic : 1;
|
||||
bool underline : 1;
|
||||
bool strikeThrough : 1;
|
||||
|
||||
bool hasBold : 1;
|
||||
bool hasItalic : 1;
|
||||
bool hasUnderline : 1;
|
||||
bool hasStrikeThrough : 1;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2022 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "theme.h"
|
||||
#include "themedata_p.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
static QExplicitlySharedDataPointer<ThemeData> &sharedDefaultThemeData()
|
||||
{
|
||||
static QExplicitlySharedDataPointer<ThemeData> data(new ThemeData);
|
||||
return data;
|
||||
}
|
||||
|
||||
Theme::Theme()
|
||||
: m_data(sharedDefaultThemeData())
|
||||
{
|
||||
}
|
||||
|
||||
Theme::Theme(const Theme ©) = default;
|
||||
|
||||
Theme::Theme(ThemeData *data)
|
||||
: m_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
Theme::~Theme() = default;
|
||||
|
||||
Theme &Theme::operator=(const Theme &other) = default;
|
||||
|
||||
bool Theme::isValid() const
|
||||
{
|
||||
return m_data.data() != sharedDefaultThemeData().data();
|
||||
}
|
||||
|
||||
QString Theme::name() const
|
||||
{
|
||||
return m_data->name();
|
||||
}
|
||||
|
||||
QString Theme::translatedName() const
|
||||
{
|
||||
return isValid() ? QCoreApplication::instance()->translate("Theme", m_data->name().toUtf8().constData()) : QString();
|
||||
}
|
||||
|
||||
bool Theme::isReadOnly() const
|
||||
{
|
||||
return m_data->isReadOnly();
|
||||
}
|
||||
|
||||
QString Theme::filePath() const
|
||||
{
|
||||
return m_data->filePath();
|
||||
}
|
||||
|
||||
QRgb Theme::textColor(TextStyle style) const
|
||||
{
|
||||
return m_data->textColor(style);
|
||||
}
|
||||
|
||||
QRgb Theme::selectedTextColor(TextStyle style) const
|
||||
{
|
||||
return m_data->selectedTextColor(style);
|
||||
}
|
||||
|
||||
QRgb Theme::backgroundColor(TextStyle style) const
|
||||
{
|
||||
return m_data->backgroundColor(style);
|
||||
}
|
||||
|
||||
QRgb Theme::selectedBackgroundColor(TextStyle style) const
|
||||
{
|
||||
return m_data->selectedBackgroundColor(style);
|
||||
}
|
||||
|
||||
bool Theme::isBold(TextStyle style) const
|
||||
{
|
||||
return m_data->isBold(style);
|
||||
}
|
||||
|
||||
bool Theme::isItalic(TextStyle style) const
|
||||
{
|
||||
return m_data->isItalic(style);
|
||||
}
|
||||
|
||||
bool Theme::isUnderline(TextStyle style) const
|
||||
{
|
||||
return m_data->isUnderline(style);
|
||||
}
|
||||
|
||||
bool Theme::isStrikeThrough(TextStyle style) const
|
||||
{
|
||||
return m_data->isStrikeThrough(style);
|
||||
}
|
||||
|
||||
QRgb Theme::editorColor(EditorColorRole role) const
|
||||
{
|
||||
return m_data->editorColor(role);
|
||||
}
|
||||
|
||||
#include "moc_theme.cpp"
|
||||
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_THEME_H
|
||||
#define KSYNTAXHIGHLIGHTING_THEME_H
|
||||
|
||||
#include "ksyntaxhighlighting_export.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QExplicitlySharedDataPointer>
|
||||
#include <QTypeInfo>
|
||||
#include <qobjectdefs.h>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class ThemeData;
|
||||
class RepositoryPrivate;
|
||||
|
||||
/**
|
||||
* Color theme definition used for highlighting.
|
||||
*
|
||||
* @section theme_intro Introduction
|
||||
*
|
||||
* The Theme provides a full color theme for painting the highlighted text.
|
||||
* One Theme is defined either as a *.theme file on disk, or as a file compiled
|
||||
* into the SyntaxHighlighting library by using Qt's resource system. Each
|
||||
* Theme has a unique name(), including a translatedName() if put into the UI.
|
||||
* Themes shipped by default are typically read-only, see isReadOnly().
|
||||
*
|
||||
* A Theme defines two sets of colors:
|
||||
* - Text colors, including foreground and background colors, colors for
|
||||
* selected text, and properties such as bold and italic. These colors are
|
||||
* used e.g. by the SyntaxHighlighter.
|
||||
* - Editor colors, including a background color for the entire editor widget,
|
||||
* the line number color, code folding colors, etc.
|
||||
*
|
||||
* @section theme_text_colors Text Colors and the Class Format
|
||||
*
|
||||
* The text colors are used for syntax highlighting.
|
||||
* // TODO: elaborate more and explain relation to Format class
|
||||
*
|
||||
* @section theme_editor_colors Editor Colors
|
||||
*
|
||||
* If you want to use the SyntaxHighlighting framework to write your own text
|
||||
* editor, you also need to paint the background of the editing widget. In
|
||||
* addition, the editor may support showing line numbers, a folding bar, a
|
||||
* highlight for the current text line, and similar features. All these colors
|
||||
* are defined in terms of the "editor colors" and accessible by calling
|
||||
* editorColor() with the desired enum EditorColorRole.
|
||||
*
|
||||
* @section theme_access Accessing a Theme
|
||||
*
|
||||
* All available Theme%s are accessed through the Repository. These themes are
|
||||
* typically valid themes. If you create a Theme on your own, isValid() will
|
||||
* return @e false, and all colors provided by this Theme are in fact invalid
|
||||
* and therefore unusable.
|
||||
*
|
||||
* @see Format
|
||||
* @since 5.28
|
||||
*/
|
||||
class KSYNTAXHIGHLIGHTING_EXPORT Theme
|
||||
{
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QString name READ name)
|
||||
Q_PROPERTY(QString translatedName READ translatedName)
|
||||
public:
|
||||
/**
|
||||
* Default styles that can be referenced from syntax definition XML files.
|
||||
* Make sure to choose readable colors with good contrast especially in
|
||||
* combination with the EditorColorRole%s.
|
||||
*/
|
||||
enum TextStyle {
|
||||
//! Default text style for normal text and source code without
|
||||
//! special highlighting.
|
||||
Normal = 0,
|
||||
//! Text style for language keywords.
|
||||
Keyword,
|
||||
//! Text style for function definitions and function calls.
|
||||
Function,
|
||||
//! Text style for variables, if applicable. For instance, variables in
|
||||
//! PHP typically start with a '$', so all identifiers following the
|
||||
//! pattern $foo are highlighted as variable.
|
||||
Variable,
|
||||
//! Text style for control flow highlighting, such as @e if, @e then,
|
||||
//! @e else, @e return, or @e continue.
|
||||
ControlFlow,
|
||||
//! Text style for operators such as +, -, *, / and :: etc.
|
||||
Operator,
|
||||
//! Text style for built-in language classes and functions.
|
||||
BuiltIn,
|
||||
//! Text style for well-known extensions, such as Qt or boost.
|
||||
Extension,
|
||||
//! Text style for preprocessor statements.
|
||||
Preprocessor,
|
||||
//! Text style for attributes of functions or objects, e.g. \@override
|
||||
//! in Java, or __declspec(...) and __attribute__((...)) in C++.
|
||||
Attribute,
|
||||
//! Text style for single characters such as 'a'.
|
||||
Char,
|
||||
//! Text style for escaped characters in strings, such as "hello\n".
|
||||
SpecialChar,
|
||||
//! Text style for strings, for instance "hello world".
|
||||
String,
|
||||
//! Text style for verbatim strings such as HERE docs.
|
||||
VerbatimString,
|
||||
//! Text style for special strings such as regular expressions in
|
||||
//! ECMAScript or the LaTeX math mode.
|
||||
SpecialString,
|
||||
//! Text style for includes, imports, modules, or LaTeX packages.
|
||||
Import,
|
||||
//! Text style for data types such as int, char, float etc.
|
||||
DataType,
|
||||
//! Text style for decimal values.
|
||||
DecVal,
|
||||
//! Text style for numbers with base other than 10.
|
||||
BaseN,
|
||||
//! Text style for floating point numbers.
|
||||
Float,
|
||||
//! Text style for language constants, e.g. True, False, None in Python
|
||||
//! or nullptr in C/C++.
|
||||
Constant,
|
||||
//! Text style for normal comments.
|
||||
Comment,
|
||||
//! Text style for comments that reflect API documentation, such as
|
||||
//! doxygen /** */ comments.
|
||||
Documentation,
|
||||
//! Text style for annotations in comments, such as \@param in Doxygen
|
||||
//! or JavaDoc.
|
||||
Annotation,
|
||||
//! Text style that refers to variables in a comment, such as after
|
||||
//! \@param \<identifier\> in Doxygen or JavaDoc.
|
||||
CommentVar,
|
||||
//! Text style for region markers, typically defined by BEGIN/END.
|
||||
RegionMarker,
|
||||
//! Text style for information, such as the keyword \@note in Doxygen.
|
||||
Information,
|
||||
//! Text style for warnings, such as the keyword \@warning in Doxygen.
|
||||
Warning,
|
||||
//! Text style for comment specials such as TODO and WARNING in
|
||||
//! comments.
|
||||
Alert,
|
||||
//! Text style indicating wrong syntax.
|
||||
Error,
|
||||
//! Text style for attributes that do not match any of the other default
|
||||
//! styles.
|
||||
Others
|
||||
};
|
||||
Q_ENUM(TextStyle)
|
||||
|
||||
/**
|
||||
* Editor color roles, used to paint line numbers, editor background etc.
|
||||
* The colors typically should have good contrast with the colors used
|
||||
* in the TextStyle%s.
|
||||
*/
|
||||
enum EditorColorRole {
|
||||
//! Background color for the editing area.
|
||||
BackgroundColor = 0,
|
||||
//! Background color for selected text.
|
||||
TextSelection,
|
||||
//! Background color for the line of the current text cursor.
|
||||
CurrentLine,
|
||||
//! Background color for matching text while searching.
|
||||
SearchHighlight,
|
||||
//! Background color for replaced text for a search & replace action.
|
||||
ReplaceHighlight,
|
||||
//! Background color for matching bracket pairs (including quotes)
|
||||
BracketMatching,
|
||||
//! Foreground color for visualizing tabs and trailing spaces.
|
||||
TabMarker,
|
||||
//! Color used to underline spell check errors.
|
||||
SpellChecking,
|
||||
//! Color used to draw vertical indentation levels, typically a line.
|
||||
IndentationLine,
|
||||
//! Background color for the icon border.
|
||||
IconBorder,
|
||||
//! Background colors for code folding regions in the text area, as well
|
||||
//! as code folding indicators in the code folding border.
|
||||
CodeFolding,
|
||||
//! Foreground color for drawing the line numbers. This should have a
|
||||
//! good contrast with the IconBorder background color.
|
||||
LineNumbers,
|
||||
//! Foreground color for drawing the current line number. This should
|
||||
//! have a good contrast with the IconBorder background color.
|
||||
CurrentLineNumber,
|
||||
//! Color used in the icon border to indicate dynamically wrapped lines.
|
||||
//! This color should have a good contrast with the IconBorder
|
||||
//! background color.
|
||||
WordWrapMarker,
|
||||
//! Color used to draw a vertical line for marking changed lines.
|
||||
ModifiedLines,
|
||||
//! Color used to draw a vertical line for marking saved lines.
|
||||
SavedLines,
|
||||
//! Line color used to draw separator lines, e.g. at column 80 in the
|
||||
//! text editor area.
|
||||
Separator,
|
||||
//! Background color for bookmarks.
|
||||
MarkBookmark,
|
||||
//! Background color for active breakpoints.
|
||||
MarkBreakpointActive,
|
||||
//! Background color for a reached breakpoint.
|
||||
MarkBreakpointReached,
|
||||
//! Background color for inactive (disabled) breakpoints.
|
||||
MarkBreakpointDisabled,
|
||||
//! Background color for marking the current execution position.
|
||||
MarkExecution,
|
||||
//! Background color for general warning marks.
|
||||
MarkWarning,
|
||||
//! Background color for general error marks.
|
||||
MarkError,
|
||||
//! Background color for text templates (snippets).
|
||||
TemplateBackground,
|
||||
//! Background color for all editable placeholders in text templates.
|
||||
TemplatePlaceholder,
|
||||
//! Background color for the currently active placeholder in text
|
||||
//! templates.
|
||||
TemplateFocusedPlaceholder,
|
||||
//! Background color for read-only placeholders in text templates.
|
||||
TemplateReadOnlyPlaceholder
|
||||
};
|
||||
Q_ENUM(EditorColorRole)
|
||||
|
||||
/**
|
||||
* Default constructor, creating an invalid Theme, see isValid().
|
||||
*/
|
||||
Theme();
|
||||
|
||||
/**
|
||||
* Copy constructor, sharing the Theme data with @p copy.
|
||||
*/
|
||||
Theme(const Theme ©);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~Theme();
|
||||
|
||||
/**
|
||||
* Assignment operator, sharing the Theme data with @p other.
|
||||
*/
|
||||
Theme &operator=(const Theme &other);
|
||||
|
||||
/**
|
||||
* Returns @c true if this is a valid Theme.
|
||||
* If the theme is invalid, none of the returned colors are well-defined.
|
||||
*/
|
||||
bool isValid() const;
|
||||
|
||||
/**
|
||||
* Returns the unique name of this Theme.
|
||||
* @see translatedName()
|
||||
*/
|
||||
QString name() const;
|
||||
|
||||
/**
|
||||
* Returns the translated name of this Theme. The translated name can be
|
||||
* used in the user interface.
|
||||
*/
|
||||
QString translatedName() const;
|
||||
|
||||
/**
|
||||
* Returns @c true if this Theme is read-only.
|
||||
*
|
||||
* A Theme is read-only, if the filePath() points to a non-writable file.
|
||||
* This is typically the case for Themes that are compiled into the executable
|
||||
* as resource file, as well as for theme files that are installed in read-only
|
||||
* system locations (e.g. /usr/share/).
|
||||
*/
|
||||
bool isReadOnly() const;
|
||||
|
||||
/**
|
||||
* Returns the full path and file name to this Theme.
|
||||
* Themes from the Qt resource return the Qt resource path.
|
||||
* Themes from disk return the local path.
|
||||
*
|
||||
* If the theme is invalid (isValid()), an empty string is returned.
|
||||
*/
|
||||
QString filePath() const;
|
||||
|
||||
/**
|
||||
* Returns the text color to be used for @p style.
|
||||
* @c 0 is returned for styles that do not specify a text color,
|
||||
* use the default text color in that case.
|
||||
*/
|
||||
QRgb textColor(TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns the selected text color to be used for @p style.
|
||||
* @c 0 is returned for styles that do not specify a selected text color,
|
||||
* use the default textColor() in that case.
|
||||
*/
|
||||
QRgb selectedTextColor(TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns the background color to be used for @p style.
|
||||
* @c 0 is returned for styles that do not specify a background color,
|
||||
* use the default background color in that case.
|
||||
*/
|
||||
QRgb backgroundColor(TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns the background color to be used for selected text for @p style.
|
||||
* @c 0 is returned for styles that do not specify a background color,
|
||||
* use the default backgroundColor() in that case.
|
||||
*/
|
||||
QRgb selectedBackgroundColor(TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns whether the given style should be shown in bold.
|
||||
*/
|
||||
bool isBold(TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns whether the given style should be shown in italic.
|
||||
*/
|
||||
bool isItalic(TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns whether the given style should be shown underlined.
|
||||
*/
|
||||
bool isUnderline(TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns whether the given style should be shown struck through.
|
||||
*/
|
||||
bool isStrikeThrough(TextStyle style) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns the editor color for the requested @p role.
|
||||
*/
|
||||
QRgb editorColor(EditorColorRole role) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Constructor taking a shared ThemeData instance.
|
||||
*/
|
||||
KSYNTAXHIGHLIGHTING_NO_EXPORT explicit Theme(ThemeData *data);
|
||||
friend class RepositoryPrivate;
|
||||
friend class ThemeData;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Shared data holder.
|
||||
*/
|
||||
QExplicitlySharedDataPointer<ThemeData> m_data;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
Q_DECLARE_TYPEINFO(KSyntaxHighlighting::Theme, Q_RELOCATABLE_TYPE);
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_THEME_H
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2016 Dominik Haumann <dhaumann@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "ksyntaxhighlighting_logging.h"
|
||||
#include "themedata_p.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QMetaEnum>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
ThemeData::ThemeData()
|
||||
{
|
||||
memset(m_editorColors, 0, sizeof(m_editorColors));
|
||||
m_textStyles.resize(QMetaEnum::fromType<Theme::TextStyle>().keyCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert QJsonValue @p val into a color, if possible. Valid colors are only
|
||||
* in hex format: #aarrggbb. On error, returns 0x00000000.
|
||||
*/
|
||||
static inline QRgb readColor(const QJsonValue &val)
|
||||
{
|
||||
const QRgb unsetColor = 0;
|
||||
if (!val.isString()) {
|
||||
return unsetColor;
|
||||
}
|
||||
const QString str = val.toString();
|
||||
if (str.isEmpty() || str[0] != QLatin1Char('#')) {
|
||||
return unsetColor;
|
||||
}
|
||||
const QColor color(str);
|
||||
return color.isValid() ? color.rgba() : unsetColor;
|
||||
}
|
||||
|
||||
static inline TextStyleData readThemeData(const QJsonObject &obj)
|
||||
{
|
||||
TextStyleData td;
|
||||
|
||||
td.textColor = readColor(obj.value(QLatin1String("text-color")));
|
||||
td.backgroundColor = readColor(obj.value(QLatin1String("background-color")));
|
||||
td.selectedTextColor = readColor(obj.value(QLatin1String("selected-text-color")));
|
||||
td.selectedBackgroundColor = readColor(obj.value(QLatin1String("selected-background-color")));
|
||||
|
||||
auto val = obj.value(QLatin1String("bold"));
|
||||
if (val.isBool()) {
|
||||
td.bold = val.toBool();
|
||||
td.hasBold = true;
|
||||
}
|
||||
val = obj.value(QLatin1String("italic"));
|
||||
if (val.isBool()) {
|
||||
td.italic = val.toBool();
|
||||
td.hasItalic = true;
|
||||
}
|
||||
val = obj.value(QLatin1String("underline"));
|
||||
if (val.isBool()) {
|
||||
td.underline = val.toBool();
|
||||
td.hasUnderline = true;
|
||||
}
|
||||
val = obj.value(QLatin1String("strike-through"));
|
||||
if (val.isBool()) {
|
||||
td.strikeThrough = val.toBool();
|
||||
td.hasStrikeThrough = true;
|
||||
}
|
||||
|
||||
return td;
|
||||
}
|
||||
|
||||
bool ThemeData::load(const QString &filePath)
|
||||
{
|
||||
// flag first as done for the error cases
|
||||
m_completelyLoaded = true;
|
||||
|
||||
QFile loadFile(filePath);
|
||||
if (!loadFile.open(QIODevice::ReadOnly)) {
|
||||
return false;
|
||||
}
|
||||
const QByteArray jsonData = loadFile.readAll();
|
||||
// look for metadata object
|
||||
int metaDataStart = jsonData.indexOf("\"metadata\"");
|
||||
int start = jsonData.indexOf('{', metaDataStart);
|
||||
int end = jsonData.indexOf("}", metaDataStart);
|
||||
if (start < 0 || end < 0) {
|
||||
qCWarning(Log) << "Failed to parse theme file" << filePath << ":"
|
||||
<< "no metadata object found";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData.sliced(start, (end + 1) - start), &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qCWarning(Log) << "Failed to parse theme file" << filePath << ":" << parseError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_filePath = filePath;
|
||||
|
||||
// we need more data later
|
||||
m_completelyLoaded = false;
|
||||
|
||||
// read metadata
|
||||
QJsonObject metadata = jsonDoc.object();
|
||||
m_name = metadata.value(QLatin1String("name")).toString();
|
||||
m_revision = metadata.value(QLatin1String("revision")).toInt();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ThemeData::loadComplete()
|
||||
{
|
||||
if (m_completelyLoaded) {
|
||||
return;
|
||||
}
|
||||
m_completelyLoaded = true;
|
||||
|
||||
QFile loadFile(m_filePath);
|
||||
if (!loadFile.open(QIODevice::ReadOnly)) {
|
||||
return;
|
||||
}
|
||||
const QByteArray jsonData = loadFile.readAll();
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qCWarning(Log) << "Failed to parse theme file" << m_filePath << ":" << parseError.errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject obj = jsonDoc.object();
|
||||
// read text styles
|
||||
const auto metaEnumStyle = QMetaEnum::fromType<Theme::TextStyle>();
|
||||
const QJsonObject textStyles = obj.value(QLatin1String("text-styles")).toObject();
|
||||
for (int i = 0; i < metaEnumStyle.keyCount(); ++i) {
|
||||
Q_ASSERT(i == metaEnumStyle.value(i));
|
||||
m_textStyles[i] = readThemeData(textStyles.value(QLatin1String(metaEnumStyle.key(i))).toObject());
|
||||
}
|
||||
|
||||
// read editor colors
|
||||
const auto metaEnumColor = QMetaEnum::fromType<Theme::EditorColorRole>();
|
||||
const QJsonObject editorColors = obj.value(QLatin1String("editor-colors")).toObject();
|
||||
for (int i = 0; i < metaEnumColor.keyCount(); ++i) {
|
||||
Q_ASSERT(i == metaEnumColor.value(i));
|
||||
m_editorColors[i] = readColor(editorColors.value(QLatin1String(metaEnumColor.key(i))));
|
||||
}
|
||||
|
||||
// if we have no new key around for Theme::BackgroundColor => use old variants to be compatible
|
||||
if (!editorColors.contains(QLatin1String(metaEnumColor.key(Theme::BackgroundColor)))) {
|
||||
m_editorColors[Theme::BackgroundColor] = readColor(editorColors.value(QLatin1String("background-color")));
|
||||
m_editorColors[Theme::TextSelection] = readColor(editorColors.value(QLatin1String("selection")));
|
||||
m_editorColors[Theme::CurrentLine] = readColor(editorColors.value(QLatin1String("current-line")));
|
||||
m_editorColors[Theme::SearchHighlight] = readColor(editorColors.value(QLatin1String("search-highlight")));
|
||||
m_editorColors[Theme::ReplaceHighlight] = readColor(editorColors.value(QLatin1String("replace-highlight")));
|
||||
m_editorColors[Theme::BracketMatching] = readColor(editorColors.value(QLatin1String("bracket-matching")));
|
||||
m_editorColors[Theme::TabMarker] = readColor(editorColors.value(QLatin1String("tab-marker")));
|
||||
m_editorColors[Theme::SpellChecking] = readColor(editorColors.value(QLatin1String("spell-checking")));
|
||||
m_editorColors[Theme::IndentationLine] = readColor(editorColors.value(QLatin1String("indentation-line")));
|
||||
m_editorColors[Theme::IconBorder] = readColor(editorColors.value(QLatin1String("icon-border")));
|
||||
m_editorColors[Theme::CodeFolding] = readColor(editorColors.value(QLatin1String("code-folding")));
|
||||
m_editorColors[Theme::LineNumbers] = readColor(editorColors.value(QLatin1String("line-numbers")));
|
||||
m_editorColors[Theme::CurrentLineNumber] = readColor(editorColors.value(QLatin1String("current-line-number")));
|
||||
m_editorColors[Theme::WordWrapMarker] = readColor(editorColors.value(QLatin1String("word-wrap-marker")));
|
||||
m_editorColors[Theme::ModifiedLines] = readColor(editorColors.value(QLatin1String("modified-lines")));
|
||||
m_editorColors[Theme::SavedLines] = readColor(editorColors.value(QLatin1String("saved-lines")));
|
||||
m_editorColors[Theme::Separator] = readColor(editorColors.value(QLatin1String("separator")));
|
||||
m_editorColors[Theme::MarkBookmark] = readColor(editorColors.value(QLatin1String("mark-bookmark")));
|
||||
m_editorColors[Theme::MarkBreakpointActive] = readColor(editorColors.value(QLatin1String("mark-breakpoint-active")));
|
||||
m_editorColors[Theme::MarkBreakpointReached] = readColor(editorColors.value(QLatin1String("mark-breakpoint-reached")));
|
||||
m_editorColors[Theme::MarkBreakpointDisabled] = readColor(editorColors.value(QLatin1String("mark-breakpoint-disabled")));
|
||||
m_editorColors[Theme::MarkExecution] = readColor(editorColors.value(QLatin1String("mark-execution")));
|
||||
m_editorColors[Theme::MarkWarning] = readColor(editorColors.value(QLatin1String("mark-warning")));
|
||||
m_editorColors[Theme::MarkError] = readColor(editorColors.value(QLatin1String("mark-error")));
|
||||
m_editorColors[Theme::TemplateBackground] = readColor(editorColors.value(QLatin1String("template-background")));
|
||||
m_editorColors[Theme::TemplatePlaceholder] = readColor(editorColors.value(QLatin1String("template-placeholder")));
|
||||
m_editorColors[Theme::TemplateFocusedPlaceholder] = readColor(editorColors.value(QLatin1String("template-focused-placeholder")));
|
||||
m_editorColors[Theme::TemplateReadOnlyPlaceholder] = readColor(editorColors.value(QLatin1String("template-read-only-placeholder")));
|
||||
}
|
||||
|
||||
// read per-definition style overrides
|
||||
const auto customStyles = obj.value(QLatin1String("custom-styles")).toObject();
|
||||
for (auto it = customStyles.begin(); it != customStyles.end(); ++it) {
|
||||
const auto obj = it.value().toObject();
|
||||
auto &overrideStyle = m_textStyleOverrides[it.key()];
|
||||
for (auto it2 = obj.begin(); it2 != obj.end(); ++it2) {
|
||||
overrideStyle.insert(it2.key(), readThemeData(it2.value().toObject()));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QString ThemeData::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
int ThemeData::revision() const
|
||||
{
|
||||
return m_revision;
|
||||
}
|
||||
|
||||
bool ThemeData::isReadOnly() const
|
||||
{
|
||||
return !QFileInfo(m_filePath).isWritable();
|
||||
}
|
||||
|
||||
QString ThemeData::filePath() const
|
||||
{
|
||||
return m_filePath;
|
||||
}
|
||||
|
||||
TextStyleData ThemeData::textStyle(Theme::TextStyle style) const
|
||||
{
|
||||
if (!m_completelyLoaded) {
|
||||
const_cast<ThemeData *>(this)->loadComplete();
|
||||
}
|
||||
return m_textStyles[style];
|
||||
}
|
||||
|
||||
QRgb ThemeData::textColor(Theme::TextStyle style) const
|
||||
{
|
||||
return textStyle(style).textColor;
|
||||
}
|
||||
|
||||
QRgb ThemeData::selectedTextColor(Theme::TextStyle style) const
|
||||
{
|
||||
return textStyle(style).selectedTextColor;
|
||||
}
|
||||
|
||||
QRgb ThemeData::backgroundColor(Theme::TextStyle style) const
|
||||
{
|
||||
return textStyle(style).backgroundColor;
|
||||
}
|
||||
|
||||
QRgb ThemeData::selectedBackgroundColor(Theme::TextStyle style) const
|
||||
{
|
||||
return textStyle(style).selectedBackgroundColor;
|
||||
}
|
||||
|
||||
bool ThemeData::isBold(Theme::TextStyle style) const
|
||||
{
|
||||
return textStyle(style).bold;
|
||||
}
|
||||
|
||||
bool ThemeData::isItalic(Theme::TextStyle style) const
|
||||
{
|
||||
return textStyle(style).italic;
|
||||
}
|
||||
|
||||
bool ThemeData::isUnderline(Theme::TextStyle style) const
|
||||
{
|
||||
return textStyle(style).underline;
|
||||
}
|
||||
|
||||
bool ThemeData::isStrikeThrough(Theme::TextStyle style) const
|
||||
{
|
||||
return textStyle(style).strikeThrough;
|
||||
}
|
||||
|
||||
QRgb ThemeData::editorColor(Theme::EditorColorRole role) const
|
||||
{
|
||||
if (!m_completelyLoaded) {
|
||||
const_cast<ThemeData *>(this)->loadComplete();
|
||||
}
|
||||
Q_ASSERT(static_cast<int>(role) >= 0 && static_cast<int>(role) <= static_cast<int>(Theme::TemplateReadOnlyPlaceholder));
|
||||
return m_editorColors[role];
|
||||
}
|
||||
|
||||
TextStyleData ThemeData::textStyleOverride(const QString &definitionName, const QString &attributeName) const
|
||||
{
|
||||
if (!m_completelyLoaded) {
|
||||
const_cast<ThemeData *>(this)->loadComplete();
|
||||
}
|
||||
auto it = m_textStyleOverrides.find(definitionName);
|
||||
if (it != m_textStyleOverrides.end()) {
|
||||
return it->value(attributeName);
|
||||
}
|
||||
return TextStyleData();
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
SPDX-FileCopyrightText: 2016 Dominik Haumann <dhaumann@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_THEMEDATA_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_THEMEDATA_P_H
|
||||
|
||||
#include "textstyledata_p.h"
|
||||
#include "theme.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QSharedData>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
/**
|
||||
* Data container for a Theme.
|
||||
*/
|
||||
class ThemeData : public QSharedData
|
||||
{
|
||||
public:
|
||||
static ThemeData *get(const Theme &theme)
|
||||
{
|
||||
return theme.m_data.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor, creating an uninitialized ThemeData instance.
|
||||
*/
|
||||
ThemeData();
|
||||
|
||||
/**
|
||||
* Load the Theme data from the file @p filePath.
|
||||
* Note, that @p filePath either is a local file, or a qt resource location.
|
||||
*/
|
||||
bool load(const QString &filePath);
|
||||
|
||||
void loadComplete();
|
||||
|
||||
/**
|
||||
* Returns the unique name of this Theme.
|
||||
*/
|
||||
QString name() const;
|
||||
|
||||
/**
|
||||
* Returns the revision of this Theme.
|
||||
* The revision in a .theme file should be increased with every change.
|
||||
*/
|
||||
int revision() const;
|
||||
|
||||
/**
|
||||
* Returns @c true if this Theme is read-only.
|
||||
* Typically, themes that are shipped by default are read-only.
|
||||
*/
|
||||
bool isReadOnly() const;
|
||||
|
||||
/**
|
||||
* Returns the full path and filename to this Theme.
|
||||
* Themes from the Qt resource return the Qt resource path.
|
||||
* Themes from disk return the local path.
|
||||
*
|
||||
* If the theme is invalid (isValid()), an empty string is returned.
|
||||
*/
|
||||
QString filePath() const;
|
||||
|
||||
/**
|
||||
* Returns the text color to be used for @p style.
|
||||
* @c 0 is returned for styles that do not specify a text color,
|
||||
* use the default text color in that case.
|
||||
*/
|
||||
QRgb textColor(Theme::TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns the text color for selected to be used for @p style.
|
||||
* @c 0 is returned for styles that do not specify a selected text color,
|
||||
* use the textColor() in that case.
|
||||
*/
|
||||
QRgb selectedTextColor(Theme::TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns the background color to be used for @p style.
|
||||
* @c 0 is returned for styles that do not specify a background color,
|
||||
* use the default background color in that case.
|
||||
*/
|
||||
QRgb backgroundColor(Theme::TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns the background color for selected text to be used for @p style.
|
||||
* @c 0 is returned for styles that do not specify a selected background
|
||||
* color, use the default backgroundColor() in that case.
|
||||
*/
|
||||
QRgb selectedBackgroundColor(Theme::TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns whether the given style should be shown in bold.
|
||||
*/
|
||||
bool isBold(Theme::TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns whether the given style should be shown in italic.
|
||||
*/
|
||||
bool isItalic(Theme::TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns whether the given style should be shown underlined.
|
||||
*/
|
||||
bool isUnderline(Theme::TextStyle style) const;
|
||||
|
||||
/**
|
||||
* Returns whether the given style should be shown struck through.
|
||||
*/
|
||||
bool isStrikeThrough(Theme::TextStyle style) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns the editor color for the requested @p role.
|
||||
*/
|
||||
QRgb editorColor(Theme::EditorColorRole role) const;
|
||||
|
||||
/**
|
||||
* Returns the TextStyle override of a specific "itemData" with attributeName
|
||||
* in the syntax definition called definitionName.
|
||||
*
|
||||
* If no override exists, a valid TextStyleData with the respective default
|
||||
* TextStyle will be used, so the returned value is always valid.
|
||||
*/
|
||||
TextStyleData textStyleOverride(const QString &definitionName, const QString &attributeName) const;
|
||||
|
||||
/**
|
||||
* Returns the TextStyle data for the given @p style.
|
||||
*/
|
||||
TextStyleData textStyle(Theme::TextStyle style) const;
|
||||
|
||||
private:
|
||||
int m_revision = 0;
|
||||
QString m_name;
|
||||
|
||||
//! Path to the file where the theme came from.
|
||||
//! This is either a resource location (":/themes/Default.theme"), or a file
|
||||
//! on disk (in a read-only or a writeable location).
|
||||
QString m_filePath;
|
||||
|
||||
// default is loaded, we have no file per default, set to false on start of load
|
||||
bool m_completelyLoaded = true;
|
||||
|
||||
//! TextStyles
|
||||
std::vector<TextStyleData> m_textStyles;
|
||||
|
||||
//! style overrides for individual itemData entries
|
||||
//! definition name -> attribute name -> style
|
||||
QHash<QString, QHash<QString, TextStyleData>> m_textStyleOverrides;
|
||||
|
||||
//! Editor area colors
|
||||
QRgb m_editorColors[Theme::TemplateReadOnlyPlaceholder + 1];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
Q_DECLARE_TYPEINFO(KSyntaxHighlighting::TextStyleData, Q_RELOCATABLE_TYPE);
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_THEMEDATA_P_H
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2007 Sebastian Pipping <webmaster@hartwork.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "wildcardmatcher.h"
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
#include <QChar>
|
||||
|
||||
namespace
|
||||
{
|
||||
bool wildcardMatch(QStringView candidate, QStringView wildcard, int candidatePosFromRight, int wildcardPosFromRight)
|
||||
{
|
||||
for (; wildcardPosFromRight >= 0; wildcardPosFromRight--) {
|
||||
const auto ch = wildcard.at(wildcardPosFromRight).unicode();
|
||||
switch (ch) {
|
||||
case L'*':
|
||||
if (candidatePosFromRight == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (wildcardPosFromRight == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Eat all we can and go back as far as we have to
|
||||
for (int j = -1; j <= candidatePosFromRight; j++) {
|
||||
if (wildcardMatch(candidate, wildcard, j, wildcardPosFromRight - 1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
case L'?':
|
||||
if (candidatePosFromRight == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
candidatePosFromRight--;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (candidatePosFromRight == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto candidateCh = candidate.at(candidatePosFromRight).unicode();
|
||||
if (candidateCh == ch) {
|
||||
candidatePosFromRight--;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return candidatePosFromRight == -1;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
bool WildcardMatcher::exactMatch(QStringView candidate, QStringView wildcard)
|
||||
{
|
||||
return ::wildcardMatch(candidate, wildcard, candidate.length() - 1, wildcard.length() - 1);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2007 Sebastian Pipping <webmaster@hartwork.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_WILDCARDMATCHER_H
|
||||
#define KSYNTAXHIGHLIGHTING_WILDCARDMATCHER_H
|
||||
|
||||
#include "ksyntaxhighlighting_export.h"
|
||||
|
||||
#include <QStringView>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
namespace WildcardMatcher
|
||||
{
|
||||
/**
|
||||
* Matches a string against a given wildcard case-sensitively.
|
||||
* The wildcard supports '*' (".*" in regex) and '?' ("." in regex), not more.
|
||||
*
|
||||
* @param candidate Text to match
|
||||
* @param wildcard Wildcard to use
|
||||
* @return True for an exact match, false otherwise
|
||||
*
|
||||
* @since 5.86
|
||||
*/
|
||||
KSYNTAXHIGHLIGHTING_EXPORT bool exactMatch(QStringView candidate, QStringView wildcard);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // KSYNTAXHIGHLIGHTING_WILDCARDMATCHER_H
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "worddelimiters_p.h"
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
WordDelimiters::WordDelimiters()
|
||||
: asciiDelimiters{}
|
||||
{
|
||||
for (const char *p = "\t !%&()*+,-./:;<=>?[\\]^{|}~"; *p; ++p) {
|
||||
asciiDelimiters.set(*p);
|
||||
}
|
||||
}
|
||||
|
||||
WordDelimiters::WordDelimiters(QStringView str)
|
||||
: asciiDelimiters{}
|
||||
{
|
||||
append(str);
|
||||
}
|
||||
|
||||
bool WordDelimiters::contains(QChar c) const
|
||||
{
|
||||
if (c.unicode() < 128) {
|
||||
return asciiDelimiters.test(c.unicode());
|
||||
}
|
||||
// perf tells contains is MUCH faster than binary search here, very short array
|
||||
return notAsciiDelimiters.contains(c);
|
||||
}
|
||||
|
||||
void WordDelimiters::append(QStringView s)
|
||||
{
|
||||
for (QChar c : s) {
|
||||
if (c.unicode() < 128) {
|
||||
asciiDelimiters.set(c.unicode());
|
||||
} else {
|
||||
notAsciiDelimiters.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WordDelimiters::remove(QStringView s)
|
||||
{
|
||||
for (QChar c : s) {
|
||||
if (c.unicode() < 128) {
|
||||
asciiDelimiters.set(c.unicode(), false);
|
||||
} else {
|
||||
notAsciiDelimiters.remove(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_WORDDELIMITERS_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_WORDDELIMITERS_P_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <bitset>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
/**
|
||||
* Represents a list of character that separates 2 words.
|
||||
*
|
||||
* Default delimiters are .():!+*,-<=>%&/;?[]^{|}~\, space (' ') and tabulator ('\t').
|
||||
*
|
||||
* @see Rule
|
||||
* @since 5.74
|
||||
*/
|
||||
class WordDelimiters
|
||||
{
|
||||
public:
|
||||
WordDelimiters();
|
||||
|
||||
/**
|
||||
* Initialize with a default delimiters.
|
||||
*/
|
||||
explicit WordDelimiters(QStringView str);
|
||||
|
||||
/**
|
||||
* Returns @c true if @p c is a word delimiter; otherwise returns @c false.
|
||||
*/
|
||||
bool contains(QChar c) const;
|
||||
|
||||
/**
|
||||
* Appends each character of @p s to word delimiters.
|
||||
*/
|
||||
void append(QStringView s);
|
||||
|
||||
/**
|
||||
* Removes each character of @p s from word delimiters.
|
||||
*/
|
||||
void remove(QStringView c);
|
||||
|
||||
private:
|
||||
/**
|
||||
* An array which represents ascii characters for very fast lookup.
|
||||
* The character is used as an index and the value @c true indicates a word delimiter.
|
||||
*/
|
||||
std::bitset<128> asciiDelimiters;
|
||||
|
||||
/**
|
||||
* Contains characters that are not ascii and is empty for most syntax definition.
|
||||
*/
|
||||
QString notAsciiDelimiters;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KSYNTAXHIGHLIGHTING_XML_P_H
|
||||
#define KSYNTAXHIGHLIGHTING_XML_P_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
/** Utilities for XML parsing. */
|
||||
namespace Xml
|
||||
{
|
||||
/** Parse a xs:boolean attribute. */
|
||||
inline bool attrToBool(QStringView str)
|
||||
{
|
||||
return str == QStringLiteral("1") || str.compare(QStringLiteral("true"), Qt::CaseInsensitive) == 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
# SPDX-FileCopyrightText: 2018 Eike Hein <hein@kde.org>
|
||||
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
ecm_add_qml_module(kquicksyntaxhighlightingplugin URI "org.kde.syntaxhighlighting")
|
||||
|
||||
target_sources(kquicksyntaxhighlightingplugin PRIVATE
|
||||
kquicksyntaxhighlightingplugin.cpp
|
||||
kquicksyntaxhighlighter.cpp
|
||||
)
|
||||
target_link_libraries(kquicksyntaxhighlightingplugin PRIVATE
|
||||
KF6SyntaxHighlighting
|
||||
Qt6::Quick
|
||||
)
|
||||
|
||||
ecm_finalize_qml_module(kquicksyntaxhighlightingplugin DESTINATION ${KDE_INSTALL_QMLDIR})
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2018 Eike Hein <hein@kde.org>
|
||||
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "kquicksyntaxhighlighter.h"
|
||||
|
||||
#include <KSyntaxHighlighting/Repository>
|
||||
#include <KSyntaxHighlighting/SyntaxHighlighter>
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QPalette>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QTextDocument>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
extern Repository *defaultRepository();
|
||||
|
||||
KQuickSyntaxHighlighter::KQuickSyntaxHighlighter(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_textEdit(nullptr)
|
||||
, m_highlighter(new KSyntaxHighlighting::SyntaxHighlighter(this))
|
||||
{
|
||||
}
|
||||
|
||||
KQuickSyntaxHighlighter::~KQuickSyntaxHighlighter() = default;
|
||||
|
||||
QObject *KQuickSyntaxHighlighter::textEdit() const
|
||||
{
|
||||
return m_textEdit;
|
||||
}
|
||||
|
||||
void KQuickSyntaxHighlighter::setTextEdit(QObject *textEdit)
|
||||
{
|
||||
if (m_textEdit != textEdit) {
|
||||
m_textEdit = textEdit;
|
||||
m_highlighter->setDocument(m_textEdit->property("textDocument").value<QQuickTextDocument *>()->textDocument());
|
||||
}
|
||||
}
|
||||
|
||||
QVariant KQuickSyntaxHighlighter::definition() const
|
||||
{
|
||||
return QVariant::fromValue(m_definition);
|
||||
}
|
||||
|
||||
void KQuickSyntaxHighlighter::setDefinition(const QVariant &definition)
|
||||
{
|
||||
Definition def;
|
||||
if (definition.userType() == QMetaType::QString) {
|
||||
def = unwrappedRepository()->definitionForName(definition.toString());
|
||||
} else {
|
||||
def = definition.value<Definition>();
|
||||
}
|
||||
|
||||
if (m_definition != def) {
|
||||
m_definition = def;
|
||||
|
||||
m_highlighter->setTheme(m_theme.isValid() ? m_theme : unwrappedRepository()->themeForPalette(QGuiApplication::palette()));
|
||||
m_highlighter->setDefinition(def);
|
||||
|
||||
Q_EMIT definitionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant KQuickSyntaxHighlighter::theme() const
|
||||
{
|
||||
return QVariant::fromValue(m_theme);
|
||||
}
|
||||
|
||||
void KQuickSyntaxHighlighter::setTheme(const QVariant &theme)
|
||||
{
|
||||
Theme t;
|
||||
if (theme.userType() == QMetaType::QString) {
|
||||
t = unwrappedRepository()->theme(theme.toString());
|
||||
} else if (theme.userType() == QMetaType::Int) {
|
||||
t = unwrappedRepository()->defaultTheme(static_cast<Repository::DefaultTheme>(theme.toInt()));
|
||||
} else {
|
||||
t = theme.value<Theme>();
|
||||
}
|
||||
|
||||
if (m_theme.name() != t.name()) {
|
||||
m_theme = t;
|
||||
m_highlighter->setTheme(m_theme);
|
||||
m_highlighter->rehighlight();
|
||||
Q_EMIT themeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
Repository *KQuickSyntaxHighlighter::repository() const
|
||||
{
|
||||
return m_repository;
|
||||
}
|
||||
|
||||
void KQuickSyntaxHighlighter::setRepository(Repository *repository)
|
||||
{
|
||||
if (m_repository == repository) {
|
||||
return;
|
||||
}
|
||||
m_repository = repository;
|
||||
Q_EMIT repositoryChanged();
|
||||
}
|
||||
|
||||
Repository *KQuickSyntaxHighlighter::unwrappedRepository() const
|
||||
{
|
||||
if (m_repository) {
|
||||
return m_repository;
|
||||
}
|
||||
return defaultRepository();
|
||||
}
|
||||
|
||||
#include "moc_kquicksyntaxhighlighter.cpp"
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2018 Eike Hein <hein@kde.org>
|
||||
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KQUICKSYNTAXHIGHLIGHTER_H
|
||||
#define KQUICKSYNTAXHIGHLIGHTER_H
|
||||
|
||||
#include <KSyntaxHighlighting/Definition>
|
||||
#include <KSyntaxHighlighting/Theme>
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
|
||||
namespace KSyntaxHighlighting
|
||||
{
|
||||
class Repository;
|
||||
class SyntaxHighlighter;
|
||||
}
|
||||
|
||||
class KQuickSyntaxHighlighter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QObject *textEdit READ textEdit WRITE setTextEdit NOTIFY textEditChanged)
|
||||
Q_PROPERTY(QVariant definition READ definition WRITE setDefinition NOTIFY definitionChanged)
|
||||
Q_PROPERTY(QVariant theme READ theme WRITE setTheme NOTIFY themeChanged)
|
||||
Q_PROPERTY(KSyntaxHighlighting::Repository *repository READ repository WRITE setRepository NOTIFY repositoryChanged)
|
||||
|
||||
public:
|
||||
explicit KQuickSyntaxHighlighter(QObject *parent = nullptr);
|
||||
~KQuickSyntaxHighlighter() override;
|
||||
|
||||
QObject *textEdit() const;
|
||||
void setTextEdit(QObject *textEdit);
|
||||
|
||||
QVariant definition() const;
|
||||
void setDefinition(const QVariant &definition);
|
||||
|
||||
QVariant theme() const;
|
||||
void setTheme(const QVariant &theme);
|
||||
|
||||
KSyntaxHighlighting::Repository *repository() const;
|
||||
void setRepository(KSyntaxHighlighting::Repository *repository);
|
||||
|
||||
Q_SIGNALS:
|
||||
void textEditChanged() const;
|
||||
void definitionChanged() const;
|
||||
void themeChanged();
|
||||
void repositoryChanged();
|
||||
|
||||
private:
|
||||
KSyntaxHighlighting::Repository *unwrappedRepository() const;
|
||||
|
||||
QObject *m_textEdit;
|
||||
KSyntaxHighlighting::Definition m_definition;
|
||||
KSyntaxHighlighting::Theme m_theme;
|
||||
KSyntaxHighlighting::Repository *m_repository = nullptr;
|
||||
KSyntaxHighlighting::SyntaxHighlighter *m_highlighter = nullptr;
|
||||
};
|
||||
|
||||
#endif // KQUICKSYNTAXHIGHLIGHTER_H
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2018 Eike Hein <hein@kde.org>
|
||||
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "kquicksyntaxhighlightingplugin.h"
|
||||
#include "kquicksyntaxhighlighter.h"
|
||||
|
||||
#include <KSyntaxHighlighting/Definition>
|
||||
#include <KSyntaxHighlighting/Repository>
|
||||
#include <KSyntaxHighlighting/Theme>
|
||||
|
||||
#include <memory>
|
||||
|
||||
using namespace KSyntaxHighlighting;
|
||||
|
||||
Repository *defaultRepository()
|
||||
{
|
||||
static std::unique_ptr<Repository> s_instance;
|
||||
if (!s_instance) {
|
||||
s_instance = std::make_unique<Repository>();
|
||||
}
|
||||
return s_instance.get();
|
||||
}
|
||||
|
||||
void KQuickSyntaxHighlightingPlugin::registerTypes(const char *uri)
|
||||
{
|
||||
Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.syntaxhighlighting"));
|
||||
qRegisterMetaType<Definition>();
|
||||
qRegisterMetaType<QList<Definition>>();
|
||||
qRegisterMetaType<Theme>();
|
||||
qRegisterMetaType<QList<Theme>>();
|
||||
qmlRegisterType<KQuickSyntaxHighlighter>(uri, 1, 0, "SyntaxHighlighter");
|
||||
qmlRegisterUncreatableMetaObject(Definition::staticMetaObject, uri, 1, 0, "Definition", {});
|
||||
qmlRegisterUncreatableMetaObject(Theme::staticMetaObject, uri, 1, 0, "Theme", {});
|
||||
qmlRegisterSingletonType<Repository>(uri, 1, 0, "Repository", [](auto engine, auto scriptEngine) {
|
||||
(void)engine;
|
||||
auto repo = defaultRepository();
|
||||
scriptEngine->setObjectOwnership(repo, QJSEngine::CppOwnership);
|
||||
return defaultRepository();
|
||||
});
|
||||
}
|
||||
|
||||
#include "moc_kquicksyntaxhighlightingplugin.cpp"
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2018 Eike Hein <hein@kde.org>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#ifndef KQUICKSYNTAXHIGHLIGHTINGPLUGIN_H
|
||||
#define KQUICKSYNTAXHIGHLIGHTINGPLUGIN_H
|
||||
|
||||
#include <QQmlEngine>
|
||||
#include <QQmlExtensionPlugin>
|
||||
|
||||
class KQuickSyntaxHighlightingPlugin : public QQmlExtensionPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
|
||||
|
||||
public:
|
||||
void registerTypes(const char *uri) override;
|
||||
};
|
||||
|
||||
#endif // KQUICKSYNTAXHIGHLIGHTINGPLUGIN_H
|
||||
Reference in New Issue
Block a user