Editor: update ksyntaxhighlighting engine to v5.90.0

Task-number: QTCREATORBUG-22558
Change-Id: I7314c146a6de359ea8d60750d8a2c8e972b33fc9
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
David Schulz
2022-02-09 09:13:05 +01:00
parent 12c63bbce9
commit 37f8fcd791
37 changed files with 7284 additions and 1440 deletions

View File

@@ -29,6 +29,7 @@ add_qtc_library(KSyntaxHighlighting SHARED
src/lib/foldingregion.cpp src/lib/foldingregion.h
src/lib/format.cpp src/lib/format.h src/lib/format_p.h
src/lib/htmlhighlighter.cpp src/lib/htmlhighlighter.h
src/lib/highlightingdata.cpp src/lib/highlightingdata_p.hpp
src/lib/keywordlist.cpp src/lib/keywordlist_p.h
src/lib/matchresult_p.h
src/lib/repository.cpp src/lib/repository.h src/lib/repository_p.h

View File

@@ -3,10 +3,10 @@
#ifndef SyntaxHighlighting_VERSION_H
#define SyntaxHighlighting_VERSION_H
#define SyntaxHighlighting_VERSION_STRING "5.87.0"
#define SyntaxHighlighting_VERSION_STRING "5.90.0"
#define SyntaxHighlighting_VERSION_MAJOR 5
#define SyntaxHighlighting_VERSION_MINOR 87
#define SyntaxHighlighting_VERSION_MINOR 90
#define SyntaxHighlighting_VERSION_PATCH 0
#define SyntaxHighlighting_VERSION ((5<<16)|(87<<8)|(0))
#define SyntaxHighlighting_VERSION ((5<<16)|(90<<8)|(0))
#endif

View File

@@ -88,7 +88,7 @@
#define KSYNTAXHIGHLIGHTING_BUILD_DEPRECATED_SINCE(major, minor) 1
#ifdef KSYNTAXHIGHLIGHTING_NO_DEPRECATED
# define KSYNTAXHIGHLIGHTING_DISABLE_DEPRECATED_BEFORE_AND_AT 0x55700
# define KSYNTAXHIGHLIGHTING_DISABLE_DEPRECATED_BEFORE_AND_AT 0x55a00
#endif
#ifdef KSYNTAXHIGHLIGHTING_NO_DEPRECATED_WARNINGS
# define KSYNTAXHIGHLIGHTING_DEPRECATED_WARNINGS_SINCE 0
@@ -98,7 +98,7 @@
# ifdef KSYNTAXHIGHLIGHTING_DISABLE_DEPRECATED_BEFORE_AND_AT
# define KSYNTAXHIGHLIGHTING_DEPRECATED_WARNINGS_SINCE KSYNTAXHIGHLIGHTING_DISABLE_DEPRECATED_BEFORE_AND_AT
# else
# define KSYNTAXHIGHLIGHTING_DEPRECATED_WARNINGS_SINCE 0x55700
# define KSYNTAXHIGHLIGHTING_DEPRECATED_WARNINGS_SINCE 0x55a00
# endif
#endif
@@ -178,7 +178,7 @@
#define KSYNTAXHIGHLIGHTING_BUILD_DEPRECATED_SINCE(major, minor) 1
#ifdef KSYNTAXHIGHLIGHTING_NO_DEPRECATED
# define KSYNTAXHIGHLIGHTING_DISABLE_DEPRECATED_BEFORE_AND_AT 0x55700
# define KSYNTAXHIGHLIGHTING_DISABLE_DEPRECATED_BEFORE_AND_AT 0x55a00
#endif
#ifdef KSYNTAXHIGHLIGHTING_NO_DEPRECATED_WARNINGS
# define KSYNTAXHIGHLIGHTING_DEPRECATED_WARNINGS_SINCE 0
@@ -188,7 +188,7 @@
# ifdef KSYNTAXHIGHLIGHTING_DISABLE_DEPRECATED_BEFORE_AND_AT
# define KSYNTAXHIGHLIGHTING_DEPRECATED_WARNINGS_SINCE KSYNTAXHIGHLIGHTING_DISABLE_DEPRECATED_BEFORE_AND_AT
# else
# define KSYNTAXHIGHLIGHTING_DEPRECATED_WARNINGS_SINCE 0x55700
# define KSYNTAXHIGHLIGHTING_DEPRECATED_WARNINGS_SINCE 0x55a00
# endif
#endif

View File

@@ -64,7 +64,7 @@ if (QRC_SYNTAX)
# generate the qrc file manually, to make dependencies on generated files work...
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/qrc_syntax-data.cpp"
COMMAND ${Qt5Core_RCC_EXECUTABLE} --name syntax_data -o "${CMAKE_CURRENT_BINARY_DIR}/qrc_syntax-data.cpp" "${CMAKE_CURRENT_BINARY_DIR}/syntax-data.qrc"
COMMAND Qt${QT_MAJOR_VERSION}::rcc --name syntax_data -o "${CMAKE_CURRENT_BINARY_DIR}/qrc_syntax-data.cpp" "${CMAKE_CURRENT_BINARY_DIR}/syntax-data.qrc"
DEPENDS ${defs} ${CMAKE_CURRENT_BINARY_DIR}/index.katesyntax
)
set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/qrc_syntax-data.cpp" PROPERTIES SKIP_AUTOMOC ON)
@@ -79,5 +79,5 @@ endif()
# this needs some more recent CMake than generally required
set_property(TARGET SyntaxHighlightingData PROPERTY POSITION_INDEPENDENT_CODE 1)
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.13.0")
target_link_libraries(SyntaxHighlightingData PRIVATE Qt5::Core)
target_link_libraries(SyntaxHighlightingData PRIVATE Qt${QT_MAJOR_VERSION}::Core)
endif()

View File

@@ -1,7 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE language SYSTEM "language.dtd"
[
<!ENTITY id_re "[_A-Za-z][\-_0-9A-Za-z]*">
<!-- NOTE See https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#variable-references -->
<!ENTITY var_ref_re "[/\.\+\-_0-9A-Za-z]+">
<!-- NOTE See `cmGeneratorExpression::IsValidTargetName` -->
<!ENTITY tgt_name_re "[A-Za-z0-9_\.\+\-]+">
]>
<!--
This file is part of KDE's kate project.
@@ -38,7 +41,9 @@
<item><!--{command.name}--></item>
<!--[- endfor ]-->
</list>
<!--[ for command in commands -]-->
<!--[- macro render_command_arg_lists(commands) ]-->
<!--[- for command in commands -]-->
<!--[- if command.named_args and command.named_args.kw ]-->
<list name="<!--{command.name}-->_nargs">
<!--[- for arg in command.named_args.kw ]-->
@@ -54,6 +59,9 @@
</list>
<!--[- endif ]-->
<!--[- endfor ]-->
<!--[- endmacro ]-->
<!--{- render_command_arg_lists(commands) }-->
<!--{- render_command_arg_lists(standard_module_commands) }-->
<list name="variables">
<!--[- for var in variables.kw ]-->
@@ -87,6 +95,24 @@
<!--[- endfor ]-->
</list>
<list name="standard-modules">
<!--[- for module in modules.utility ]-->
<item><!--{ module }--></item>
<!--[- endfor ]-->
</list>
<list name="standard-finder-modules">
<!--[- for module in modules.finder ]-->
<item><!--{ module | replace('Find', '') }--></item>
<!--[- endfor ]-->
</list>
<list name="deprecated-modules">
<!--[- for module in modules.deprecated ]-->
<item><!--{ module }--></item>
<!--[- endfor ]-->
</list>
<contexts>
<context attribute="Normal Text" lineEndContext="#stay" name="Normal Text">
@@ -94,25 +120,68 @@
<!--[ for command in commands -]-->
<WordDetect String="<!--{command.name}-->" insensitive="true" attribute="Command" context="<!--{command.name}-->_ctx"<!--[ if command.start_region ]--> beginRegion="<!--{command.start_region}-->"<!--[ endif -]--> <!--[- if command.end_region ]--> endRegion="<!--{command.end_region}-->"<!--[ endif ]--> />
<!--[ endfor -]-->
<!--[ for command in standard_module_commands -]-->
<WordDetect String="<!--{command.name}-->" insensitive="true" attribute="CMake Provided Function/Macro" context="<!--{command.name}-->_ctx" />
<!--[ endfor -]-->
<DetectChar attribute="Comment" context="Match Comments and Docs" char="#" lookAhead="true" />
<DetectIdentifier attribute="User Function/Macro" context="User Function" />
<RegExpr attribute="@Variable Substitution" context="@VarSubst" String="@&id_re;@" lookAhead="true" />
<!-- Include keywords matching for language autocompleter work -->
<keyword attribute="Command" context="#stay" String="commands" />
<RegExpr attribute="@Variable Substitution" context="@VarSubst" String="@&var_ref_re;@" lookAhead="true" />
<RegExpr attribute="Error" context="#stay" String=".*" />
</context>
<!--[- macro render_command_parsers(commands) ]-->
<!--[ for command in commands -]-->
<context attribute="Normal Text" lineEndContext="#stay" name="<!--{command.name}-->_ctx">
<DetectChar attribute="Normal Text" context="<!--{command.name}-->_ctx_op" char="(" />
<DetectChar attribute="Normal Text" context="<!--{command.name}-->_ctx_op<!--{'_tgt_first' if command.first_arg_is_target else '_tgts_first' if command.first_args_are_targets else ''}-->" char="(" />
<DetectChar attribute="Normal Text" context="#pop" char=")" />
</context>
<!--[- if command.first_arg_is_target ]-->
<context attribute="Normal Text" lineEndContext="#stay" name="<!--{command.name}-->_ctx_op_tgt_first">
<RegExpr attribute="Aliased Targets" context="<!--{command.name}-->_ctx_op" String="&tgt_name_re;::&tgt_name_re;(?:\:\:&tgt_name_re;)*" />
<RegExpr attribute="Targets" context="<!--{command.name}-->_ctx_op" String="&tgt_name_re;" />
<DetectChar attribute="Normal Text" context="#pop" char=")" lookAhead="true" />
<IncludeRules context="User Function Args" />
<DetectSpaces />
<RegExpr attribute="Error" context="#stay" String=".*" />
</context>
<!--[- endif ]-->
<!--[- if command.first_args_are_targets ]-->
<context attribute="Normal Text" lineEndContext="#stay" name="<!--{command.name}-->_ctx_op_tgts_first">
<!--[- if command.named_args and command.named_args.kw ]-->
<!-- NOTE Handle the only case in CMake nowadays:
1. `set_target_properties` have a named keyword (`PROPERTIES`) after targets list
-->
<keyword context="<!--{command.name}-->_ctx_op" String="<!--{command.name}-->_nargs" lookAhead="true" />
<!--[- endif ]-->
<IncludeRules context="Detect Aliased Targets" />
<RegExpr attribute="Targets" context="#stay" String="&tgt_name_re;" />
<DetectChar attribute="Normal Text" context="#pop" char=")" lookAhead="true" />
<IncludeRules context="User Function Args" />
<DetectSpaces />
<RegExpr attribute="Error" context="#stay" String=".*" />
</context>
<!--[- endif ]-->
<!--[- if not command.first_args_are_targets or (command.named_args and command.named_args.kw) ]-->
<context attribute="Normal Text" lineEndContext="#stay" name="<!--{command.name}-->_ctx_op">
<!--[- if command.nested_parentheses ]-->
<DetectChar attribute="Normal Text" context="<!--{command.name}-->_ctx_op_nested" char="(" />
<!--[- endif ]-->
<IncludeRules context="EndCmdPop2" />
<DetectChar attribute="Normal Text" context="#pop" char=")" lookAhead="true" />
<!--[- if command.named_args and command.named_args.kw ]-->
<!--[- if command.has_target_name_after_kw ]-->
<WordDetect String="<!--{command.has_target_name_after_kw}-->" attribute="Named Args" context="Target Name" />
<!--[- endif ]-->
<!--[- if command.has_target_names_after_kw ]-->
<WordDetect String="<!--{command.has_target_names_after_kw}-->" attribute="Named Args" context="<!--{command.name}-->_tgts" />
<!--[- endif ]-->
<keyword attribute="Named Args" context="#stay" String="<!--{command.name}-->_nargs" />
<!--[- endif ]-->
<!--[- if command.name == 'include' ]-->
<keyword attribute="Standard Module" context="#stay" String="standard-modules" />
<keyword attribute="Deprecated Module" context="#stay" String="deprecated-modules" />
<!--[- endif ]-->
<!--[- if command.name == 'find_package' ]-->
<keyword attribute="Standard Module" context="#stay" String="standard-finder-modules" />
<!--[- endif ]-->
<!--[- if command.special_args and command.special_args.kw ]-->
<keyword attribute="Special Args" context="#stay" String="<!--{command.name}-->_sargs" />
<!--[- endif ]-->
@@ -132,9 +201,21 @@
<!--[- endif ]-->
<!--[- endif ]-->
</context>
<!--[- endif ]-->
<!--[- if command.has_target_names_after_kw ]-->
<context attribute="Normal Text" lineEndContext="#stay" name="<!--{command.name}-->_tgts">
<DetectChar attribute="Normal Text" context="#pop" char=")" lookAhead="true" />
<keyword attribute="Named Args" context="#pop" String="<!--{command.name}-->_nargs" />
<RegExpr attribute="Aliased Targets" context="#stay" String="&tgt_name_re;::&tgt_name_re;(?:\:\:&tgt_name_re;)*" />
<RegExpr attribute="Targets" context="#stay" String="&tgt_name_re;" />
<IncludeRules context="User Function Args" />
<DetectSpaces />
<RegExpr attribute="Error" context="#stay" String=".*" />
</context>
<!--[- endif ]-->
<!--[- if command.nested_parentheses ]-->
<context attribute="Normal Text" lineEndContext="#stay" name="<!--{command.name}-->_ctx_op_nested">
<IncludeRules context="EndCmdPop" />
<DetectChar attribute="Normal Text" context="#pop" char=")" />
<!--[- if command.named_args and command.named_args.kw ]-->
<keyword attribute="Named Args" context="#stay" String="<!--{command.name}-->_nargs" />
<!--[- endif ]-->
@@ -153,33 +234,27 @@
</context>
<!--[- endif ]-->
<!--[ endfor -]-->
<!--[- endmacro -]-->
<!--{- render_command_parsers(commands) -}-->
<!--{- render_command_parsers(standard_module_commands) -}-->
<!--[ for kind in properties.kinds if properties[kind].re -]-->
<context attribute="Normal Text" lineEndContext="#stay" name="Detect More <!--{ kind|replace('_', '-') }-->">
<RegExpr attribute="Property" context="#stay" String="<!--{properties[kind].re}-->" />
</context><!--{ '\n' }-->
<!--[ endfor -]-->
<context attribute="Normal Text" lineEndContext="#stay" name="EndCmdPop">
<context attribute="User Function/Macro" lineEndContext="#stay" name="User Function">
<DetectChar attribute="Normal Text" context="User Function Opened" char="(" />
<DetectChar attribute="Normal Text" context="#pop" char=")" />
</context>
<context attribute="Normal Text" lineEndContext="#stay" name="EndCmdPop2">
<DetectChar attribute="Normal Text" context="#pop#pop" char=")" />
</context>
<context attribute="User Function/Macro" lineEndContext="#stay" name="User Function">
<DetectChar attribute="Normal Text" context="User Function Opened" char="(" />
<IncludeRules context="EndCmdPop2" />
</context>
<context attribute="Normal Text" lineEndContext="#stay" name="User Function Opened">
<IncludeRules context="EndCmdPop2" />
<DetectChar attribute="Normal Text" context="#pop" char=")" lookAhead="true" />
<IncludeRules context="User Function Args" />
</context>
<context attribute="Normal Text" lineEndContext="#stay" name="Detect Builtin Variables">
<RegExpr attribute="Internal Name" context="#stay" String="\b_&id_re;\b" />
<RegExpr attribute="Internal Name" context="#stay" String="\b_&var_ref_re;\b" />
<keyword attribute="CMake Internal Variable" context="#stay" String="deprecated-or-internal-variables" insensitive="false" />
<keyword attribute="Builtin Variable" context="#stay" String="variables" insensitive="false" />
<IncludeRules context="Detect More Builtin Variables" />
@@ -198,7 +273,7 @@
<RegExpr attribute="Cache Variable Substitution" context="#stay" String="\$CACHE\{\s*[\w-]+\s*\}" />
<RegExpr attribute="Environment Variable Substitution" context="EnvVarSubst" String="\$?ENV\{" />
<Detect2Chars attribute="Variable Substitution" context="VarSubst" char="$" char1="{" />
<RegExpr attribute="@Variable Substitution" context="@VarSubst" String="@&id_re;@" lookAhead="true" />
<RegExpr attribute="@Variable Substitution" context="@VarSubst" String="@&var_ref_re;@" lookAhead="true" />
</context>
<context attribute="Environment Variable Substitution" lineEndContext="#pop" name="EnvVarSubst">
@@ -229,6 +304,15 @@
<DetectChar attribute="@Variable Substitution" context="#pop#pop" char="@" />
</context>
<context attribute="Normal Text" lineEndContext="#stay" name="Target Name">
<RegExpr attribute="Aliased Targets" context="#pop" String="&tgt_name_re;::&tgt_name_re;(?:\:\:&tgt_name_re;)*" />
<RegExpr attribute="Targets" context="#pop" String="&tgt_name_re;" />
<DetectChar attribute="Normal Text" context="#pop" char=")" lookAhead="true" />
<IncludeRules context="User Function Args" />
<DetectSpaces />
<RegExpr attribute="Error" context="#stay" String=".*" />
</context>
<context attribute="Normal Text" lineEndContext="#stay" name="User Function Args">
<Detect2Chars attribute="Normal Text" context="#stay" char="\" char1="(" />
<Detect2Chars attribute="Normal Text" context="#stay" char="\" char1=")" />
@@ -261,12 +345,12 @@
<WordDetect attribute="False Special Arg" context="#stay" String="N" insensitive="true" />
<WordDetect attribute="False Special Arg" context="#stay" String="IGNORE" insensitive="true" />
<WordDetect attribute="False Special Arg" context="#stay" String="0" />
<RegExpr attribute="False Special Arg" context="#stay" String="\b(?:&id_re;-)?NOTFOUND\b" />
<RegExpr attribute="Special Args" context="#stay" String="\bCMP[0-9][0-9][0-9]\b" />
<RegExpr attribute="False Special Arg" context="#stay" String="\b(?:&var_ref_re;-)?NOTFOUND\b" />
<RegExpr attribute="Special Args" context="#stay" String="\bCMP[0-9][0-9][0-9][0-9]\b" />
</context>
<context attribute="Normal Text" lineEndContext="#stay" name="Detect Aliased Targets">
<RegExpr attribute="Aliased Targets" context="#stay" String="\b&id_re;::&id_re;(?:\:\:&id_re;)*\b" />
<RegExpr attribute="Aliased Targets" context="#stay" String="&tgt_name_re;::&tgt_name_re;(?:\:\:&tgt_name_re;)*" />
</context>
<context attribute="Comment" lineEndContext="#pop" name="Match Comments">
@@ -330,9 +414,12 @@
<itemDatas>
<itemData name="Normal Text" defStyleNum="dsNormal" spellChecking="false" />
<itemData name="Comment" defStyleNum="dsComment" spellChecking="true" />
<itemData name="Command" defStyleNum="dsKeyword" spellChecking="false" />
<itemData name="CMake Provided Function/Macro" defStyleNum="dsFunction" bold="true" spellChecking="false" />
<itemData name="User Function/Macro" defStyleNum="dsFunction" spellChecking="false" />
<itemData name="Property" defStyleNum="dsOthers" spellChecking="false" />
<itemData name="Targets" defStyleNum="dsBaseN" spellChecking="false" />
<itemData name="Aliased Targets" defStyleNum="dsBaseN" spellChecking="false" />
<itemData name="Named Args" defStyleNum="dsOthers" spellChecking="false" />
<itemData name="Special Args" defStyleNum="dsOthers" spellChecking="false" />
@@ -351,8 +438,10 @@
<itemData name="Standard Environment Variable" defStyleNum="dsFloat" spellChecking="false" />
<itemData name="Generator Expression Keyword" defStyleNum="dsKeyword" color="#b84040" selColor="#b84040" spellChecking="false" />
<itemData name="Generator Expression" defStyleNum="dsOthers" color="#b86050" selColor="#b86050" spellChecking="false" />
<itemData name="Comment" defStyleNum="dsComment" spellChecking="true" />
<itemData name="Standard Module" defStyleNum="dsImport" spellChecking="false" />
<itemData name="Deprecated Module" defStyleNum="dsImport" spellChecking="false" />
<itemData name="Region Marker" defStyleNum="dsRegionMarker" spellChecking="false" />
<itemData name="Error" defStyleNum="dsError" spellChecking="false" />
</itemDatas>
</highlighting>
@@ -360,7 +449,7 @@
<general>
<comments>
<comment name="singleLine" start="#" position="afterwhitespace" />
<comment name="multiLine" start="#[[" end="]]" region="BracketedComment"/>
<comment name="multiLine" start="#[[" end="]]" region="BracketedComment" />
</comments>
<keywords casesensitive="1" weakDeliminator="." />
</general>

File diff suppressed because it is too large Load Diff

View File

@@ -57,7 +57,7 @@ def try_transform_placeholder_string_to_regex(name):
if 'ARGV' in m:
return 'ARGV[0-9]+'
return '&id_re;'.join(m) if 1 < len(m) else name
return '&var_ref_re;'.join(m) if 1 < len(m) else name
def try_placeholders_to_regex(names):
@@ -109,6 +109,26 @@ def transform_command(cmd):
cmd['nested_parentheses'] = cmd['nested-parentheses?'] if 'nested-parentheses?' in cmd else False
if 'first-arg-is-target?' in cmd:
cmd['first_arg_is_target'] = cmd['first-arg-is-target?']
can_be_nulary = False
if 'first-args-are-targets?' in cmd:
cmd['first_args_are_targets'] = cmd['first-args-are-targets?']
can_be_nulary = False
if 'has-target-name-after-kw' in cmd:
cmd['has_target_name_after_kw'] = cmd['has-target-name-after-kw']
can_be_nulary = False
if 'has-target-names-after-kw' in cmd:
cmd['has_target_names_after_kw'] = cmd['has-target-names-after-kw']
can_be_nulary = False
if 'second-arg-is-target?' in cmd:
cmd['second_arg_is_target'] = cmd['second-arg-is-target?']
can_be_nulary = False
if 'nulary?' in cmd and cmd['nulary?'] and not can_be_nulary:
raise RuntimeError('Command `{}` w/ args declared nulary!?'.format(cmd['name']))
@@ -124,8 +144,7 @@ def transform_command(cmd):
#BEGIN Jinja filters
def cmd_is_nulary(cmd):
assert not ('named-args' in cmd or 'special-args' in cmd or 'property-args' in cmd)
return 'nulary?' in cmd and cmd['nulary?']
return cmd.setdefault('nulary?', False)
#END Jinja filters
@@ -134,7 +153,7 @@ def cmd_is_nulary(cmd):
@click.argument('input_yaml', type=click.File('r'))
@click.argument('template', type=click.File('r'), default='./cmake.xml.tpl')
def cli(input_yaml, template):
data = yaml.load(input_yaml)
data = yaml.load(input_yaml, Loader=yaml.BaseLoader)
# Partition `variables` and `environment-variables` lists into "pure" (key)words and regexes to match
for var_key in _VAR_KIND_LIST:
@@ -164,8 +183,16 @@ def cli(input_yaml, template):
data['commands'] = list(
map(
transform_command
, data['scripting-commands'] + data['project-commands'] + data['ctest-commands'])
, data['scripting-commands'] + data['project-commands'] + data['ctest-commands']
)
)
data['standard_module_commands'] = list(
map(
transform_command
, data['standard-module-commands']
)
)
del data['standard-module-commands']
# Fix node names to be accessible from Jinja template
data['generator_expressions'] = data['generator-expressions']

View File

@@ -1,7 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE language SYSTEM "language.dtd">
<!-- ***** THIS FILE WAS GENERATED BY A SCRIPT - DO NOT EDIT *****
cd data/generators
# increase version of spdx-comments.xml.tpl then
./generate-spdx-syntax.py > ../syntax/spdx-comments.xml
-->
<language
version="4"
version="6"
kateversion="3.1"
name="SPDX-Comments"
section="Other"
@@ -58,9 +63,7 @@
<context name="license-expression" attribute="SPDX Value" lineEndContext="#pop" fallthrough="true" fallthroughContext="#pop">
<DetectSpaces/>
<DetectChar char="(" context="#stay" attribute="SPDX License Expression Operator" />
<DetectChar char=")" context="#stay" attribute="SPDX License Expression Operator" />
<DetectChar char="+" context="#stay" attribute="SPDX License Expression Operator" />
<AnyChar String="()+" context="#stay" attribute="SPDX License Expression Operator" />
<keyword String="licenses" context="#stay" attribute="SPDX License" />
<keyword String="deprecated-licenses" context="#stay" attribute="SPDX Deprecated License" />
<keyword String="exceptions" context="#stay" attribute="SPDX License Exception" />

View File

@@ -130,8 +130,8 @@
extensions: A file glob or pattern to decide for which documents to use this syntax description
style: The style that this highlighter provides. It is used through required-syntax-style by the indenters. [optional]
mimetype: A list of mimetypes to decide for which documents to use this syntax description [optional]
version: Version number of this syntax description [optional]
kateversion: Kate version required for using this file [optional]
version: Version number of this syntax description
kateversion: Kate version required for using this file
casesensitive: Whether text is matched case sensitive. [boolean, optional, default=true] FIXME: This is not implemented yet
priority: Priority of this language, if more than one are usable for the file [optional]
author: Name of author of this hl file [optional]

View File

@@ -39,7 +39,7 @@
<!ENTITY arithmetic_as_subshell "\(((?:[^`'&quot;()$]++|\$\{[^`'&quot;(){}$]+\}|\$(?=[^{`'&quot;()])|`[^`]*+`|\((?1)(?:[)]|(?=['&quot;])))++)(?:[)](?=$|[^)])|[&quot;'])">
]>
<language name="Bash" version="30" kateversion="5.79" section="Scripts" extensions="*.sh;*.bash;*.ebuild;*.eclass;*.nix;.bashrc;.bash_profile;.bash_login;.profile;PKGBUILD;APKBUILD" mimetype="application/x-shellscript" casesensitive="1" author="Wilbert Berendsen (wilbert@kde.nl)" license="LGPL">
<language name="Bash" version="34" kateversion="5.79" section="Scripts" extensions="*.sh;*.bash;*.ebuild;*.eclass;*.exlib;*.exheres-0;.bashrc;.bash_profile;.bash_login;.profile;PKGBUILD;APKBUILD" mimetype="application/x-shellscript" casesensitive="1" author="Wilbert Berendsen (wilbert@kde.nl)" license="LGPL">
<!-- (c) 2004 by Wilbert Berendsen (wilbert@kde.nl)
Changes by Matthew Woehlke (mw_triad@users.sourceforge.net)
@@ -133,6 +133,9 @@
<!-- /bin -->
<item>arch</item>
<item>awk</item>
<item>b2sum</item>
<item>base32</item>
<item>base64</item>
<item>bash</item>
<item>bunzip2</item>
<item>bzcat</item>
@@ -147,11 +150,15 @@
<item>bzmore</item>
<item>cat</item>
<item>chattr</item>
<item>chcon</item>
<item>chgrp</item>
<item>chmod</item>
<item>chown</item>
<item>chvt</item>
<item>cksum</item>
<item>cp</item>
<item>crontab</item>
<item>csplit</item>
<item>date</item>
<item>dd</item>
<item>deallocvt</item>
@@ -165,9 +172,11 @@
<item>dumpkeys</item>
<item>ed</item>
<item>egrep</item>
<item>expand</item>
<item>false</item>
<item>fgconsole</item>
<item>fgrep</item>
<item>fold</item>
<item>fuser</item>
<item>gawk</item>
<item>getkeycodes</item>
@@ -178,7 +187,9 @@
<item>gunzip</item>
<item>gzexe</item>
<item>gzip</item>
<item>hostid</item>
<item>hostname</item>
<item>iconv</item>
<item>igawk</item>
<item>install</item>
<item>kbd_mode</item>
@@ -206,6 +217,7 @@
<item>lzmainfo</item>
<item>lzmore</item>
<item>mapscrn</item>
<item>md5sum</item>
<item>mesg</item>
<item>mkdir</item>
<item>mkfifo</item>
@@ -217,20 +229,29 @@
<item>nano</item>
<item>netstat</item>
<item>nisdomainname</item>
<item>nproc</item>
<item>nroff</item>
<item>numfmt</item>
<item>openvt</item>
<item>paste</item>
<item>pathchk</item>
<item>pgawk</item>
<item>pidof</item>
<item>ping</item>
<item>pinky</item>
<item>printenv</item>
<item>ps</item>
<item>pstree</item>
<item>ptx</item>
<item>rbash</item>
<item>readlink</item>
<item>realpath</item>
<item>red</item>
<item>resizecons</item>
<item>rm</item>
<item>rmdir</item>
<item>run-parts</item>
<item>runcon</item>
<item>sash</item>
<item>sed</item>
<item>setfont</item>
@@ -239,22 +260,33 @@
<item>setmetamode</item>
<item>setserial</item>
<item>sh</item>
<item>sha1sum</item>
<item>sha224sum</item>
<item>sha256sum</item>
<item>sha384sum</item>
<item>sha512sum</item>
<item>showkey</item>
<item>shred</item>
<item>shuf</item>
<item>sleep</item>
<item>ssed</item>
<item>stat</item>
<item>stdbuf</item>
<item>stty</item>
<item>su</item>
<item>sync</item>
<item>tar</item>
<item>tempfile</item>
<item>timeout</item>
<item>touch</item>
<item>tput</item>
<item>troff</item>
<item>true</item>
<item>truncate</item>
<item>tty</item>
<item>umount</item>
<item>uname</item>
<item>unexpand</item>
<item>unicode_start</item>
<item>unicode_stop</item>
<item>unlink</item>
@@ -263,6 +295,7 @@
<item>utmpdump</item>
<item>uuidgen</item>
<item>vdir</item>
<item>vi</item>
<item>wall</item>
<item>wc</item>
<item>xz</item>
@@ -336,6 +369,7 @@
<item>gc</item>
<item>gcc</item>
<item>clang</item>
<item>clang++</item>
<item>valgrind</item>
<item>xdg-open</item>
<item>cmake</item>
@@ -464,6 +498,19 @@
<contexts>
<context attribute="Normal Text" lineEndContext="#stay" name="Start" fallthroughContext="Command">
<IncludeRules context="BashOneLine"/>
</context>
<!-- used by other syntaxes -->
<context attribute="Normal Text" lineEndContext="#pop" name="FindOneLineBackq" fallthroughContext="#pop">
<DetectChar attribute="Backquote" context="OneLineBackq" char="`"/>
</context>
<context attribute="Normal Text" lineEndContext="#stay" name="OneLineBackq" fallthroughContext="Command">
<DetectChar attribute="Backquote" context="#pop" char="`"/>
<IncludeRules context="Start"/>
</context>
<context attribute="Normal Text" lineEndContext="#pop" name="BashOneLine" fallthroughContext="Command">
<DetectSpaces attribute="Normal Text" context="#stay"/>
<DetectChar attribute="Comment" context="Comment" char="#"/>
<!-- start expression in double parentheses -->
@@ -1104,6 +1151,7 @@
<!-- VarBracePrefix called as soon as ${! is encoutered -->
<context attribute="Variable" lineEndContext="#pop" name="VarBracePrefix">
<DetectIdentifier attribute="Variable" context="#pop!VarBracePrefixSuffix"/>
<Int attribute="Variable" context="#pop!VarBracePrefixSuffix"/>
</context>
<context attribute="Variable" lineEndContext="#pop" name="VarBracePrefixSuffix" fallthroughContext="#pop!VarError">
<DetectChar attribute="Parameter Expansion" context="#pop" char="}"/>

File diff suppressed because one or more lines are too long

View File

@@ -90,7 +90,7 @@
<!ENTITY checkbox "\[[ x]\](?=\s)">
]>
<language name="Markdown" version="19" kateversion="5.79" section="Markup" extensions="*.md;*.mmd;*.markdown" priority="15" author="Darrin Yeager, Claes Holmerson" license="GPL,BSD">
<language name="Markdown" version="21" kateversion="5.79" section="Markup" extensions="*.md;*.mmd;*.markdown" priority="15" author="Darrin Yeager, Claes Holmerson" license="GPL,BSD">
<highlighting>
<contexts>
<!-- Start of the Markdown document: find metadata or code block -->
@@ -149,32 +149,42 @@
</context>
<context name="find-header" attribute="Normal Text" lineEndContext="#pop">
<!-- TODO: Replace "(?=.$)" in the regexes with just "$" when close-H#-region are removed -->
<RegExpr attribute="Header H1" context="#pop!close-H2-region" String="^#\s.*[#]?(?=.$)" column="0" endRegion="H1" beginRegion="H1"/>
<RegExpr attribute="Header H2" context="#pop!close-H3-region" String="^##\s.*[#]?(?=.$)" column="0" endRegion="H2" beginRegion="H2"/>
<RegExpr attribute="Header H3" context="#pop!close-H4-region" String="^###\s.*[#]?(?=.$)" column="0" endRegion="H3" beginRegion="H3"/>
<RegExpr attribute="Header H4" context="#pop!close-H5-region" String="^####\s.*[#]?(?=.$)" column="0" endRegion="H4" beginRegion="H4"/>
<RegExpr attribute="Header H5" context="#pop!close-H6-region" String="^#####\s.*[#]?(?=.$)" column="0" endRegion="H5" beginRegion="H5"/>
<RegExpr attribute="Header H6" context="#pop" String="^######\s.*[#]?$" column="0" endRegion="H6" beginRegion="H6"/>
<RegExpr attribute="Header H1" context="#pop!close-H2-region" String="^(#\s.*[#]?)$" column="0" endRegion="H1" beginRegion="H1" lookAhead="true"/>
<RegExpr attribute="Header H2" context="#pop!close-H3-region" String="^(##\s.*[#]?)$" column="0" endRegion="H2" beginRegion="H2" lookAhead="true"/>
<RegExpr attribute="Header H3" context="#pop!close-H4-region" String="^(###\s.*[#]?)$" column="0" endRegion="H3" beginRegion="H3" lookAhead="true"/>
<RegExpr attribute="Header H4" context="#pop!close-H5-region" String="^(####\s.*[#]?)$" column="0" endRegion="H4" beginRegion="H4" lookAhead="true"/>
<RegExpr attribute="Header H5" context="#pop!close-H6-region" String="^(#####\s.*[#]?)$" column="0" endRegion="H5" beginRegion="H5" lookAhead="true"/>
<RegExpr attribute="Header H6" context="#pop!close-sentinel-region" String="^(######\s.*[#]?)$" column="0" endRegion="H6" beginRegion="H6" lookAhead="true"/>
<DetectChar attribute="Normal Text" context="#pop" char="#"/>
</context>
<!-- BUG: 441278 sub-headers should be closed when their parent header is closed (e.g. in h1 h2 h3 h1, h1-h3 should all be closed at the 2nd h1) -->
<!-- TODO: Port to a less hacky version (maybe a new attribute for declaring multiple endRegions) -->
<context name="close-H2-region" attribute="Header H2" lineEndContext="#pop!close-H3-region" fallthroughContext="#pop!close-H3-region">
<RegExpr attribute="Header H2" context="#pop!close-H3-region" String="." lookAhead="true" endRegion="H2"/>
<RegExpr attribute="Header H2" context="#pop!close-H3-region" String="(%1)" dynamic="true" lookAhead="true" endRegion="H2"/>
</context>
<context name="close-H3-region" attribute="Header H3" lineEndContext="#pop!close-H4-region" fallthroughContext="#pop!close-H4-region">
<RegExpr attribute="Header H3" context="#pop!close-H4-region" String="." lookAhead="true" endRegion="H3"/>
<RegExpr attribute="Header H3" context="#pop!close-H4-region" String="(%1)" dynamic="true" lookAhead="true" endRegion="H3"/>
</context>
<context name="close-H4-region" attribute="Header H4" lineEndContext="#pop!close-H5-region" fallthroughContext="#pop!close-H5-region">
<RegExpr attribute="Header H4" context="#pop!close-H5-region" String="." lookAhead="true" endRegion="H4"/>
<RegExpr attribute="Header H4" context="#pop!close-H5-region" String="(%1)" dynamic="true" lookAhead="true" endRegion="H4"/>
</context>
<context name="close-H5-region" attribute="Header H5" lineEndContext="#pop!close-H6-region" fallthroughContext="#pop!close-H6-region">
<RegExpr attribute="Header H5" context="#pop!close-H6-region" String="." lookAhead="true" endRegion="H5"/>
<RegExpr attribute="Header H5" context="#pop!close-H6-region" String="(%1)" dynamic="true" lookAhead="true" endRegion="H5"/>
</context>
<context name="close-H6-region" attribute="Header H6" lineEndContext="#pop" fallthroughContext="#pop">
<RegExpr attribute="Header H6" context="#pop" String="." endRegion="H6"/>
<RegExpr attribute="Header H6" context="#pop!close-sentinel-region" String="(%1)" dynamic="true" lookAhead="true" endRegion="H6"/>
</context>
<!-- This sentinel does not close any actual region, it's just here so that the proper attribute/highlighting is applied and -->
<!--to ensure that H6 headers won't be set as the "primary" region that was closed just because it was closed last -->
<context name="close-sentinel-region" attribute="Normal Text" lineEndContext="#pop" fallthroughContext="#pop">
<RegExpr attribute="Header H1" context="#pop" String="^(?=#\s)%1$" column="0" dynamic="true"/>
<RegExpr attribute="Header H2" context="#pop" String="^(?=##\s)%1$" column="0" dynamic="true"/>
<RegExpr attribute="Header H3" context="#pop" String="^(?=###\s)%1$" column="0" dynamic="true"/>
<RegExpr attribute="Header H4" context="#pop" String="^(?=####\s)%1$" column="0" dynamic="true"/>
<RegExpr attribute="Header H5" context="#pop" String="^(?=#####\s)%1$" column="0" dynamic="true"/>
<RegExpr attribute="Header H6" context="#pop" String="^(?=######\s)%1$" column="0" dynamic="true"/>
</context>
<context name="find-strong-normal" attribute="Normal Text" lineEndContext="#pop">
<RegExpr attribute="Strong-Emphasis Text" context="#pop" minimal="true" String="&strongemphasisregex_ast_und;|&strongemphasisregex_ast_und2;"/>
@@ -320,6 +330,7 @@
<RegExpr attribute="Fenced Code" context="#pop!javascript-code" String="&fcode;\s*(?:javascript|m?js|es6|kwinscript|julius)&end;" insensitive="true"/>
<RegExpr attribute="Fenced Code" context="#pop!jsx-code" String="&fcode;\s*(?:jsx|tsx|(?:java|type)script\-react)&end;" insensitive="true"/> <!-- Included in the HTML definition. Also apply for TSX. -->
<RegExpr attribute="Fenced Code" context="#pop!json-code" String="&fcode;\s*(?:json5?|gltf)&end;" insensitive="true"/>
<RegExpr attribute="Fenced Code" context="#pop!yaml-code" String="&fcode;\s*(?:ya?ml)&end;" insensitive="true"/>
<RegExpr attribute="Fenced Code" context="#pop!matlab-code" String="&fcode;\s*matlab&end;" insensitive="true"/>
<RegExpr attribute="Fenced Code" context="#pop!markdown-code" String="&fcode;\s*(?:markdown|m?md)&end;" insensitive="true"/>
<RegExpr attribute="Fenced Code" context="#pop!mustache-code" String="&fcode;\s*(?:handlebars|hbs|mustache|mst|ractive|hogan|hulk)&end;" insensitive="true"/> <!-- Included in the HTML definition -->
@@ -404,6 +415,10 @@
<IncludeRules context="code"/>
<IncludeRules context="##JSON" includeAttrib="true"/>
</context>
<context attribute="Normal Text" lineEndContext="#stay" name="yaml-code">
<IncludeRules context="code"/>
<IncludeRules context="##YAML" includeAttrib="true"/>
</context>
<context attribute="Normal Text" lineEndContext="#stay" lineEmptyContext="find-code-block" name="markdown-code">
<IncludeRules context="code"/>
<IncludeRules context="Normal Text"/>

File diff suppressed because it is too large Load Diff

View File

@@ -162,9 +162,9 @@
"MarkError": "#cc0000",
"MarkExecution": "#888a85",
"MarkWarning": "#ad7fa8",
"ModifiedLines": "#451e1a",
"ModifiedLines": "#cc0000",
"ReplaceHighlight": "#356703",
"SavedLines": "#23321a",
"SavedLines": "#4e9a06",
"SearchHighlight": "#4e9a06",
"Separator": "#787775",
"SpellChecking": "#e85848",

View File

@@ -1,8 +1,11 @@
add_subdirectory(indexer)
if(TARGET Qt5::Gui)
if(TARGET Qt${QT_MAJOR_VERSION}::Gui)
add_subdirectory(lib)
add_subdirectory(cli)
endif()
if(TARGET Qt${QT_MAJOR_VERSION}::Quick)
add_subdirectory(quick)
endif()
ecm_qt_install_logging_categories(
EXPORT KSYNTAXHIGHLIGHTING

View File

@@ -20,6 +20,7 @@ elseif(CMAKE_CROSSCOMPILING)
-DECM_DIR=${ECM_DIR} -DCMAKE_PREFIX_PATH=${NATIVE_PREFIX}
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}
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)
@@ -32,6 +33,6 @@ else()
if(Qt5XmlPatterns_FOUND AND NOT ECM_ENABLE_SANITIZERS)
target_link_libraries(katehighlightingindexer Qt5::XmlPatterns)
else()
target_link_libraries(katehighlightingindexer Qt5::Core)
target_link_libraries(katehighlightingindexer Qt${QT_MAJOR_VERSION}::Core)
endif()
endif()

View File

@@ -350,7 +350,7 @@ private:
friend uint qHash(const Item &item, uint seed = 0)
{
return uint(qHash(item.content, seed));
return qHash(item.content, seed);
}
friend bool operator==(const Item &item0, const Item &item1)
@@ -513,7 +513,7 @@ private:
static const QRegularExpression isDot(QStringLiteral(R"(^\(?\.(?:[*+][*+?]?|[*+]|\{1\})?\$?$)"));
// remove "(?:" and ")"
static const QRegularExpression removeParentheses(QStringLiteral(R"(\((?:\?:)?|\))"));
// remove parentheses on a double from the string
// remove parentheses on a copy of string
auto reg = QString(string).replace(removeParentheses, QString());
isDotRegex = reg.contains(isDot);
}
@@ -719,7 +719,7 @@ private:
friend uint qHash(const Style &style, uint seed = 0)
{
return uint(qHash(style.name, seed));
return qHash(style.name, seed);
}
friend bool operator==(const Style &style0, const Style &style1)
@@ -995,7 +995,6 @@ private:
}
success = checkLookAhead(rule) && success;
success = checkStringDetect(rule) && success;
success = checkAnyChar(rule) && success;
success = checkKeyword(definition, rule, referencedKeywords) && success;
success = checkRegExpr(filename, rule, context) && success;
success = checkDelimiters(definition, rule) && success;
@@ -1053,12 +1052,9 @@ private:
"\\.\\*[?*]?" REG_CHAR "|"
"\\[\\^(" REG_ESCAPE_CHAR "|.)\\]\\*[?*]?\\1"
")$"));
if (( rule.lookAhead == XmlBool::True
|| rule.minimal == XmlBool::True
|| rule.string.contains(QStringLiteral(".*?"))
|| rule.string.contains(QStringLiteral("[^"))
) && reg.contains(isRange)
) {
if ((rule.lookAhead == XmlBool::True || rule.minimal == XmlBool::True || rule.string.contains(QStringLiteral(".*?"))
|| rule.string.contains(QStringLiteral("[^")))
&& reg.contains(isRange)) {
qWarning() << filename << "line" << rule.line << "RegExpr should be replaced by RangeDetect:" << rule.string;
return false;
}
@@ -1079,13 +1075,10 @@ private:
#undef REG_ESCAPE_CHAR
// use minimal or lazy operator
static const QRegularExpression isMinimal(QStringLiteral(
R"([.][*+][^][?+*()|$]*$)"));
if (rule.lookAhead == XmlBool::True
&& rule.minimal != XmlBool::True
&& reg.contains(isMinimal)
) {
qWarning() << filename << "line" << rule.line << "RegExpr should be have minimal=\"1\" or use lazy operator (i.g, '.*' -> '.*?'):" << rule.string;
static const QRegularExpression isMinimal(QStringLiteral(R"([.][*+][^][?+*()|$]*$)"));
if (rule.lookAhead == XmlBool::True && rule.minimal != XmlBool::True && reg.contains(isMinimal)) {
qWarning() << filename << "line" << rule.line
<< "RegExpr should be have minimal=\"1\" or use lazy operator (i.g, '.*' -> '.*?'):" << rule.string;
return false;
}
@@ -1179,19 +1172,18 @@ private:
if (*first == QLatin1Char('^')) {
hasStartOfLine = true;
break;
}
else if (*first == QLatin1Char('(')) {
} else if (*first == QLatin1Char('(')) {
if (last - first >= 3 && first[1] == QLatin1Char('?') && first[2] == QLatin1Char(':')) {
first += 2;
}
}
else {
} else {
break;
}
}
if (!hasStartOfLine) {
qWarning() << rule.filename << "line" << rule.line << "start of line missing in the pattern with column=\"0\" (i.e. abc -> ^abc):" << rule.string;
qWarning() << rule.filename << "line" << rule.line
<< "start of line missing in the pattern with column=\"0\" (i.e. abc -> ^abc):" << rule.string;
return false;
}
}
@@ -1314,14 +1306,12 @@ private:
}
// unnecessary quantifier
static const QRegularExpression unnecessaryQuantifier1(QStringLiteral(
R"([*+?]([.][*+?]{0,2})?$)"));
static const QRegularExpression unnecessaryQuantifier2(QStringLiteral(
R"([*+?]([.][*+?]{0,2})?[)]*$)"));
static const QRegularExpression unnecessaryQuantifier1(QStringLiteral(R"([*+?]([.][*+?]{0,2})?$)"));
static const QRegularExpression unnecessaryQuantifier2(QStringLiteral(R"([*+?]([.][*+?]{0,2})?[)]*$)"));
auto &unnecessaryQuantifier = useCapture ? unnecessaryQuantifier1 : unnecessaryQuantifier2;
if (rule.lookAhead == XmlBool::True && rule.minimal != XmlBool::True && reg.contains(unnecessaryQuantifier)) {
qWarning() << filename << "line" << rule.line << "Last quantifier is not necessary (i.g., 'xyz*' -> 'xy', 'xyz+.' -> 'xyz.'):"
<< rule.string;
qWarning() << filename << "line" << rule.line
<< "Last quantifier is not necessary (i.g., 'xyz*' -> 'xy', 'xyz+.' -> 'xyz.'):" << rule.string;
return false;
}
}
@@ -1484,28 +1474,7 @@ private:
qWarning() << rule.filename << "line" << rule.line << "broken regex:" << rule.string << "problem: dynamic=true but no %\\d+ placeholder";
return false;
}
} else {
if (rule.string.size() <= 1) {
const auto replacement = rule.insensitive == XmlBool::True ? QStringLiteral("AnyChar") : QStringLiteral("DetectChar");
qWarning() << rule.filename << "line" << rule.line << "StringDetect should be replaced by" << replacement;
return false;
}
if (rule.string.size() <= 2 && rule.insensitive != XmlBool::True) {
qWarning() << rule.filename << "line" << rule.line << "StringDetect should be replaced by Detect2Chars";
return false;
}
}
}
return true;
}
//! Check that AnyChar contains more that 1 character
bool checkAnyChar(const Context::Rule &rule) const
{
if (rule.type == Context::Rule::Type::AnyChar && rule.string.size() <= 1) {
qWarning() << rule.filename << "line" << rule.line << "AnyChar should be replaced by DetectChar";
return false;
}
return true;
}
@@ -1895,7 +1864,7 @@ private:
Rule4 detectIdentifierRule{};
// Contains includedRules and included includedRules
QMap<Context const*, RuleAndInclude> includeContexts;
QMap<Context const *, RuleAndInclude> includeContexts;
DotRegex dotRegex;
@@ -2232,8 +2201,7 @@ private:
if (auto &ruleAndInclude = includeContexts[rule.context.context]) {
updateUnreachable1(ruleAndInclude);
}
else {
} else {
ruleAndInclude.rule = &rule;
}
@@ -2378,25 +2346,21 @@ private:
const auto end = context.rules.end() - 1;
for (; it < end; ++it) {
auto& rule1 = *it;
auto& rule2 = it[1];
auto &rule1 = *it;
auto &rule2 = it[1];
auto isCommonCompatible = [&]{
return rule1.attribute == rule2.attribute
&& rule1.beginRegion == rule2.beginRegion
&& rule1.endRegion == rule2.endRegion
&& rule1.lookAhead == rule2.lookAhead
&& rule1.firstNonSpace == rule2.firstNonSpace
&& rule1.context.context == rule2.context.context
&& rule1.context.popCount == rule2.context.popCount
;
auto isCommonCompatible = [&] {
return rule1.attribute == rule2.attribute && rule1.beginRegion == rule2.beginRegion && rule1.endRegion == rule2.endRegion
&& rule1.lookAhead == rule2.lookAhead && rule1.firstNonSpace == rule2.firstNonSpace && rule1.context.context == rule2.context.context
&& rule1.context.popCount == rule2.context.popCount;
};
switch (rule1.type) {
// request to merge AnyChar/DetectChar
case Context::Rule::Type::AnyChar:
case Context::Rule::Type::DetectChar:
if ((rule2.type == Context::Rule::Type::AnyChar || rule2.type == Context::Rule::Type::DetectChar) && isCommonCompatible() && rule1.column == rule2.column) {
if ((rule2.type == Context::Rule::Type::AnyChar || rule2.type == Context::Rule::Type::DetectChar) && isCommonCompatible()
&& rule1.column == rule2.column) {
qWarning() << filename << "line" << rule2.line << "can be merged as AnyChar with the previous rule";
success = false;
}
@@ -2404,7 +2368,8 @@ private:
// request to merge multiple RegExpr
case Context::Rule::Type::RegExpr:
if (rule2.type == Context::Rule::Type::RegExpr && isCommonCompatible() && rule1.dynamic == rule2.dynamic && (rule1.column == rule2.column || (rule1.column <= 0 && rule2.column <= 0))) {
if (rule2.type == Context::Rule::Type::RegExpr && isCommonCompatible() && rule1.dynamic == rule2.dynamic
&& (rule1.column == rule2.column || (rule1.column <= 0 && rule2.column <= 0))) {
qWarning() << filename << "line" << rule2.line << "can be merged with the previous rule";
success = false;
}

View File

@@ -7,6 +7,7 @@ target_sources(KF5SyntaxHighlighting PRIVATE
context.cpp
contextswitch.cpp
definitiondownloader.cpp
highlightingdata.cpp
foldingregion.cpp
format.cpp
htmlhighlighter.cpp
@@ -46,13 +47,13 @@ set_target_properties(KF5SyntaxHighlighting PROPERTIES
SOVERSION ${SyntaxHighlighting_SOVERSION}
EXPORT_NAME SyntaxHighlighting
)
target_include_directories(KF5SyntaxHighlighting INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF5}/KSyntaxHighlighting;${KDE_INSTALL_INCLUDEDIR_KF5}>")
target_include_directories(KF5SyntaxHighlighting INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KSyntaxHighlighting;${KDE_INSTALL_INCLUDEDIR_KF}>")
target_include_directories(KF5SyntaxHighlighting PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR};${CMAKE_CURRENT_BINARY_DIR};>")
target_link_libraries(KF5SyntaxHighlighting
PUBLIC
Qt5::Gui
Qt${QT_MAJOR_VERSION}::Gui
PRIVATE
Qt5::Network
Qt${QT_MAJOR_VERSION}::Network
)
ecm_generate_headers(SyntaxHighlighting_HEADERS
@@ -74,7 +75,7 @@ install(TARGETS KF5SyntaxHighlighting EXPORT KF5SyntaxHighlightingTargets ${KDE_
install(FILES
${SyntaxHighlighting_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/ksyntaxhighlighting_export.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KSyntaxHighlighting)
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KSyntaxHighlighting)
if(BUILD_QCH)
ecm_add_qch(
@@ -104,6 +105,6 @@ ecm_generate_pri_file(
KF5SyntaxHighlighting
DEPS "gui"
FILENAME_VAR PRI_FILENAME
INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KSyntaxHighlighting
INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF}/KSyntaxHighlighting
)
install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR})

View File

@@ -137,8 +137,7 @@ State AbstractHighlighter::highlightLine(QStringView text, const State &state)
* see https://phabricator.kde.org/D18509
*/
int endlessLoopingCounter = 0;
while (!stateData->topContext()->lineEmptyContext().isStay()
|| (stateData->topContext()->lineEmptyContext().isStay() && !stateData->topContext()->lineEndContext().isStay())) {
while (!stateData->topContext()->lineEmptyContext().isStay() || !stateData->topContext()->lineEndContext().isStay()) {
/**
* line empty context switches
*/
@@ -153,8 +152,7 @@ State AbstractHighlighter::highlightLine(QStringView text, const State &state)
* line end context switches only when lineEmptyContext is #stay. This avoids
* skipping empty lines after a line continuation character (see bug 405903)
*/
} else if (!stateData->topContext()->lineEndContext().isStay()
&& !d->switchContext(stateData, stateData->topContext()->lineEndContext(), QStringList())) {
} else if (!d->switchContext(stateData, stateData->topContext()->lineEndContext(), QStringList())) {
break;
}
@@ -305,7 +303,7 @@ State AbstractHighlighter::highlightLine(QStringView text, const State &state)
d->switchContext(stateData, rule->context(), newResult.captures());
newFormat = rule->attributeFormat().isValid() ? &rule->attributeFormat() : &stateData->topContext()->attributeFormat();
if (newOffset == text.size() && std::dynamic_pointer_cast<LineContinue>(rule)) {
if (newOffset == text.size() && rule->isLineContinue()) {
lineContinuation = true;
}
break;

View File

@@ -17,7 +17,7 @@
#include <QColor>
#include <QFile>
#include <QFileInfo>
#include <QMap>
#include <QHash>
#include <QTextStream>
#include <cmath>
@@ -539,7 +539,7 @@ double calculate_CIEDE2000(const CieLab &color1, const CieLab &color2)
}
struct AnsiBuffer {
using ColorCache = QMap<QRgb, int>;
using ColorCache = QHash<QRgb, int>;
void append(char c)
{
@@ -683,7 +683,8 @@ struct GraphLine {
int labelLineLength = 0;
int nextLabelOffset = 0;
template<class String> void pushLabel(int offset, String const &s, int charCounter)
template<class String>
void pushLabel(int offset, String const &s, int charCounter)
{
Q_ASSERT(offset >= labelLineLength);
const int n = offset - labelLineLength;
@@ -693,7 +694,8 @@ struct GraphLine {
nextLabelOffset = labelLineLength;
}
template<class String> void pushGraph(int offset, String const &s, int charCounter)
template<class String>
void pushGraph(int offset, String const &s, int charCounter)
{
Q_ASSERT(offset >= graphLineLength);
const int n = offset - graphLineLength;
@@ -754,8 +756,15 @@ public:
void setDefinition(const KSyntaxHighlighting::Definition &def) override
{
AbstractHighlighter::setDefinition(def);
m_defData = DefinitionData::get(def);
m_contextCapture.setDefinition(def);
const auto &definitions = def.includedDefinitions();
for (const auto &definition : definitions) {
const auto *defData = DefinitionData::get(definition);
for (const auto &context : defData->contexts) {
m_defDataBycontexts.insert(&context, defData);
}
}
}
void highlightData(QTextStream &in,
@@ -928,8 +937,8 @@ private:
}
const auto context = stateData->topContext();
const auto defData = DefinitionData::get(context->definition());
const auto contextName = (defData != m_defData) ? QString(QLatin1Char('<') % defData->name % QLatin1Char('>')) : QString();
const auto defDataIt = m_defDataBycontexts.find(context);
const auto contextName = (defDataIt != m_defDataBycontexts.end()) ? QString(QLatin1Char('<') % (*defDataIt)->name % QLatin1Char('>')) : QString();
return QString(label % contextName % QLatin1Char('[') % context->name() % QLatin1Char(']'));
}
@@ -1128,12 +1137,13 @@ private:
std::vector<HighlightFragment> m_highlightedFragments;
std::vector<GraphLine> m_formatGraph;
ContextCaptureHighlighter m_contextCapture;
DefinitionData *m_defData;
int m_regionDepth = 0;
std::vector<Region> m_regions;
std::vector<GraphLine> m_regionGraph;
std::vector<QStringView> m_regionStyles;
QHash<const Context *, const DefinitionData *> m_defDataBycontexts;
};
} // anonymous namespace

View File

@@ -18,177 +18,111 @@
using namespace KSyntaxHighlighting;
Definition Context::definition() const
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)
{
return m_def.definition();
}
void Context::setDefinition(const DefinitionRef &def)
{
m_def = def;
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
{
if (m_noIndentationBasedFolding) {
return false;
}
return m_def.definition().indentationBasedFoldingEnabled();
return m_indentationBasedFolding;
}
void Context::load(QXmlStreamReader &reader)
void Context::resolveContexts(DefinitionData &def, const HighlightingContextData &data)
{
Q_ASSERT(reader.name() == QLatin1String("context"));
Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
m_name = reader.attributes().value(QLatin1String("name")).toString();
m_attribute = reader.attributes().value(QLatin1String("attribute")).toString();
m_lineEndContext.parse(reader.attributes().value(QLatin1String("lineEndContext")));
m_lineEmptyContext.parse(reader.attributes().value(QLatin1String("lineEmptyContext")));
m_fallthroughContext.parse(reader.attributes().value(QLatin1String("fallthroughContext")));
m_lineEndContext.resolve(def, data.lineEndContext);
m_lineEmptyContext.resolve(def, data.lineEmptyContext);
m_fallthroughContext.resolve(def, data.fallthroughContext);
m_fallthrough = !m_fallthroughContext.isStay();
m_noIndentationBasedFolding = Xml::attrToBool(reader.attributes().value(QLatin1String("noIndentationBasedFolding")));
reader.readNext();
while (!reader.atEnd()) {
switch (reader.tokenType()) {
case QXmlStreamReader::StartElement: {
auto rule = Rule::create(reader.name());
if (rule) {
rule->setDefinition(m_def.definition());
if (rule->load(reader)) {
m_rules.push_back(std::move(rule));
}
} else {
reader.skipCurrentElement();
}
reader.readNext();
break;
}
case QXmlStreamReader::EndElement:
return;
default:
reader.readNext();
break;
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::resolveContexts()
void Context::resolveIncludes(DefinitionData &def)
{
const auto def = m_def.definition();
m_lineEndContext.resolve(def);
m_lineEmptyContext.resolve(def);
m_fallthroughContext.resolve(def);
for (const auto &rule : m_rules) {
rule->resolveContext();
}
}
Context::ResolveState Context::resolveState()
{
if (m_resolveState == Unknown) {
for (const auto &rule : m_rules) {
auto inc = std::dynamic_pointer_cast<IncludeRules>(rule);
if (inc) {
m_resolveState = Unresolved;
return m_resolveState;
}
}
m_resolveState = Resolved;
}
return m_resolveState;
}
void Context::resolveIncludes()
{
if (resolveState() == Resolved) {
if (m_resolveState == Resolved) {
return;
}
if (resolveState() == Resolving) {
if (m_resolveState == Resolving) {
qCWarning(Log) << "Cyclic dependency!";
return;
}
Q_ASSERT(resolveState() == Unresolved);
Q_ASSERT(m_resolveState == Unresolved);
m_resolveState = Resolving; // cycle guard
for (auto it = m_rules.begin(); it != m_rules.end();) {
auto inc = std::dynamic_pointer_cast<IncludeRules>(*it);
if (!inc) {
const IncludeRules *includeRules = it->get()->castToIncludeRules();
if (!includeRules) {
++it;
continue;
}
Context *context = nullptr;
auto myDefData = DefinitionData::get(m_def.definition());
if (inc->definitionName().isEmpty()) { // local include
context = myDefData->contextByName(inc->contextName());
DefinitionData *defData = &def;
const auto &contextName = includeRules->contextName();
const int idx = contextName.indexOf(QLatin1String("##"));
if (idx == -1) { // local include
context = def.contextByName(contextName);
} else {
auto def = myDefData->repo->definitionForName(inc->definitionName());
if (!def.isValid()) {
qCWarning(Log) << "Unable to resolve external include rule for definition" << inc->definitionName() << "in" << m_def.definition().name();
auto definitionName = contextName.mid(idx + 2);
auto includedDef = def.repo->definitionForName(definitionName);
if (!includedDef.isValid()) {
qCWarning(Log) << "Unable to resolve external include rule for definition" << definitionName << "in" << def.name;
++it;
continue;
}
auto defData = DefinitionData::get(def);
defData = DefinitionData::get(includedDef);
def.addImmediateIncludedDefinition(includedDef);
defData->load();
if (inc->contextName().isEmpty()) {
if (idx == 0) {
context = defData->initialContext();
} else {
context = defData->contextByName(inc->contextName());
context = defData->contextByName(contextName.left(idx));
}
}
if (!context) {
qCWarning(Log) << "Unable to resolve include rule for definition" << inc->contextName() << "##" << inc->definitionName() << "in"
<< m_def.definition().name();
qCWarning(Log) << "Unable to resolve include rule for definition" << contextName << "in" << def.name;
++it;
continue;
}
context->resolveIncludes();
if (context == this) {
qCWarning(Log) << "Unable to resolve self include rule for definition" << contextName << "in" << def.name;
++it;
continue;
}
if (context->m_resolveState != Resolved) {
context->resolveIncludes(*defData);
}
/**
* handle included attribute
* transitive closure: we might include attributes included from somewhere else
*/
if (inc->includeAttribute()) {
m_attribute = context->m_attribute;
m_attributeContext = context->m_attributeContext ? context->m_attributeContext : context;
if (includeRules->includeAttribute()) {
m_attributeFormat = context->m_attributeFormat;
}
it = m_rules.erase(it);
for (const auto &rule : context->rules()) {
it = m_rules.insert(it, rule);
++it;
}
it = m_rules.insert(it, context->rules().begin(), context->rules().end());
it += context->rules().size();
}
m_resolveState = Resolved;
}
void Context::resolveAttributeFormat()
{
/**
* try to get our format from the definition we stem from
* we need to handle included attributes via m_attributeContext
*/
if (!m_attribute.isEmpty()) {
const auto def = m_attributeContext ? m_attributeContext->m_def.definition() : m_def.definition();
m_attributeFormat = DefinitionData::get(def)->formatByName(m_attribute);
if (!m_attributeFormat.isValid()) {
if (m_attributeContext) {
qCWarning(Log) << "Context: Unknown format" << m_attribute << "in context" << m_name << "of definition" << m_def.definition().name()
<< "from included context" << m_attributeContext->m_name << "of definition" << def.name();
} else {
qCWarning(Log) << "Context: Unknown format" << m_attribute << "in context" << m_name << "of definition" << m_def.definition().name();
}
}
}
/**
* lookup formats for our rules
*/
for (const auto &rule : m_rules) {
rule->resolveAttributeFormat(this);
}
}

View File

@@ -8,9 +8,8 @@
#define KSYNTAXHIGHLIGHTING_CONTEXT_P_H
#include "contextswitch_p.h"
#include "definition.h"
#include "definitionref_p.h"
#include "format.h"
#include "highlightingdata_p.hpp"
#include "rule_p.h"
#include <QString>
@@ -23,14 +22,18 @@ QT_END_NAMESPACE
namespace KSyntaxHighlighting
{
class DefinitionData;
class Context
{
public:
Context() = default;
~Context() = default;
Q_DISABLE_COPY(Context)
Definition definition() const;
void setDefinition(const DefinitionRef &def);
Context(Context &&) = default;
Context &operator=(Context &&) = default;
Context(const DefinitionData &def, const HighlightingContextData &data);
~Context() = default;
const QString &name() const
{
@@ -73,44 +76,28 @@ public:
*/
bool indentationBasedFoldingEnabled() const;
void load(QXmlStreamReader &reader);
void resolveContexts();
void resolveIncludes();
void resolveAttributeFormat();
void resolveContexts(DefinitionData &def, const HighlightingContextData &data);
void resolveIncludes(DefinitionData &def);
private:
Q_DISABLE_COPY(Context)
enum ResolveState : quint8 { Unresolved, Resolving, Resolved };
enum ResolveState { Unknown, Unresolved, Resolving, Resolved };
ResolveState resolveState();
std::vector<Rule::Ptr> m_rules;
DefinitionRef m_def;
QString m_name;
/**
* attribute name, to lookup our format
*/
QString m_attribute;
/**
* context to use for lookup, if != this context
*/
const Context *m_attributeContext = nullptr;
/**
* resolved format for our attribute, done in resolveAttributeFormat
*/
Format m_attributeFormat;
ContextSwitch m_lineEndContext;
ContextSwitch m_lineEmptyContext;
ContextSwitch m_fallthroughContext;
std::vector<Rule::Ptr> m_rules;
/**
* resolved format for our attribute, done in constructor and resolveIncludes
*/
Format m_attributeFormat;
ResolveState m_resolveState = Unknown;
ResolveState m_resolveState = Unresolved;
bool m_fallthrough = false;
bool m_noIndentationBasedFolding = false;
bool m_indentationBasedFolding;
};
}

View File

@@ -7,69 +7,45 @@
#include "contextswitch_p.h"
#include "definition.h"
#include "definition_p.h"
#include "highlightingdata_p.hpp"
#include "ksyntaxhighlighting_logging.h"
#include "repository.h"
using namespace KSyntaxHighlighting;
bool ContextSwitch::isStay() const
void ContextSwitch::resolve(DefinitionData &def, QStringView contextInstr)
{
return m_popCount == 0 && !m_context && m_contextName.isEmpty() && m_defName.isEmpty();
}
HighlightingContextData::ContextSwitch ctx(contextInstr);
int ContextSwitch::popCount() const
{
return m_popCount;
}
m_popCount = ctx.popCount();
m_isStay = !m_popCount;
Context *ContextSwitch::context() const
{
return m_context;
}
auto contextName = ctx.contextName();
auto defName = ctx.defName();
void ContextSwitch::parse(QStringView contextInstr)
{
if (contextInstr.isEmpty() || contextInstr == QLatin1String("#stay")) {
if (contextName.isEmpty() && defName.isEmpty()) {
return;
}
if (contextInstr.startsWith(QLatin1String("#pop!"))) {
++m_popCount;
m_contextName = contextInstr.mid(5).toString();
return;
}
if (contextInstr.startsWith(QLatin1String("#pop"))) {
++m_popCount;
parse(contextInstr.mid(4));
return;
}
const auto idx = contextInstr.indexOf(QLatin1String("##"));
if (idx >= 0) {
m_contextName = contextInstr.left(idx).toString();
m_defName = contextInstr.mid(idx + 2).toString();
if (defName.isEmpty()) {
m_context = def.contextByName(contextName.toString());
} else {
m_contextName = contextInstr.toString();
}
}
void ContextSwitch::resolve(const Definition &def)
{
auto d = def;
if (!m_defName.isEmpty()) {
d = DefinitionData::get(def)->repo->definitionForName(m_defName);
auto d = def.repo->definitionForName(defName.toString());
if (d.isValid()) {
auto data = DefinitionData::get(d);
def.addImmediateIncludedDefinition(d);
data->load();
if (m_contextName.isEmpty()) {
if (contextName.isEmpty()) {
m_context = data->initialContext();
} else {
m_context = data->contextByName(contextName.toString());
}
}
}
if (!m_contextName.isEmpty()) {
m_context = DefinitionData::get(d)->contextByName(m_contextName);
if (!m_context) {
qCWarning(Log) << "cannot find context" << m_contextName << "in" << def.name();
}
qCWarning(Log) << "cannot find context" << contextName << "in" << def.name;
} else {
m_isStay = false;
}
}

View File

@@ -12,7 +12,7 @@
namespace KSyntaxHighlighting
{
class Context;
class Definition;
class DefinitionData;
class ContextSwitch
{
@@ -20,19 +20,27 @@ public:
ContextSwitch() = default;
~ContextSwitch() = default;
bool isStay() const;
bool isStay() const
{
return m_isStay;
}
int popCount() const;
Context *context() const;
int popCount() const
{
return m_popCount;
}
void parse(QStringView contextInstr);
void resolve(const Definition &def);
Context *context() const
{
return m_context;
}
void resolve(DefinitionData &def, QStringView contextInstr);
private:
QString m_defName;
QString m_contextName;
Context *m_context = nullptr;
int m_popCount = 0;
bool m_isStay = true;
};
}

View File

@@ -14,6 +14,7 @@
#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"
@@ -39,10 +40,7 @@ DefinitionData::DefinitionData()
{
}
DefinitionData::~DefinitionData()
{
qDeleteAll(contexts);
}
DefinitionData::~DefinitionData() = default;
DefinitionData *DefinitionData::get(const Definition &def)
{
@@ -237,45 +235,23 @@ QVector<Definition> Definition::includedDefinitions() const
d->load();
// init worklist and result used as guard with this definition
QVector<Definition> queue{*this};
QVector<const DefinitionData *> queue{d.get()};
QVector<Definition> definitions{*this};
while (!queue.isEmpty()) {
// Iterate all context rules to find associated Definitions. This will
// automatically catch other Definitions referenced with IncludeRuldes or ContextSwitch.
const auto definition = queue.takeLast();
for (const auto &context : std::as_const(definition.d->contexts)) {
// handle context switch attributes of this context itself
for (const auto switchContext :
{context->lineEndContext().context(), context->lineEmptyContext().context(), context->fallthroughContext().context()}) {
if (switchContext) {
if (!definitions.contains(switchContext->definition())) {
queue.push_back(switchContext->definition());
definitions.push_back(switchContext->definition());
}
}
}
// handle the embedded rules
for (const auto &rule : context->rules()) {
// handle include rules like inclusion
if (!definitions.contains(rule->definition())) {
queue.push_back(rule->definition());
definitions.push_back(rule->definition());
}
// handle context switch context inclusion
if (auto switchContext = rule->context().context()) {
if (!definitions.contains(switchContext->definition())) {
queue.push_back(switchContext->definition());
definitions.push_back(switchContext->definition());
}
}
while (!queue.empty()) {
const auto *def = queue.back();
queue.pop_back();
for (const auto &defRef : def->immediateIncludedDefinitions) {
const auto definition = defRef.definition();
if (!definitions.contains(definition)) {
definitions.push_back(definition);
queue.push_back(definition.d.get());
}
}
}
// remove the 1st entry, since it is this Definition
definitions.pop_front();
definitions.front() = std::move(definitions.back());
definitions.pop_back();
return definitions;
}
@@ -304,17 +280,17 @@ QVector<QPair<QChar, QString>> Definition::characterEncodings() const
return d->characterEncodings;
}
Context *DefinitionData::initialContext() const
Context *DefinitionData::initialContext()
{
Q_ASSERT(!contexts.isEmpty());
return contexts.first();
Q_ASSERT(!contexts.empty());
return &contexts.front();
}
Context *DefinitionData::contextByName(const QString &wantedName) const
Context *DefinitionData::contextByName(const QString &wantedName)
{
for (const auto context : contexts) {
if (context->name() == wantedName) {
return context;
for (auto &context : contexts) {
if (context.name() == wantedName) {
return &context;
}
}
return nullptr;
@@ -338,7 +314,7 @@ Format DefinitionData::formatByName(const QString &wantedName) const
bool DefinitionData::isLoaded() const
{
return !contexts.isEmpty();
return !contexts.empty();
}
bool DefinitionData::load(OnlyKeywords onlyKeywords)
@@ -383,17 +359,7 @@ bool DefinitionData::load(OnlyKeywords onlyKeywords)
it->setCaseSensitivity(caseSensitive);
}
for (const auto context : std::as_const(contexts)) {
context->resolveContexts();
context->resolveIncludes();
context->resolveAttributeFormat();
}
for (const auto context : std::as_const(contexts)) {
for (const auto &rule : context->rules()) {
rule->resolvePostProcessing();
}
}
resolveContexts();
return true;
}
@@ -402,9 +368,21 @@ void DefinitionData::clear()
{
// keep only name and repo, so we can re-lookup to make references persist over repo reloads
keywordLists.clear();
qDeleteAll(contexts);
contexts.clear();
formats.clear();
contextDatas.clear();
immediateIncludedDefinitions.clear();
wordDelimiters = WordDelimiters();
wordWrapDelimiters = wordDelimiters;
keywordIsLoaded = false;
hasFoldingRegions = false;
indentationBasedFolding = false;
foldingIgnoreList.clear();
singleLineCommentMarker.clear();
singleLineCommentPosition = CommentPosition::StartOfLine;
multiLineCommentStartMarker.clear();
multiLineCommentEndMarker.clear();
characterEncodings.clear();
fileName.clear();
section.clear();
@@ -414,8 +392,6 @@ void DefinitionData::clear()
license.clear();
mimetypes.clear();
extensions.clear();
wordDelimiters = WordDelimiters();
wordWrapDelimiters = wordDelimiters;
caseSensitive = Qt::CaseSensitive;
version = 0.0f;
priority = 0;
@@ -563,14 +539,14 @@ 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")) {
auto context = new Context;
context->setDefinition(q);
context->load(reader);
contexts.push_back(context);
contextDatas.push_back(HighlightingContextData());
contextDatas.back().load(name, reader);
}
reader.readNext();
break;
@@ -583,6 +559,50 @@ void DefinitionData::loadContexts(QXmlStreamReader &reader)
}
}
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);
}
/**
* Post-processing on rules.
*/
for (const auto &context : contexts) {
for (auto &rule : context.rules()) {
rule->resolvePostProcessing();
}
}
}
void DefinitionData::loadItemData(QXmlStreamReader &reader)
{
Q_ASSERT(reader.name() == QLatin1String("itemDatas"));
@@ -635,8 +655,7 @@ void DefinitionData::loadGeneral(QXmlStreamReader &reader)
wordDelimiters.remove(reader.attributes().value(QLatin1String("weakDeliminator")));
// adapt WordWrapDelimiters
auto wordWrapDeliminatorAttr = reader.attributes().value(
QLatin1String("wordWrapDeliminator"));
auto wordWrapDeliminatorAttr = reader.attributes().value(QLatin1String("wordWrapDeliminator"));
if (wordWrapDeliminatorAttr.isEmpty()) {
wordWrapDelimiters = wordDelimiters;
} else {
@@ -803,21 +822,40 @@ quint16 DefinitionData::foldingRegionId(const QString &foldName)
return RepositoryPrivate::get(repo)->foldingRegionId(name, foldName);
}
DefinitionRef::DefinitionRef()
void DefinitionData::addImmediateIncludedDefinition(const Definition &def)
{
if (get(def) != this) {
DefinitionRef defRef(def);
if (!immediateIncludedDefinitions.contains(defRef)) {
immediateIncludedDefinitions.push_back(std::move(defRef));
}
}
}
DefinitionRef::DefinitionRef() = default;
DefinitionRef::DefinitionRef(const Definition &def)
: d(def.d)
{
}
DefinitionRef::DefinitionRef(Definition &&def)
: d(std::move(def.d))
{
}
DefinitionRef &DefinitionRef::operator=(const Definition &def)
{
d = def.d;
return *this;
}
DefinitionRef &DefinitionRef::operator=(Definition &&def)
{
d = std::move(def.d);
return *this;
}
Definition DefinitionRef::definition() const
{
if (!d.expired()) {
@@ -828,9 +866,5 @@ Definition DefinitionRef::definition() const
bool DefinitionRef::operator==(const DefinitionRef &other) const
{
if (d.expired() != other.d.expired()) {
return false;
}
return d.expired() || d.lock().get() == other.d.lock().get();
return !d.owner_before(other.d) && !other.d.owner_before(d);
}

View File

@@ -8,14 +8,16 @@
#ifndef KSYNTAXHIGHLIGHTING_DEFINITION_P_H
#define KSYNTAXHIGHLIGHTING_DEFINITION_P_H
#include "definition.h"
#include "definitionref_p.h"
#include "highlightingdata_p.hpp"
#include "worddelimiters_p.h"
#include <QHash>
#include <QString>
#include <QVector>
#include <vector>
QT_BEGIN_NAMESPACE
class QCborMap;
class QXmlStreamReader;
@@ -55,23 +57,31 @@ public:
void loadSpellchecking(QXmlStreamReader &reader);
bool checkKateVersion(QStringView verStr);
void resolveContexts();
void resolveIncludeKeywords();
KeywordList *keywordList(const QString &name);
Context *initialContext() const;
Context *contextByName(const QString &name) const;
Context *initialContext();
Context *contextByName(const QString &name);
Format formatByName(const QString &name) const;
quint16 foldingRegionId(const QString &foldName);
void addImmediateIncludedDefinition(const Definition &def);
DefinitionRef q;
Repository *repo = nullptr;
QHash<QString, KeywordList> keywordLists;
QVector<Context *> contexts;
std::vector<Context> contexts;
QHash<QString, Format> formats;
// data loaded from xml file and emptied after loading contexts
QVector<HighlightingContextData> contextDatas;
// Definition referenced by IncludeRules and ContextSwitch
QVector<DefinitionRef> immediateIncludedDefinitions;
WordDelimiters wordDelimiters;
WordDelimiters wordWrapDelimiters;
bool keywordIsLoaded = false;

View File

@@ -172,8 +172,7 @@ void DefinitionDownloader::start()
const QString url = QLatin1String("https://www.kate-editor.org/syntax/update-") + QString::number(SyntaxHighlighting_VERSION_MAJOR) + QLatin1Char('.')
+ QString::number(SyntaxHighlighting_VERSION_MINOR) + QLatin1String(".xml");
auto req = QNetworkRequest(QUrl(url));
req.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
QNetworkRequest::NoLessSafeRedirectPolicy);
req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
auto reply = d->nam->get(req);
QObject::connect(reply, &QNetworkReply::finished, this, [=]() {
d->definitionListDownloadFinished(reply);

View File

@@ -59,7 +59,7 @@ public:
/**
* Destructor.
*/
~DefinitionDownloader();
~DefinitionDownloader() override;
/**
* Starts the update procedure.

View File

@@ -7,6 +7,8 @@
#ifndef KSYNTAXHIGHLIGHTING_DEFINITIONREF_P_H
#define KSYNTAXHIGHLIGHTING_DEFINITIONREF_P_H
#include "definition.h"
#include <memory>
namespace KSyntaxHighlighting
@@ -29,7 +31,9 @@ class DefinitionRef
public:
DefinitionRef();
explicit DefinitionRef(const Definition &def);
explicit DefinitionRef(Definition &&def);
DefinitionRef &operator=(const Definition &def);
DefinitionRef &operator=(Definition &&def);
Definition definition() const;

View File

@@ -0,0 +1,402 @@
/*
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.attributeName = attrs.value(QLatin1String("attribute")).toString();
rule.common.firstNonSpace = Xml::attrToBool(attrs.value(QLatin1String("firstNonSpace")));
rule.common.lookAhead = Xml::attrToBool(attrs.value(QLatin1String("lookAhead")));
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();
});
}
HighlightingContextData::ContextSwitch::ContextSwitch(QStringView str)
{
if (str.isEmpty() || str == QStringLiteral("#stay")) {
return;
}
while (str.startsWith(QStringLiteral("#pop"))) {
++m_popCount;
if (str.size() > 4 && str.at(4) == QLatin1Char('!')) {
str = str.mid(5);
break;
}
str = str.mid(4);
}
if (str.isEmpty()) {
return;
}
m_contextAndDefName = str.toString();
m_defNameIndex = str.indexOf(QStringLiteral("##"));
}
bool HighlightingContextData::ContextSwitch::isStay() const
{
return m_popCount == -1 && m_contextAndDefName.isEmpty();
}
QStringView HighlightingContextData::ContextSwitch::contextName() const
{
if (m_defNameIndex == -1) {
return m_contextAndDefName;
}
return QStringView(m_contextAndDefName).left(m_defNameIndex);
}
QStringView HighlightingContextData::ContextSwitch::defName() const
{
if (m_defNameIndex == -1) {
return QStringView();
}
return QStringView(m_contextAndDefName).mid(m_defNameIndex + 2);
}
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")));
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;
}
}
}

View File

@@ -0,0 +1,215 @@
/*
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 <QStringList>
#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 ContextSwitch {
ContextSwitch() = default;
ContextSwitch(QStringView str);
QStringView contextName() const;
QStringView defName() const;
bool isStay() const;
int popCount() const
{
return m_popCount;
}
private:
int m_popCount = 0;
int m_defNameIndex = -1;
QString m_contextAndDefName;
};
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 noIndentationBasedFolding = false;
};
}
#endif

View File

@@ -44,6 +44,11 @@ public:
return m_keywords;
}
Qt::CaseSensitivity caseSensitivity() const
{
return m_caseSensitive;
}
void setKeywordList(const QStringList &keywords)
{
m_keywords = keywords;
@@ -53,7 +58,10 @@ public:
}
/** Checks if @p str is a keyword in this list. */
bool contains(QStringView str) const { return contains(str, m_caseSensitive); }
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;

View File

@@ -14,7 +14,6 @@
#include "xml_p.h"
#include <QString>
#include <QXmlStreamReader>
using namespace KSyntaxHighlighting;
@@ -97,172 +96,137 @@ static QString replaceCaptures(const QString &pattern, const QStringList &captur
return result;
}
Rule::~Rule()
static MatchResult matchString(QStringView pattern, QStringView text, int offset, Qt::CaseSensitivity caseSensitivity)
{
if (!m_additionalDeliminator.isEmpty() || !m_weakDeliminator.isEmpty()) {
delete m_wordDelimiters;
if (offset + pattern.size() <= text.size() && text.mid(offset, pattern.size()).compare(pattern, caseSensitivity) == 0) {
return offset + pattern.size();
}
return offset;
}
Definition Rule::definition() const
static void resolveAdditionalWordDelimiters(WordDelimiters &wordDelimiters, const HighlightingContextData::Rule::WordDelimiters &delimiters)
{
return m_def.definition();
}
void Rule::setDefinition(const Definition &def)
{
m_def = def;
}
bool Rule::load(QXmlStreamReader &reader)
{
Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement);
m_attribute = reader.attributes().value(QLatin1String("attribute")).toString();
if (reader.name() != QLatin1String("IncludeRules")) { // IncludeRules uses this with a different semantic
m_context.parse(reader.attributes().value(QLatin1String("context")));
}
m_firstNonSpace = Xml::attrToBool(reader.attributes().value(QLatin1String("firstNonSpace")));
m_lookAhead = Xml::attrToBool(reader.attributes().value(QLatin1String("lookAhead")));
bool colOk = false;
m_column = reader.attributes().value(QLatin1String("column")).toInt(&colOk);
if (!colOk) {
m_column = -1;
}
auto regionName = reader.attributes().value(QLatin1String("beginRegion"));
if (!regionName.isEmpty()) {
m_beginRegion = FoldingRegion(FoldingRegion::Begin, DefinitionData::get(m_def.definition())->foldingRegionId(regionName.toString()));
}
regionName = reader.attributes().value(QLatin1String("endRegion"));
if (!regionName.isEmpty()) {
m_endRegion = FoldingRegion(FoldingRegion::End, DefinitionData::get(m_def.definition())->foldingRegionId(regionName.toString()));
}
auto result = doLoad(reader);
if (m_lookAhead && m_context.isStay()) {
result = false;
}
// be done with this rule, skip all subelements, e.g. no longer supported sub-rules
reader.skipCurrentElement();
return result;
}
void Rule::resolveContext()
{
auto const &def = m_def.definition();
m_context.resolve(def);
// cache for DefinitionData::wordDelimiters, is accessed VERY often
m_wordDelimiters = &DefinitionData::get(def)->wordDelimiters;
if (!m_additionalDeliminator.isEmpty() || !m_weakDeliminator.isEmpty()) {
m_wordDelimiters = new WordDelimiters(*m_wordDelimiters);
m_wordDelimiters->append(m_additionalDeliminator);
m_wordDelimiters->remove(m_weakDeliminator);
if (!delimiters.additionalDeliminator.isEmpty() || !delimiters.weakDeliminator.isEmpty()) {
wordDelimiters.append(QStringView(delimiters.additionalDeliminator));
wordDelimiters.remove(QStringView(delimiters.weakDeliminator));
}
}
void Rule::resolveAttributeFormat(Context *lookupContext)
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 (!m_attribute.isEmpty()) {
m_attributeFormat = DefinitionData::get(definition())->formatByName(m_attribute);
if (!ruleData.common.attributeName.isEmpty()) {
m_attributeFormat = def.formatByName(ruleData.common.attributeName);
if (!m_attributeFormat.isValid()) {
qCWarning(Log) << "Rule: Unknown format" << m_attribute << "in context" << lookupContext->name() << "of definition" << definition().name();
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());
}
bool Rule::doLoad(QXmlStreamReader &reader)
static Rule::Ptr createRule(DefinitionData &def, const HighlightingContextData::Rule &ruleData, QStringView lookupContextName)
{
Q_UNUSED(reader);
return true;
}
using Type = HighlightingContextData::Rule::Type;
void Rule::loadAdditionalWordDelimiters(QXmlStreamReader &reader)
{
m_additionalDeliminator = reader.attributes().value(QLatin1String("additionalDeliminator")).toString();
m_weakDeliminator = reader.attributes().value(QLatin1String("weakDeliminator")).toString();
}
Rule::Ptr Rule::create(QStringView name)
{
if (name == QLatin1String("AnyChar")) {
return std::make_shared<AnyChar>();
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:
return std::make_shared<RegExpr>(ruleData.data.regExpr);
case Type::StringDetect:
if (ruleData.data.stringDetect.dynamic) {
return std::make_shared<DynamicStringDetect>(ruleData.data.stringDetect);
}
if (name == QLatin1String("DetectChar")) {
return std::make_shared<DetectChar>();
}
if (name == QLatin1String("Detect2Chars")) {
return std::make_shared<Detect2Char>();
}
if (name == QLatin1String("DetectIdentifier")) {
return std::make_shared<DetectIdentifier>();
}
if (name == QLatin1String("DetectSpaces")) {
return std::make_shared<DetectSpaces>();
}
if (name == QLatin1String("Float")) {
return std::make_shared<Float>();
}
if (name == QLatin1String("Int")) {
return std::make_shared<Int>();
}
if (name == QLatin1String("HlCChar")) {
return std::make_shared<HlCChar>();
}
if (name == QLatin1String("HlCHex")) {
return std::make_shared<HlCHex>();
}
if (name == QLatin1String("HlCOct")) {
return std::make_shared<HlCOct>();
}
if (name == QLatin1String("HlCStringChar")) {
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>();
}
if (name == QLatin1String("IncludeRules")) {
return std::make_shared<IncludeRules>();
}
if (name == QLatin1String("keyword")) {
return std::make_shared<KeywordListRule>();
}
if (name == QLatin1String("LineContinue")) {
return std::make_shared<LineContinue>();
}
if (name == QLatin1String("RangeDetect")) {
return std::make_shared<RangeDetect>();
}
if (name == QLatin1String("RegExpr")) {
return std::make_shared<RegExpr>();
}
if (name == QLatin1String("StringDetect")) {
return std::make_shared<StringDetect>();
}
if (name == QLatin1String("WordDetect")) {
return std::make_shared<WordDetect>();
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:;
}
qCWarning(Log) << "Unknown rule type:" << name;
return Ptr(nullptr);
return Rule::Ptr(nullptr);
}
bool Rule::isWordDelimiter(QChar c) const
Rule::Ptr Rule::create(DefinitionData &def, const HighlightingContextData::Rule &ruleData, QStringView lookupContextName)
{
return m_wordDelimiters->contains(c);
auto rule = createRule(def, ruleData, lookupContextName);
if (rule && !rule->resolveCommon(def, ruleData, lookupContextName)) {
rule.reset();
}
return rule;
}
bool AnyChar::doLoad(QXmlStreamReader &reader)
AnyChar::AnyChar(const HighlightingContextData::Rule::AnyChar &data)
: m_chars(data.chars)
{
m_chars = reader.attributes().value(QLatin1String("String")).toString();
if (m_chars.size() == 1) {
qCDebug(Log) << "AnyChar rule with just one char: use DetectChar instead.";
}
return !m_chars.isEmpty();
}
MatchResult AnyChar::doMatch(QStringView text, int offset, const QStringList &) const
@@ -273,18 +237,11 @@ MatchResult AnyChar::doMatch(QStringView text, int offset, const QStringList &)
return offset;
}
bool DetectChar::doLoad(QXmlStreamReader &reader)
DetectChar::DetectChar(const HighlightingContextData::Rule::DetectChar &data)
: m_char(data.char1)
, m_captureIndex(data.dynamic ? data.char1.digitValue() : 0)
{
const auto s = reader.attributes().value(QLatin1String("char"));
if (s.isEmpty()) {
return false;
}
m_char = s.at(0);
m_dynamic = Xml::attrToBool(reader.attributes().value(QLatin1String("dynamic")));
if (m_dynamic) {
m_captureIndex = m_char.digitValue();
}
return true;
m_dynamic = data.dynamic;
}
MatchResult DetectChar::doMatch(QStringView text, int offset, const QStringList &captures) const
@@ -305,19 +262,13 @@ MatchResult DetectChar::doMatch(QStringView text, int offset, const QStringList
return offset;
}
bool Detect2Char::doLoad(QXmlStreamReader &reader)
Detect2Chars::Detect2Chars(const HighlightingContextData::Rule::Detect2Chars &data)
: m_char1(data.char1)
, m_char2(data.char2)
{
const auto s1 = reader.attributes().value(QLatin1String("char"));
const auto s2 = reader.attributes().value(QLatin1String("char1"));
if (s1.isEmpty() || s2.isEmpty()) {
return false;
}
m_char1 = s1.at(0);
m_char2 = s2.at(0);
return true;
}
MatchResult Detect2Char::doMatch(QStringView text, int offset, const QStringList &) const
MatchResult Detect2Chars::doMatch(QStringView text, int offset, const QStringList &) const
{
if (text.size() - offset < 2) {
return offset;
@@ -352,15 +303,15 @@ MatchResult DetectSpaces::doMatch(QStringView text, int offset, const QStringLis
return offset;
}
bool Float::doLoad(QXmlStreamReader &reader)
Float::Float(DefinitionData &def, const HighlightingContextData::Rule::Float &data)
: m_wordDelimiters(def.wordDelimiters)
{
loadAdditionalWordDelimiters(reader);
return true;
resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
}
MatchResult Float::doMatch(QStringView text, int offset, const QStringList &) const
{
if (offset > 0 && !isWordDelimiter(text.at(offset - 1))) {
if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1))) {
return offset;
}
@@ -432,15 +383,15 @@ MatchResult HlCChar::doMatch(QStringView text, int offset, const QStringList &)
return offset;
}
bool HlCHex::doLoad(QXmlStreamReader &reader)
HlCHex::HlCHex(DefinitionData &def, const HighlightingContextData::Rule::HlCHex &data)
: m_wordDelimiters(def.wordDelimiters)
{
loadAdditionalWordDelimiters(reader);
return true;
resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
}
MatchResult HlCHex::doMatch(QStringView text, int offset, const QStringList &) const
{
if (offset > 0 && !isWordDelimiter(text.at(offset - 1))) {
if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1))) {
return offset;
}
@@ -466,15 +417,15 @@ MatchResult HlCHex::doMatch(QStringView text, int offset, const QStringList &) c
return offset;
}
bool HlCOct::doLoad(QXmlStreamReader &reader)
HlCOct::HlCOct(DefinitionData &def, const HighlightingContextData::Rule::HlCOct &data)
: m_wordDelimiters(def.wordDelimiters)
{
loadAdditionalWordDelimiters(reader);
return true;
resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
}
MatchResult HlCOct::doMatch(QStringView text, int offset, const QStringList &) const
{
if (offset > 0 && !isWordDelimiter(text.at(offset - 1))) {
if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1))) {
return offset;
}
@@ -503,53 +454,28 @@ MatchResult HlCStringChar::doMatch(QStringView text, int offset, const QStringLi
return matchEscapedChar(text, offset);
}
QString IncludeRules::contextName() const
IncludeRules::IncludeRules(const HighlightingContextData::Rule::IncludeRules &data)
: m_contextName(data.contextName)
, m_includeAttribute(data.includeAttribute)
{
return m_contextName;
}
QString IncludeRules::definitionName() const
{
return m_defName;
}
bool IncludeRules::includeAttribute() const
{
return m_includeAttribute;
}
bool IncludeRules::doLoad(QXmlStreamReader &reader)
{
const auto s = reader.attributes().value(QLatin1String("context"));
const auto split = s.split(QString::fromLatin1("##"), Qt::KeepEmptyParts);
if (split.isEmpty()) {
return false;
}
m_contextName = split.at(0).toString();
if (split.size() > 1) {
m_defName = split.at(1).toString();
}
m_includeAttribute = Xml::attrToBool(reader.attributes().value(QLatin1String("includeAttrib")));
return !m_contextName.isEmpty() || !m_defName.isEmpty();
}
MatchResult IncludeRules::doMatch(QStringView text, int offset, const QStringList &) const
{
Q_UNUSED(text);
qCWarning(Log) << "Unresolved include rule for" << m_contextName << "##" << m_defName;
qCWarning(Log) << "Unresolved include rule";
return offset;
}
bool Int::doLoad(QXmlStreamReader &reader)
Int::Int(DefinitionData &def, const HighlightingContextData::Rule::Int &data)
: m_wordDelimiters(def.wordDelimiters)
{
loadAdditionalWordDelimiters(reader);
return true;
resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
}
MatchResult Int::doMatch(QStringView text, int offset, const QStringList &) const
{
if (offset > 0 && !isWordDelimiter(text.at(offset - 1))) {
if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1))) {
return offset;
}
@@ -559,67 +485,61 @@ MatchResult Int::doMatch(QStringView text, int offset, const QStringList &) cons
return offset;
}
bool KeywordListRule::doLoad(QXmlStreamReader &reader)
Rule::Ptr KeywordListRule::create(DefinitionData &def, const HighlightingContextData::Rule::Keyword &data, QStringView lookupContextName)
{
/**
* get our keyword list, if not found => bail out
*/
auto defData = DefinitionData::get(definition());
m_keywordList = defData->keywordList(reader.attributes().value(QLatin1String("String")).toString());
if (!m_keywordList) {
return false;
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 (reader.attributes().hasAttribute(QLatin1String("insensitive"))) {
m_hasCaseSensitivityOverride = true;
m_caseSensitivityOverride = Xml::attrToBool(reader.attributes().value(QLatin1String("insensitive"))) ? Qt::CaseInsensitive : Qt::CaseSensitive;
m_keywordList->initLookupForCaseSensitivity(m_caseSensitivityOverride);
} else {
m_hasCaseSensitivityOverride = false;
if (data.hasCaseSensitivityOverride) {
keywordList->initLookupForCaseSensitivity(data.caseSensitivityOverride);
}
loadAdditionalWordDelimiters(reader);
return std::make_shared<KeywordListRule>(*keywordList, def, data);
}
return !m_keywordList->isEmpty();
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);
}
MatchResult KeywordListRule::doMatch(QStringView text, int offset, const QStringList &) const
{
auto newOffset = offset;
while (text.size() > newOffset && !isWordDelimiter(text.at(newOffset))) {
while (text.size() > newOffset && !m_wordDelimiters.contains(text.at(newOffset))) {
++newOffset;
}
if (newOffset == offset) {
return offset;
}
if (m_hasCaseSensitivityOverride) {
if (m_keywordList->contains(text.mid(offset, newOffset - offset), m_caseSensitivityOverride)) {
if (m_keywordList.contains(text.mid(offset, newOffset - offset), m_caseSensitivity)) {
return newOffset;
}
} else {
if (m_keywordList->contains(text.mid(offset, newOffset - offset))) {
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);
}
bool LineContinue::doLoad(QXmlStreamReader &reader)
LineContinue::LineContinue(const HighlightingContextData::Rule::LineContinue &data)
: m_char(data.char1)
{
const auto s = reader.attributes().value(QLatin1String("char"));
if (s.isEmpty()) {
m_char = QLatin1Char('\\');
} else {
m_char = s.at(0);
}
return true;
}
MatchResult LineContinue::doMatch(QStringView text, int offset, const QStringList &) const
@@ -630,16 +550,10 @@ MatchResult LineContinue::doMatch(QStringView text, int offset, const QStringLis
return offset;
}
bool RangeDetect::doLoad(QXmlStreamReader &reader)
RangeDetect::RangeDetect(const HighlightingContextData::Rule::RangeDetect &data)
: m_begin(data.begin)
, m_end(data.end)
{
const auto s1 = reader.attributes().value(QLatin1String("char"));
const auto s2 = reader.attributes().value(QLatin1String("char1"));
if (s1.isEmpty() || s2.isEmpty()) {
return false;
}
m_begin = s1.at(0);
m_end = s2.at(0);
return true;
}
MatchResult RangeDetect::doMatch(QStringView text, int offset, const QStringList &) const
@@ -661,25 +575,20 @@ MatchResult RangeDetect::doMatch(QStringView text, int offset, const QStringList
return offset;
}
bool RegExpr::doLoad(QXmlStreamReader &reader)
RegExpr::RegExpr(const HighlightingContextData::Rule::RegExpr &data)
{
m_regexp.setPattern(reader.attributes().value(QLatin1String("String")).toString());
const auto isMinimal = Xml::attrToBool(reader.attributes().value(QLatin1String("minimal")));
const auto isCaseInsensitive = Xml::attrToBool(reader.attributes().value(QLatin1String("insensitive")));
m_regexp.setPatternOptions((isMinimal ? QRegularExpression::InvertedGreedinessOption : QRegularExpression::NoPatternOption)
| (isCaseInsensitive ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption)
m_regexp.setPattern(data.pattern);
m_regexp.setPatternOptions((data.isMinimal ? QRegularExpression::InvertedGreedinessOption : QRegularExpression::NoPatternOption)
| (data.caseSensitivity == Qt::CaseInsensitive ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption)
// DontCaptureOption is removed by resolvePostProcessing() when necessary
| QRegularExpression::DontCaptureOption
// ensure Unicode support is enabled
| QRegularExpression::UseUnicodePropertiesOption);
m_dynamic = Xml::attrToBool(reader.attributes().value(QLatin1String("dynamic")));
return !m_regexp.pattern().isEmpty();
m_dynamic = data.dynamic;
}
void KSyntaxHighlighting::RegExpr::resolvePostProcessing()
void RegExpr::resolvePostProcessing()
{
if (m_isResolved) {
return;
@@ -728,11 +637,7 @@ MatchResult RegExpr::doMatch(QStringView text, int offset, const QStringList &ca
/**
* match the pattern
*/
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 2)
const auto result = regexp.match(text.toString(), offset, QRegularExpression::NormalMatch, QRegularExpression::DontCheckSubjectStringMatchOption);
#else
const auto result = regexp.match(text, offset, QRegularExpression::NormalMatch, QRegularExpression::DontCheckSubjectStringMatchOption);
#endif
if (result.capturedStart() == offset) {
/**
* we only need to compute the captured texts if we have real capture groups
@@ -756,33 +661,39 @@ MatchResult RegExpr::doMatch(QStringView text, int offset, const QStringList &ca
return MatchResult(offset, result.capturedStart());
}
bool StringDetect::doLoad(QXmlStreamReader &reader)
StringDetect::StringDetect(const HighlightingContextData::Rule::StringDetect &data)
: m_string(data.string)
, m_caseSensitivity(data.caseSensitivity)
{
m_string = reader.attributes().value(QLatin1String("String")).toString();
m_caseSensitivity = Xml::attrToBool(reader.attributes().value(QLatin1String("insensitive"))) ? Qt::CaseInsensitive : Qt::CaseSensitive;
m_dynamic = Xml::attrToBool(reader.attributes().value(QLatin1String("dynamic")));
return !m_string.isEmpty();
}
MatchResult StringDetect::doMatch(QStringView text, int offset, const QStringList &captures) const
MatchResult StringDetect::doMatch(QStringView text, int offset, const QStringList &) 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) const
{
/**
* for dynamic case: create new pattern with right instantiation
*/
const auto &pattern = m_dynamic ? replaceCaptures(m_string, captures, false) : m_string;
if (offset + pattern.size() <= text.size() && text.mid(offset, pattern.size()).compare(pattern, m_caseSensitivity) == 0) {
return offset + pattern.size();
}
return offset;
const auto pattern = replaceCaptures(m_string, captures, false);
return matchString(pattern, text, offset, m_caseSensitivity);
}
bool WordDetect::doLoad(QXmlStreamReader &reader)
WordDetect::WordDetect(DefinitionData &def, const HighlightingContextData::Rule::WordDetect &data)
: m_wordDelimiters(def.wordDelimiters)
, m_word(data.word)
, m_caseSensitivity(data.caseSensitivity)
{
m_word = reader.attributes().value(QLatin1String("String")).toString();
m_caseSensitivity = Xml::attrToBool(reader.attributes().value(QLatin1String("insensitive"))) ? Qt::CaseInsensitive : Qt::CaseSensitive;
loadAdditionalWordDelimiters(reader);
return !m_word.isEmpty();
resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
}
MatchResult WordDetect::doMatch(QStringView text, int offset, const QStringList &) const
@@ -795,7 +706,7 @@ MatchResult WordDetect::doMatch(QStringView text, int offset, const QStringList
* detect delimiter characters on the inner and outer boundaries of the string
* NOTE: m_word isn't empty
*/
if (offset > 0 && !isWordDelimiter(text.at(offset - 1)) && !isWordDelimiter(text.at(offset))) {
if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1)) && !m_wordDelimiters.contains(text.at(offset))) {
return offset;
}
@@ -803,7 +714,8 @@ MatchResult WordDetect::doMatch(QStringView text, int offset, const QStringList
return offset;
}
if (text.size() == offset + m_word.size() || isWordDelimiter(text.at(offset + m_word.size())) || isWordDelimiter(text.at(offset + m_word.size() - 1))) {
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();
}

View File

@@ -9,25 +9,24 @@
#define KSYNTAXHIGHLIGHTING_RULE_P_H
#include "contextswitch_p.h"
#include "definition.h"
#include "definitionref_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>
QT_BEGIN_NAMESPACE
class QXmlStreamReader;
QT_END_NAMESPACE
namespace KSyntaxHighlighting
{
class WordDelimiters;
class DefinitionData;
class IncludeRules;
class Rule
{
@@ -37,9 +36,6 @@ public:
typedef std::shared_ptr<Rule> Ptr;
Definition definition() const;
void setDefinition(const Definition &def);
const Format &attributeFormat() const
{
return m_attributeFormat;
@@ -80,51 +76,51 @@ public:
return m_endRegion;
}
bool load(QXmlStreamReader &reader);
void resolveContext();
void resolveAttributeFormat(Context *lookupContext);
const IncludeRules *castToIncludeRules() const;
bool isLineContinue() const
{
return m_type == Type::LineContinue;
}
virtual void resolvePostProcessing()
{
}
virtual MatchResult doMatch(QStringView text, int offset, const QStringList &captures) const = 0;
static Rule::Ptr create(QStringView name);
protected:
virtual bool doLoad(QXmlStreamReader &reader);
bool isWordDelimiter(QChar c) const;
void loadAdditionalWordDelimiters(QXmlStreamReader &reader);
static Rule::Ptr create(DefinitionData &def, const HighlightingContextData::Rule &ruleData, QStringView lookupContextName);
private:
Q_DISABLE_COPY(Rule)
DefinitionRef m_def;
QString m_attribute;
bool resolveCommon(DefinitionData &def, const HighlightingContextData::Rule &ruleData, QStringView lookupContextName);
enum class Type : quint8 {
OtherRule,
LineContinue,
IncludeRules,
};
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;
// cache for DefinitionData::wordDelimiters, is accessed VERY often
WordDelimiters *m_wordDelimiters = nullptr;
QString m_additionalDeliminator;
QString m_weakDeliminator;
protected:
bool m_dynamic = false;
};
class AnyChar final : public Rule
{
public:
AnyChar(const HighlightingContextData::Rule::AnyChar &data);
protected:
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &) const override;
private:
@@ -133,20 +129,24 @@ private:
class DetectChar final : public Rule
{
public:
DetectChar(const HighlightingContextData::Rule::DetectChar &data);
protected:
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &captures) const override;
MatchResult doMatch(QStringView text, int offset, const QStringList &) const override;
private:
QChar m_char;
int m_captureIndex = 0;
};
class Detect2Char final : public Rule
class Detect2Chars final : public Rule
{
public:
Detect2Chars(const HighlightingContextData::Rule::Detect2Chars &data);
protected:
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &captures) const override;
MatchResult doMatch(QStringView text, int offset, const QStringList &) const override;
private:
QChar m_char1;
@@ -167,33 +167,49 @@ protected:
class Float final : public Rule
{
public:
Float(DefinitionData &def, const HighlightingContextData::Rule::Float &data);
protected:
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &) const override;
private:
WordDelimiters m_wordDelimiters;
};
class IncludeRules final : public Rule
{
public:
QString contextName() const;
QString definitionName() const;
bool includeAttribute() const;
IncludeRules(const HighlightingContextData::Rule::IncludeRules &data);
const QString &contextName() const
{
return m_contextName;
}
bool includeAttribute() const
{
return m_includeAttribute;
}
protected:
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &) const override;
private:
QString m_contextName;
QString m_defName;
bool m_includeAttribute;
};
class Int final : public Rule
{
public:
Int(DefinitionData &def, const HighlightingContextData::Rule::Int &data);
protected:
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &captures) const override;
MatchResult doMatch(QStringView text, int offset, const QStringList &) const override;
private:
WordDelimiters m_wordDelimiters;
};
class HlCChar final : public Rule
@@ -204,16 +220,26 @@ protected:
class HlCHex final : public Rule
{
public:
HlCHex(DefinitionData &def, const HighlightingContextData::Rule::HlCHex &data);
protected:
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &) const override;
private:
WordDelimiters m_wordDelimiters;
};
class HlCOct final : public Rule
{
public:
HlCOct(DefinitionData &def, const HighlightingContextData::Rule::HlCOct &data);
protected:
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &) const override;
private:
WordDelimiters m_wordDelimiters;
};
class HlCStringChar final : public Rule
@@ -224,20 +250,26 @@ protected:
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:
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &) const override;
private:
KeywordList *m_keywordList;
bool m_hasCaseSensitivityOverride;
Qt::CaseSensitivity m_caseSensitivityOverride;
WordDelimiters m_wordDelimiters;
const KeywordList &m_keywordList;
Qt::CaseSensitivity m_caseSensitivity;
};
class LineContinue final : public Rule
{
public:
LineContinue(const HighlightingContextData::Rule::LineContinue &data);
protected:
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &) const override;
private:
@@ -246,8 +278,10 @@ private:
class RangeDetect final : public Rule
{
public:
RangeDetect(const HighlightingContextData::Rule::RangeDetect &data);
protected:
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &) const override;
private:
@@ -257,10 +291,12 @@ private:
class RegExpr final : public Rule
{
public:
RegExpr(const HighlightingContextData::Rule::RegExpr &data);
protected:
MatchResult doMatch(QStringView text, int offset, const QStringList &) const override;
void resolvePostProcessing() override;
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &captures) const override;
private:
QRegularExpression m_regexp;
@@ -269,9 +305,24 @@ private:
class StringDetect final : public Rule
{
public:
StringDetect(const HighlightingContextData::Rule::StringDetect &data);
protected:
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &captures) const override;
MatchResult doMatch(QStringView text, int offset, const QStringList &) 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 &) const override;
private:
QString m_string;
@@ -280,11 +331,14 @@ private:
class WordDetect final : public Rule
{
public:
WordDetect(DefinitionData &def, const HighlightingContextData::Rule::WordDetect &data);
protected:
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(QStringView text, int offset, const QStringList &captures) const override;
MatchResult doMatch(QStringView text, int offset, const QStringList &) const override;
private:
WordDelimiters m_wordDelimiters;
QString m_word;
Qt::CaseSensitivity m_caseSensitivity;
};

View File

@@ -9,7 +9,6 @@
using namespace KSyntaxHighlighting;
#include <QChar>
#include <QStringView>
namespace
{

View File

@@ -12,15 +12,14 @@ WordDelimiters::WordDelimiters()
: asciiDelimiters{}
{
for (const char *p = "\t !%&()*+,-./:;<=>?[\\]^{|}~"; *p; ++p) {
// int(*p) fix -Wchar-subscripts
asciiDelimiters[int(*p)] = true;
asciiDelimiters.set(*p);
}
}
bool WordDelimiters::contains(QChar c) const
{
if (c.unicode() < 128) {
return asciiDelimiters[c.unicode()];
return asciiDelimiters.test(c.unicode());
}
// perf tells contains is MUCH faster than binary search here, very short array
return notAsciiDelimiters.contains(c);
@@ -30,7 +29,7 @@ void WordDelimiters::append(QStringView s)
{
for (QChar c : s) {
if (c.unicode() < 128) {
asciiDelimiters[c.unicode()] = true;
asciiDelimiters.set(c.unicode());
} else {
notAsciiDelimiters.append(c);
}
@@ -41,7 +40,7 @@ void WordDelimiters::remove(QStringView s)
{
for (QChar c : s) {
if (c.unicode() < 128) {
asciiDelimiters[c.unicode()] = false;
asciiDelimiters.set(c.unicode(), false);
} else {
notAsciiDelimiters.remove(c);
}

View File

@@ -9,6 +9,8 @@
#include <QString>
#include <bitset>
namespace KSyntaxHighlighting
{
/**
@@ -44,7 +46,7 @@ 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.
*/
bool asciiDelimiters[128];
std::bitset<128> asciiDelimiters;
/**
* Contains characters that are not ascii and is empty for most syntax definition.