├── .clang-format ├── .editorconfig ├── .github └── workflows │ ├── build_test.yml │ └── check_format.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE.MIT ├── README.md ├── example ├── CMakeLists.txt ├── image │ └── preview.png ├── include │ └── MainWindow.hpp ├── resources │ ├── code_samples │ │ ├── cxx.cpp │ │ ├── java.java │ │ ├── js.js │ │ ├── json.json │ │ ├── lua.lua │ │ ├── python.py │ │ ├── shader.glsl │ │ └── xml.xml │ ├── demo_resources.qrc │ └── styles │ │ └── drakula.xml └── src │ ├── MainWindow.cpp │ └── main.cpp ├── include ├── QCXXHighlighter ├── QCodeEditor ├── QGLSLCompleter ├── QGLSLHighlighter ├── QHighlightBlockRule ├── QHighlightRule ├── QJSHighlighter ├── QJSONHighlighter ├── QJavaHighlighter ├── QLanguage ├── QLineNumberArea ├── QLuaCompleter ├── QLuaHighlighter ├── QPythonCompleter ├── QPythonHighlighter ├── QStyleSyntaxHighlighter ├── QSyntaxStyle ├── QXMLHighlighter └── internal │ ├── QCXXHighlighter.hpp │ ├── QCodeEditor.hpp │ ├── QGLSLCompleter.hpp │ ├── QGLSLHighlighter.hpp │ ├── QHighlightBlockRule.hpp │ ├── QHighlightRule.hpp │ ├── QJSHighlighter.hpp │ ├── QJSONHighlighter.hpp │ ├── QJavaHighlighter.hpp │ ├── QLanguage.hpp │ ├── QLineNumberArea.hpp │ ├── QLuaCompleter.hpp │ ├── QLuaHighlighter.hpp │ ├── QPythonCompleter.hpp │ ├── QPythonHighlighter.hpp │ ├── QStyleSyntaxHighlighter.hpp │ ├── QSyntaxStyle.hpp │ └── QXMLHighlighter.hpp ├── resources ├── default_style.xml ├── languages │ ├── cpp.xml │ ├── glsl.xml │ ├── java.xml │ ├── js.xml │ ├── lua.xml │ └── python.xml └── qcodeeditor_resources.qrc └── src └── internal ├── QCXXHighlighter.cpp ├── QCodeEditor.cpp ├── QGLSLCompleter.cpp ├── QGLSLHighlighter.cpp ├── QJSHighlighter.cpp ├── QJSONHighlighter.cpp ├── QJavaHighlighter.cpp ├── QLanguage.cpp ├── QLineNumberArea.cpp ├── QLuaCompleter.cpp ├── QLuaHighlighter.cpp ├── QPythonCompleter.cpp ├── QPythonHighlighter.cpp ├── QStyleSyntaxHighlighter.cpp ├── QSyntaxStyle.cpp └── QXMLHighlighter.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Microsoft 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveDeclarations: false 9 | AlignEscapedNewlines: Right 10 | AlignOperands: true 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: false 16 | AllowShortCaseLabelsOnASingleLine: false 17 | AllowShortFunctionsOnASingleLine: None 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortIfStatementsOnASingleLine: Never 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: MultiLine 25 | BinPackArguments: true 26 | BinPackParameters: true 27 | BraceWrapping: 28 | AfterCaseLabel: false 29 | AfterClass: true 30 | AfterControlStatement: true 31 | AfterEnum: true 32 | AfterFunction: true 33 | AfterNamespace: true 34 | AfterObjCDeclaration: true 35 | AfterStruct: true 36 | AfterUnion: false 37 | AfterExternBlock: true 38 | BeforeCatch: true 39 | BeforeElse: true 40 | IndentBraces: false 41 | SplitEmptyFunction: true 42 | SplitEmptyRecord: true 43 | SplitEmptyNamespace: true 44 | BreakBeforeBinaryOperators: None 45 | BreakBeforeBraces: Custom 46 | BreakBeforeInheritanceComma: false 47 | BreakInheritanceList: BeforeColon 48 | BreakBeforeTernaryOperators: true 49 | BreakConstructorInitializersBeforeComma: false 50 | BreakConstructorInitializers: BeforeColon 51 | BreakAfterJavaFieldAnnotations: false 52 | BreakStringLiterals: true 53 | ColumnLimit: 120 54 | CommentPragmas: '^ IWYU pragma:' 55 | CompactNamespaces: false 56 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 57 | ConstructorInitializerIndentWidth: 4 58 | ContinuationIndentWidth: 4 59 | Cpp11BracedListStyle: true 60 | DerivePointerAlignment: false 61 | DisableFormat: false 62 | ExperimentalAutoDetectBinPacking: false 63 | FixNamespaceComments: true 64 | ForEachMacros: 65 | - foreach 66 | - Q_FOREACH 67 | - BOOST_FOREACH 68 | IncludeBlocks: Preserve 69 | IncludeCategories: 70 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 71 | Priority: 2 72 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 73 | Priority: 3 74 | - Regex: '.*' 75 | Priority: 1 76 | IncludeIsMainRegex: '(Test)?$' 77 | IndentCaseLabels: false 78 | IndentPPDirectives: None 79 | IndentWidth: 4 80 | IndentWrappedFunctionNames: false 81 | JavaScriptQuotes: Leave 82 | JavaScriptWrapImports: true 83 | KeepEmptyLinesAtTheStartOfBlocks: true 84 | MacroBlockBegin: '' 85 | MacroBlockEnd: '' 86 | MaxEmptyLinesToKeep: 1 87 | NamespaceIndentation: None 88 | ObjCBinPackProtocolList: Auto 89 | ObjCBlockIndentWidth: 2 90 | ObjCSpaceAfterProperty: false 91 | ObjCSpaceBeforeProtocolList: true 92 | PenaltyBreakAssignment: 2 93 | PenaltyBreakBeforeFirstCallParameter: 19 94 | PenaltyBreakComment: 300 95 | PenaltyBreakFirstLessLess: 120 96 | PenaltyBreakString: 1000 97 | PenaltyBreakTemplateDeclaration: 10 98 | PenaltyExcessCharacter: 1000000 99 | PenaltyReturnTypeOnItsOwnLine: 1000 100 | PointerAlignment: Right 101 | ReflowComments: true 102 | SortIncludes: true 103 | SortUsingDeclarations: true 104 | SpaceAfterCStyleCast: false 105 | SpaceAfterLogicalNot: false 106 | SpaceAfterTemplateKeyword: true 107 | SpaceBeforeAssignmentOperators: true 108 | SpaceBeforeCpp11BracedList: false 109 | SpaceBeforeCtorInitializerColon: true 110 | SpaceBeforeInheritanceColon: true 111 | SpaceBeforeParens: ControlStatements 112 | SpaceBeforeRangeBasedForLoopColon: true 113 | SpaceInEmptyParentheses: false 114 | SpacesBeforeTrailingComments: 1 115 | SpacesInAngles: false 116 | SpacesInContainerLiterals: true 117 | SpacesInCStyleCastParentheses: false 118 | SpacesInParentheses: false 119 | SpacesInSquareBrackets: false 120 | Standard: Cpp11 121 | StatementMacros: 122 | - Q_UNUSED 123 | - QT_REQUIRE_VERSION 124 | TabWidth: 4 125 | UseTab: Never 126 | ... 127 | 128 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig file (https://editorconfig.org) for the QCodeEditor project 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | # QCodeEditor C++ files 11 | [**.cpp] 12 | indent_style = space 13 | indent_size = 4 14 | [**.hpp] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | # QCodeEditor XML files 19 | [**.xml] 20 | indent_style = space 21 | indent_size = 4 22 | [**.qrc] 23 | indent_style = space 24 | indent_size = 4 25 | 26 | # QCodeEditor CMake files 27 | [CMakeLists.txt] 28 | indent_style = space 29 | indent_size = 4 30 | -------------------------------------------------------------------------------- /.github/workflows/build_test.yml: -------------------------------------------------------------------------------- 1 | name: "CI: Build Test" 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '*.md' 7 | - '*/*.md' 8 | - '*/*/*.md' 9 | pull_request: 10 | paths-ignore: 11 | - '*.md' 12 | - '*/*.md' 13 | - '*/*/*.md' 14 | 15 | env: 16 | CMAKE_VERSION: 3.17.1 17 | NINJA_VERSION: 1.10.0 18 | BUILD_TYPE: Release 19 | CCACHE_VERSION: 3.7.7 20 | 21 | jobs: 22 | build: 23 | name: "${{ matrix.config.os }} Qt ${{ matrix.qt_version }}" 24 | runs-on: ${{ matrix.config.os }} 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | qt_version: [5.12.8, 5.14.2] 29 | config: 30 | - { 31 | os: windows-latest, 32 | cc: "cl", cxx: "cl", 33 | binary: 'cpeditor.exe', 34 | environment_script: "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build/vcvars64.bat" 35 | } 36 | - { 37 | os: ubuntu-latest, 38 | cc: "gcc", cxx: "g++", 39 | binary: 'cpeditor' 40 | } 41 | - { 42 | os: macos-latest, 43 | cc: "clang", cxx: "clang++", 44 | binary: 'cpeditor.app/Contents/MacOS/cpeditor' 45 | } 46 | 47 | steps: 48 | - uses: actions/checkout@v2 49 | 50 | - name: Install Python 3.7 51 | uses: actions/setup-python@v1 52 | with: 53 | python-version: 3.7 54 | 55 | - name: Restore Qt from cache 56 | id: cache-qt 57 | uses: actions/cache@v1 58 | with: 59 | path: ../Qt 60 | key: Qt-${{ matrix.config.os }}-${{ matrix.qt_version }} 61 | 62 | - name: Set up Qt environment 63 | uses: jurplel/install-qt-action@v2 64 | with: 65 | cached: ${{ steps.cache-qt.outputs.cache-hit }} 66 | version: ${{ matrix.qt_version }} 67 | 68 | - name: Download Ninja and CMake 69 | id: cmake_and_ninja 70 | shell: cmake -P {0} 71 | run: | 72 | set(cmake_version $ENV{CMAKE_VERSION}) 73 | set(ninja_version $ENV{NINJA_VERSION}) 74 | message(STATUS "Using host CMake version: ${CMAKE_VERSION}") 75 | if ("${{ runner.os }}" STREQUAL "Windows") 76 | set(ninja_suffix "win.zip") 77 | set(cmake_suffix "win64-x64.zip") 78 | set(cmake_dir "cmake-${cmake_version}-win64-x64/bin") 79 | elseif ("${{ runner.os }}" STREQUAL "Linux") 80 | set(ninja_suffix "linux.zip") 81 | set(cmake_suffix "Linux-x86_64.tar.gz") 82 | set(cmake_dir "cmake-${cmake_version}-Linux-x86_64/bin") 83 | elseif ("${{ runner.os }}" STREQUAL "macOS") 84 | set(ninja_suffix "mac.zip") 85 | set(cmake_suffix "Darwin-x86_64.tar.gz") 86 | set(cmake_dir "cmake-${cmake_version}-Darwin-x86_64/CMake.app/Contents/bin") 87 | endif() 88 | set(ninja_url "https://github.com/ninja-build/ninja/releases/download/v${ninja_version}/ninja-${ninja_suffix}") 89 | file(DOWNLOAD "${ninja_url}" ./ninja.zip SHOW_PROGRESS) 90 | execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./ninja.zip) 91 | set(cmake_url "https://github.com/Kitware/CMake/releases/download/v${cmake_version}/cmake-${cmake_version}-${cmake_suffix}") 92 | file(DOWNLOAD "${cmake_url}" ./cmake.zip SHOW_PROGRESS) 93 | execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./cmake.zip) 94 | # Save the path for other steps 95 | file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/${cmake_dir}" cmake_dir) 96 | message("::set-output name=cmake_dir::${cmake_dir}") 97 | if (NOT "${{ runner.os }}" STREQUAL "Windows") 98 | execute_process( 99 | COMMAND chmod +x ninja 100 | COMMAND chmod +x ${cmake_dir}/cmake 101 | ) 102 | endif() 103 | 104 | - name: Download ccache 105 | id: ccache 106 | shell: cmake -P {0} 107 | run: | 108 | set(ccache_url "https://github.com/cristianadam/ccache/releases/download/v$ENV{CCACHE_VERSION}/${{ runner.os }}.tar.xz") 109 | file(DOWNLOAD "${ccache_url}" ./ccache.tar.xz SHOW_PROGRESS) 110 | execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ./ccache.tar.xz) 111 | 112 | - name: Prepare ccache timestamp 113 | id: ccache_cache_timestamp 114 | shell: cmake -P {0} 115 | run: | 116 | string(TIMESTAMP current_date "%Y-%m-%d-%H;%M;%S" UTC) 117 | message("::set-output name=timestamp::${current_date}") 118 | 119 | - name: Restore ccache cache files 120 | uses: actions/cache@v1 121 | with: 122 | path: .ccache 123 | key: ${{ matrix.config.os }}-${{ matrix.qt_version }}-ccache-${{ steps.ccache_cache_timestamp.outputs.timestamp }} 124 | restore-keys: | 125 | ${{ matrix.config.os }}-${{ matrix.qt_version }}-ccache- 126 | 127 | - name: Configure CMake 128 | shell: cmake -P {0} 129 | run: | 130 | set(ENV{CC} ${{ matrix.config.cc }}) 131 | set(ENV{CXX} ${{ matrix.config.cxx }}) 132 | if ("${{ runner.os }}" STREQUAL "Windows" AND NOT "x${{ matrix.config.environment_script }}" STREQUAL "x") 133 | execute_process( 134 | COMMAND "${{ matrix.config.environment_script }}" && set 135 | OUTPUT_FILE environment_script_output.txt 136 | ) 137 | file(STRINGS environment_script_output.txt output_lines) 138 | foreach(line IN LISTS output_lines) 139 | if (line MATCHES "^([a-zA-Z0-9_-]+)=(.*)$") 140 | set(ENV{${CMAKE_MATCH_1}} "${CMAKE_MATCH_2}") 141 | endif() 142 | endforeach() 143 | endif() 144 | set(path_separator ":") 145 | if ("${{ runner.os }}" STREQUAL "Windows") 146 | set(path_separator ";") 147 | endif() 148 | set(ENV{PATH} "$ENV{GITHUB_WORKSPACE}${path_separator}$ENV{PATH}") 149 | execute_process( 150 | COMMAND ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake 151 | -S . 152 | -B build 153 | -D BUILD_EXAMPLE=On 154 | -D CMAKE_BUILD_TYPE=$ENV{BUILD_TYPE} 155 | -G Ninja 156 | -D CMAKE_MAKE_PROGRAM=ninja 157 | -D CMAKE_C_COMPILER_LAUNCHER=ccache 158 | -D CMAKE_CXX_COMPILER_LAUNCHER=ccache 159 | RESULT_VARIABLE result 160 | ) 161 | if (NOT result EQUAL 0) 162 | message(FATAL_ERROR "Bad exit status") 163 | endif() 164 | 165 | - name: Build 166 | shell: cmake -P {0} 167 | run: | 168 | set(ENV{NINJA_STATUS} "[%f/%t %o/sec] ") 169 | if ("${{ runner.os }}" STREQUAL "Windows" AND NOT "x${{ matrix.config.environment_script }}" STREQUAL "x") 170 | file(STRINGS environment_script_output.txt output_lines) 171 | foreach(line IN LISTS output_lines) 172 | if (line MATCHES "^([a-zA-Z0-9_-]+)=(.*)$") 173 | set(ENV{${CMAKE_MATCH_1}} "${CMAKE_MATCH_2}") 174 | endif() 175 | endforeach() 176 | endif() 177 | set(path_separator ":") 178 | if ("${{ runner.os }}" STREQUAL "Windows") 179 | set(path_separator ";") 180 | endif() 181 | set(ENV{PATH} "$ENV{GITHUB_WORKSPACE}${path_separator}$ENV{PATH}") 182 | file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}" ccache_basedir) 183 | set(ENV{CCACHE_BASEDIR} "${ccache_basedir}") 184 | set(ENV{CCACHE_DIR} "${ccache_basedir}/.ccache") 185 | set(ENV{CCACHE_COMPRESS} "true") 186 | set(ENV{CCACHE_COMPRESSLEVEL} "6") 187 | set(ENV{CCACHE_MAXSIZE} "100M") 188 | if ("${{ matrix.config.cxx }}" STREQUAL "cl") 189 | set(ENV{CCACHE_MAXSIZE} "150M") 190 | endif() 191 | execute_process(COMMAND ccache -p) 192 | execute_process(COMMAND ccache -z) 193 | execute_process( 194 | COMMAND ${{ steps.cmake_and_ninja.outputs.cmake_dir }}/cmake --build build 195 | RESULT_VARIABLE result 196 | ) 197 | if (NOT result EQUAL 0) 198 | message(FATAL_ERROR "Bad exit status") 199 | endif() 200 | execute_process(COMMAND ccache -s) 201 | -------------------------------------------------------------------------------- /.github/workflows/check_format.yml: -------------------------------------------------------------------------------- 1 | name: "Check Format" 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | format: 7 | name: "Check Clang Format" 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: "Install clang-format-9" 12 | run: | 13 | sudo apt-get update 14 | sudo apt-get install clang-format-9 15 | - name: "Format Codes" 16 | run: clang-format-9 -i src/internal/*.cpp include/internal/*.hpp 17 | - name: Check diff 18 | run: git diff --exit-code HEAD 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | cmake-build-debug/ 3 | cmake-build-release/ 4 | .idea/ 5 | .kdev4/ 6 | QCodeEditor.kdev4 7 | *~ 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | project(QCodeEditor) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | option(BUILD_EXAMPLE "Example building required" Off) 7 | 8 | if (${BUILD_EXAMPLE}) 9 | message(STATUS "QCodeEditor example will be built.") 10 | add_subdirectory(example) 11 | endif() 12 | 13 | set(RESOURCES_FILE 14 | resources/qcodeeditor_resources.qrc 15 | ) 16 | 17 | set(INCLUDE_FILES 18 | include/QHighlightRule 19 | include/QHighlightBlockRule 20 | include/QCodeEditor 21 | include/QCXXHighlighter 22 | include/QLineNumberArea 23 | include/QStyleSyntaxHighlighter 24 | include/QSyntaxStyle 25 | include/QGLSLCompleter 26 | include/QGLSLHighlighter 27 | include/QJavaHighlighter 28 | include/QJSHighlighter 29 | include/QLanguage 30 | include/QXMLHighlighter 31 | include/QJSONHighlighter 32 | include/QLuaCompleter 33 | include/QLuaHighlighter 34 | include/QPythonHighlighter 35 | include/internal/QHighlightRule.hpp 36 | include/internal/QHighlightBlockRule.hpp 37 | include/internal/QCodeEditor.hpp 38 | include/internal/QCXXHighlighter.hpp 39 | include/internal/QJavaHighlighter.hpp 40 | include/internal/QJSHighlighter.hpp 41 | include/internal/QLineNumberArea.hpp 42 | include/internal/QStyleSyntaxHighlighter.hpp 43 | include/internal/QSyntaxStyle.hpp 44 | include/internal/QGLSLCompleter.hpp 45 | include/internal/QGLSLHighlighter.hpp 46 | include/internal/QLanguage.hpp 47 | include/internal/QXMLHighlighter.hpp 48 | include/internal/QJSONHighlighter.hpp 49 | include/internal/QLuaCompleter.hpp 50 | include/internal/QLuaHighlighter.hpp 51 | include/internal/QPythonCompleter.hpp 52 | include/internal/QPythonHighlighter.hpp 53 | ) 54 | 55 | set(SOURCE_FILES 56 | src/internal/QCodeEditor.cpp 57 | src/internal/QLineNumberArea.cpp 58 | src/internal/QCXXHighlighter.cpp 59 | src/internal/QSyntaxStyle.cpp 60 | src/internal/QStyleSyntaxHighlighter.cpp 61 | src/internal/QGLSLCompleter.cpp 62 | src/internal/QGLSLHighlighter.cpp 63 | src/internal/QJavaHighlighter.cpp 64 | src/internal/QJSHighlighter.cpp 65 | src/internal/QLanguage.cpp 66 | src/internal/QXMLHighlighter.cpp 67 | src/internal/QJSONHighlighter.cpp 68 | src/internal/QLuaCompleter.cpp 69 | src/internal/QLuaHighlighter.cpp 70 | src/internal/QPythonCompleter.cpp 71 | src/internal/QPythonHighlighter.cpp 72 | ) 73 | 74 | # Create code for QObjects 75 | set(CMAKE_AUTOMOC On) 76 | 77 | # Create code from resource files 78 | set(CMAKE_AUTORCC ON) 79 | 80 | # Generate compile_commands.json in build/ for analyzers like clang-tidy. 81 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 82 | 83 | # Find includes in corresponding build directories 84 | find_package(Qt5Core CONFIG REQUIRED) 85 | find_package(Qt5Widgets CONFIG REQUIRED) 86 | find_package(Qt5Gui CONFIG REQUIRED) 87 | 88 | add_library(QCodeEditor STATIC 89 | ${RESOURCES_FILE} 90 | ${SOURCE_FILES} 91 | ${INCLUDE_FILES} 92 | ) 93 | 94 | target_include_directories(QCodeEditor PUBLIC 95 | include 96 | ) 97 | 98 | if(CMAKE_COMPILER_IS_GNUCXX) 99 | target_compile_options(QCodeEditor 100 | PRIVATE 101 | -pedantic 102 | -Wall 103 | -Wextra 104 | -Weffc++ 105 | -Woverloaded-virtual 106 | -Winit-self 107 | -Wunreachable-code 108 | ) 109 | endif(CMAKE_COMPILER_IS_GNUCXX) 110 | 111 | target_link_libraries(QCodeEditor 112 | Qt5::Core 113 | Qt5::Widgets 114 | Qt5::Gui 115 | ) 116 | -------------------------------------------------------------------------------- /LICENSE.MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-2019 Megaxela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qt Code Editor Widget 2 | 3 | [](https://github.com/cpeditor/QCodeEditor/actions?query=event%3Apush) 4 | 5 | It's a widget for editing/viewing code. 6 | 7 | This project uses a resource named `qcodeeditor_resources.qrc`. The main application 8 | must not use a resource file with the same name. 9 | 10 | (It's not a project from a Qt example.) 11 | 12 | ## Requirements 13 | 0. C++11 featured compiler. 14 | 0. Qt 5. 15 | 16 | ## Abilities 17 | 1. Auto parentheses. 18 | 1. Different highlight rules. 19 | 1. Auto indentation. 20 | 1. Replace tabs with spaces. 21 | 1. GLSL completion rules. 22 | 1. GLSL highlight rules. 23 | 1. C++ highlight rules. 24 | 1. XML highlight rules. 25 | 1. JSON highligh rules. 26 | 1. Java highligh rules. 27 | 1. JavaScript highligh rules. 28 | 1. Frame selection. 29 | 1. Qt Creator styles. 30 | 31 | ## Build 32 | It's a CMake-based library, so it can be used as a submodule (see the example). 33 | But here are the steps to build it as a static library (for external use for example). 34 | 35 | 1. Clone the repository: `git clone https://github.com/Megaxela/QCodeEditor` 36 | 1. Go into the repository: `cd QCodeEditor` 37 | 1. Create a build folder: `mkdir build` 38 | 1. Go into the build folder: `cd build` 39 | 1. Generate a build file for your compiler: `cmake ..` 40 | 1. If you need to build the example, specify `-DBUILD_EXAMPLE=On` on this step. 41 | 1. Build the library: `cmake --build .` 42 | 43 | ## Example 44 | 45 | By default, `QCodeEditor` uses the standard QtCreator theme. But you may specify 46 | your own by parsing it with `QSyntaxStyle`. The example uses [Dracula](https://draculatheme.com) theme. 47 | (See the example for more.) 48 | 49 | 50 | 51 | ## LICENSE 52 | 53 | 54 | 55 | Library is licensed under the [MIT License](https://opensource.org/licenses/MIT) 56 | 57 | Permission is hereby granted, free of charge, to any person obtaining a copy 58 | of this software and associated documentation files (the "Software"), to deal 59 | in the Software without restriction, including without limitation the rights 60 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 61 | copies of the Software, and to permit persons to whom the Software is 62 | furnished to do so, subject to the following conditions: 63 | 64 | The above copyright notice and this permission notice shall be included in all 65 | copies or substantial portions of the Software. 66 | 67 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 68 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 69 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 70 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 71 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 72 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 73 | SOFTWARE. 74 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | project(QCodeEditorExample) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | set(CMAKE_AUTOMOC On) 7 | set(CMAKE_AUTORCC ON) 8 | 9 | find_package(Qt5Core CONFIG REQUIRED) 10 | find_package(Qt5Widgets CONFIG REQUIRED) 11 | find_package(Qt5Gui CONFIG REQUIRED) 12 | 13 | add_executable(QCodeEditorExample 14 | resources/demo_resources.qrc 15 | src/main.cpp 16 | src/MainWindow.cpp 17 | include/MainWindow.hpp 18 | ) 19 | 20 | target_include_directories(QCodeEditorExample PUBLIC 21 | include 22 | ) 23 | 24 | target_link_libraries(QCodeEditorExample 25 | Qt5::Core 26 | Qt5::Widgets 27 | Qt5::Gui 28 | QCodeEditor 29 | ) -------------------------------------------------------------------------------- /example/image/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpeditor/QCodeEditor/ed1196a91dd6415c5ad6d0e85a90630e9b3b9f6c/example/image/preview.png -------------------------------------------------------------------------------- /example/include/MainWindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Qt 4 | #include // Required for inheritance 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class QVBoxLayout; 13 | class QSyntaxStyle; 14 | class QComboBox; 15 | class QCheckBox; 16 | class QSpinBox; 17 | class QCompleter; 18 | class QStyleSyntaxHighlighter; 19 | class QCodeEditor; 20 | 21 | /** 22 | * @brief Class, that describes demo main window. 23 | */ 24 | class MainWindow : public QMainWindow 25 | { 26 | Q_OBJECT 27 | 28 | public: 29 | 30 | /** 31 | * @brief Constructor. 32 | * @param parent Pointer to parent widget. 33 | */ 34 | explicit MainWindow(QWidget* parent=nullptr); 35 | 36 | private: 37 | 38 | void loadStyle(QString path); 39 | 40 | QString loadCode(QString path); 41 | 42 | void initData(); 43 | 44 | void createWidgets(); 45 | 46 | void setupWidgets(); 47 | 48 | void performConnections(); 49 | 50 | QVBoxLayout* m_setupLayout; 51 | 52 | QComboBox* m_codeSampleCombobox; 53 | QComboBox* m_highlighterCombobox; 54 | QComboBox* m_completerCombobox; 55 | QComboBox* m_styleCombobox; 56 | 57 | QCheckBox* m_readOnlyCheckBox; 58 | QCheckBox* m_wordWrapCheckBox; 59 | QCheckBox* m_tabReplaceEnabledCheckbox; 60 | QSpinBox* m_tabReplaceNumberSpinbox; 61 | QCheckBox* m_autoIndentationCheckbox; 62 | 63 | QMenu * m_mainMenu; 64 | QAction * m_actionToggleComment; 65 | QAction * m_actionToggleBlockComment; 66 | 67 | QCodeEditor* m_codeEditor; 68 | 69 | QVector> m_codeSamples; 70 | QVector> m_completers; 71 | QVector> m_highlighters; 72 | QVector> m_styles; 73 | }; 74 | 75 | -------------------------------------------------------------------------------- /example/resources/code_samples/cxx.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | int n, sum = 0; 6 | 7 | std::cout << "Enter a positive integer: "; 8 | std::cin >> n; 9 | 10 | for (int i = 1; i <= n; ++i) 11 | { 12 | sum += i; 13 | } 14 | 15 | std::cout << "Sum = " << sum; 16 | return 0; 17 | } -------------------------------------------------------------------------------- /example/resources/code_samples/java.java: -------------------------------------------------------------------------------- 1 | import java.util.Scanner; 2 | 3 | /* This program is simply for demonstrating 4 | * the highlighting of Java syntax. 5 | */ 6 | 7 | class Animal { } 8 | 9 | class Dog extends Animal { } 10 | 11 | public class Example { 12 | /** Prompts the user for a string and returns what they entered. 13 | * @param message The message to display to the user. 14 | * @return The string that the user entered. 15 | * */ 16 | static String prompt(String message) { 17 | 18 | Scanner scanner = new Scanner(System.in); 19 | 20 | System.out.printf("%s: ", message); 21 | 22 | return scanner.nextLine(); 23 | } 24 | public static void main(String[] args) { 25 | 26 | double pi = 3.1415; 27 | 28 | String name = prompt("Enter your name"); 29 | 30 | System.out.println("Nice to meet you, " + name + "!"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/resources/code_samples/js.js: -------------------------------------------------------------------------------- 1 | // example of a JS code to test JS syntax highlighter 2 | // source: 3 | // https://stackoverflow.com/questions/61301013/why-adapter-function-changing-my-num-array 4 | // https://stackoverflow.com/questions/61301471/how-can-i-filter-undefined-from-an-object 5 | 6 | var num = [] 7 | 8 | // creates a copy of a passed list, pushes 1 to it and returns it 9 | function push(nums) { 10 | var orig = nums.map(x => x) 11 | var newNum = pushed() 12 | num = orig 13 | return newNum 14 | } 15 | 16 | /* 17 | This is a helper function 18 | */ 19 | function pushed() { 20 | num.push(1) 21 | return num 22 | } 23 | 24 | function filterFunnel() { 25 | let filterLists = $("#funnel-filters").children().filter(".filter"); 26 | let query = `SELECT * FROM funnel WHERE `; 27 | let count = 0; 28 | for(i = 0; i < Object.keys(filterLists).length; i++) { 29 | let dom = filterLists[i]; 30 | if(dom === undefined){ 31 | return query; 32 | } else { 33 | if(dom.value === "Select" || dom.value === "") { 34 | 35 | } else { 36 | if(count >= 1) { 37 | query += ` AND ${dom.id} = ${dom.value}`; 38 | }else{ 39 | query += `${dom.id} = ${dom.value} ` 40 | } 41 | count += 1; 42 | } 43 | } 44 | } 45 | } 46 | 47 | console.log("new", push(num)) 48 | console.log("old", num) -------------------------------------------------------------------------------- /example/resources/code_samples/json.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": { 3 | "some array": [1, 2, 3], 4 | "some string": "Hello world", 5 | "some dbl": 2.121323, 6 | "some bool": true, 7 | "some null": null 8 | } 9 | } -------------------------------------------------------------------------------- /example/resources/code_samples/lua.lua: -------------------------------------------------------------------------------- 1 | -- Two dashes start a one-line comment. 2 | --[[ 3 | Adding two ['s and ]'s makes it a 4 | multi-line comment. 5 | --]] 6 | 7 | ---------------------------------------------------- 8 | -- 1. Variables and flow control. 9 | ---------------------------------------------------- 10 | 11 | num = 42 -- All numbers are doubles. 12 | -- Don't freak out, 64-bit doubles have 52 bits for 13 | -- storing exact int values; machine precision is 14 | -- not a problem for ints that need < 52 bits. 15 | 16 | s = 'walternate' -- Immutable strings like Python. 17 | t = "double-quotes are also fine" 18 | u = [[ Double brackets 19 | start and end 20 | multi-line strings.]] 21 | t = nil -- Undefines t; Lua has garbage collection. 22 | 23 | -- Blocks are denoted with keywords like do/end: 24 | while num < 50 do 25 | num = num + 1 -- No ++ or += type operators. 26 | end 27 | 28 | -- If clauses: 29 | if num > 40 then 30 | print('over 40') 31 | elseif s ~= 'walternate' then -- ~= is not equals. 32 | -- Equality check is == like Python; ok for strs. 33 | io.write('not over 40\n') -- Defaults to stdout. 34 | else 35 | -- Variables are global by default. 36 | thisIsGlobal = 5 -- Camel case is common. 37 | 38 | -- How to make a variable local: 39 | local line = io.read() -- Reads next stdin line. 40 | 41 | -- String concatenation uses the .. operator: 42 | print('Winter is coming, ' .. line) 43 | end 44 | 45 | -- Undefined variables return nil. 46 | -- This is not an error: 47 | foo = anUnknownVariable -- Now foo = nil. 48 | 49 | aBoolValue = false 50 | 51 | -- Only nil and false are falsy; 0 and '' are true! 52 | if not aBoolValue then print('twas false') end 53 | 54 | -- 'or' and 'and' are short-circuited. 55 | -- This is similar to the a?b:c operator in C/js: 56 | ans = aBoolValue and 'yes' or 'no' --> 'no' 57 | 58 | karlSum = 0 59 | for i = 1, 100 do -- The range includes both ends. 60 | karlSum = karlSum + i 61 | end 62 | 63 | -- Use "100, 1, -1" as the range to count down: 64 | fredSum = 0 65 | for j = 100, 1, -1 do fredSum = fredSum + j end 66 | 67 | -- In general, the range is begin, end[, step]. 68 | 69 | -- Another loop construct: 70 | repeat 71 | print('the way of the future') 72 | num = num - 1 73 | until num == 0 74 | 75 | 76 | ---------------------------------------------------- 77 | -- 2. Functions. 78 | ---------------------------------------------------- 79 | 80 | function fib(n) 81 | if n < 2 then return 1 end 82 | return fib(n - 2) + fib(n - 1) 83 | end 84 | 85 | -- Closures and anonymous functions are ok: 86 | function adder(x) 87 | -- The returned function is created when adder is 88 | -- called, and remembers the value of x: 89 | return function (y) return x + y end 90 | end 91 | a1 = adder(9) 92 | a2 = adder(36) 93 | print(a1(16)) --> 25 94 | print(a2(64)) --> 100 95 | 96 | -- Returns, func calls, and assignments all work 97 | -- with lists that may be mismatched in length. 98 | -- Unmatched receivers are nil; 99 | -- unmatched senders are discarded. 100 | 101 | x, y, z = 1, 2, 3, 4 102 | -- Now x = 1, y = 2, z = 3, and 4 is thrown away. 103 | 104 | function bar(a, b, c) 105 | print(a, b, c) 106 | return 4, 8, 15, 16, 23, 42 107 | end 108 | 109 | x, y = bar('zaphod') --> print zaphod nil nil 110 | -- Now x = 4, y = 8, values 15..42 are discarded. 111 | 112 | -- Functions are first-class, may be local/global. 113 | -- These are the same: 114 | function f(x) return x * x end 115 | f = function (x) return x * x end 116 | 117 | -- And so are these: 118 | local function g(x) return math.sin(x) end 119 | local g; g = function (x) return math.sin(x) end 120 | -- the 'local g' decl makes g-self-references ok. 121 | 122 | -- Trig funcs work in radians, by the way. 123 | 124 | -- Calls with one string param don't need parens: 125 | print 'hello' -- Works fine. 126 | 127 | 128 | ---------------------------------------------------- 129 | -- 3. Tables. 130 | ---------------------------------------------------- 131 | 132 | -- Tables = Lua's only compound data structure; 133 | -- they are associative arrays. 134 | -- Similar to php arrays or js objects, they are 135 | -- hash-lookup dicts that can also be used as lists. 136 | 137 | -- Using tables as dictionaries / maps: 138 | 139 | -- Dict literals have string keys by default: 140 | t = {key1 = 'value1', key2 = false} 141 | 142 | -- String keys can use js-like dot notation: 143 | print(t.key1) -- Prints 'value1'. 144 | t.newKey = {} -- Adds a new key/value pair. 145 | t.key2 = nil -- Removes key2 from the table. 146 | 147 | -- Literal notation for any (non-nil) value as key: 148 | u = {['@!#'] = 'qbert', [{}] = 1729, [6.28] = 'tau'} 149 | print(u[6.28]) -- prints tau 150 | 151 | -- Key matching is basically by value for numbers 152 | -- and strings, but by identity for tables. 153 | a = u['@!#'] -- Now a = 'qbert'. 154 | b = u[{}] -- We might expect 1729, but it's nil: 155 | -- b = nil since the lookup fails. It fails 156 | -- because the key we used is not the same object 157 | -- as the one used to store the original value. So 158 | -- strings & numbers are more portable keys. 159 | 160 | -- A one-table-param function call needs no parens: 161 | function h(x) print(x.key1) end 162 | h{key1 = 'Sonmi~451'} -- Prints 'Sonmi~451'. 163 | 164 | for key, val in pairs(u) do -- Table iteration. 165 | print(key, val) 166 | end 167 | 168 | -- _G is a special table of all globals. 169 | print(_G['_G'] == _G) -- Prints 'true'. 170 | 171 | -- Using tables as lists / arrays: 172 | 173 | -- List literals implicitly set up int keys: 174 | v = {'value1', 'value2', 1.21, 'gigawatts'} 175 | for i = 1, #v do -- #v is the size of v for lists. 176 | print(v[i]) -- Indices start at 1 !! SO CRAZY! 177 | end 178 | -- A 'list' is not a real type. v is just a table 179 | -- with consecutive integer keys, treated as a list. 180 | 181 | ---------------------------------------------------- 182 | -- 3.1 Metatables and metamethods. 183 | ---------------------------------------------------- 184 | 185 | -- A table can have a metatable that gives the table 186 | -- operator-overloadish behavior. Later we'll see 187 | -- how metatables support js-prototypey behavior. 188 | 189 | f1 = {a = 1, b = 2} -- Represents the fraction a/b. 190 | f2 = {a = 2, b = 3} 191 | 192 | -- This would fail: 193 | -- s = f1 + f2 194 | 195 | metafraction = {} 196 | function metafraction.__add(f1, f2) 197 | sum = {} 198 | sum.b = f1.b * f2.b 199 | sum.a = f1.a * f2.b + f2.a * f1.b 200 | return sum 201 | end 202 | 203 | setmetatable(f1, metafraction) 204 | setmetatable(f2, metafraction) 205 | 206 | s = f1 + f2 -- call __add(f1, f2) on f1's metatable 207 | 208 | -- f1, f2 have no key for their metatable, unlike 209 | -- prototypes in js, so you must retrieve it as in 210 | -- getmetatable(f1). The metatable is a normal table 211 | -- with keys that Lua knows about, like __add. 212 | 213 | -- But the next line fails since s has no metatable: 214 | -- t = s + s 215 | -- Class-like patterns given below would fix this. 216 | 217 | -- An __index on a metatable overloads dot lookups: 218 | defaultFavs = {animal = 'gru', food = 'donuts'} 219 | myFavs = {food = 'pizza'} 220 | setmetatable(myFavs, {__index = defaultFavs}) 221 | eatenBy = myFavs.animal -- works! thanks, metatable 222 | 223 | -- Direct table lookups that fail will retry using 224 | -- the metatable's __index value, and this recurses. 225 | 226 | -- An __index value can also be a function(tbl, key) 227 | -- for more customized lookups. 228 | 229 | -- Values of __index,add, .. are called metamethods. 230 | -- Full list. Here a is a table with the metamethod. 231 | 232 | -- __add(a, b) for a + b 233 | -- __sub(a, b) for a - b 234 | -- __mul(a, b) for a * b 235 | -- __div(a, b) for a / b 236 | -- __mod(a, b) for a % b 237 | -- __pow(a, b) for a ^ b 238 | -- __unm(a) for -a 239 | -- __concat(a, b) for a .. b 240 | -- __len(a) for #a 241 | -- __eq(a, b) for a == b 242 | -- __lt(a, b) for a < b 243 | -- __le(a, b) for a <= b 244 | -- __index(a, b) for a.b 245 | -- __newindex(a, b, c) for a.b = c 246 | -- __call(a, ...) for a(...) 247 | 248 | ---------------------------------------------------- 249 | -- 3.2 Class-like tables and inheritance. 250 | ---------------------------------------------------- 251 | 252 | -- Classes aren't built in; there are different ways 253 | -- to make them using tables and metatables. 254 | 255 | -- Explanation for this example is below it. 256 | 257 | Dog = {} -- 1. 258 | 259 | function Dog:new() -- 2. 260 | newObj = {sound = 'woof'} -- 3. 261 | self.__index = self -- 4. 262 | return setmetatable(newObj, self) -- 5. 263 | end 264 | 265 | function Dog:makeSound() -- 6. 266 | print('I say ' .. self.sound) 267 | end 268 | 269 | mrDog = Dog:new() -- 7. 270 | mrDog:makeSound() -- 'I say woof' -- 8. 271 | 272 | -- 1. Dog acts like a class; it's really a table. 273 | -- 2. function tablename:fn(...) is the same as 274 | -- function tablename.fn(self, ...) 275 | -- The : just adds a first arg called self. 276 | -- Read 7 & 8 below for how self gets its value. 277 | -- 3. newObj will be an instance of class Dog. 278 | -- 4. self = the class being instantiated. Often 279 | -- self = Dog, but inheritance can change it. 280 | -- newObj gets self's functions when we set both 281 | -- newObj's metatable and self's __index to self. 282 | -- 5. Reminder: setmetatable returns its first arg. 283 | -- 6. The : works as in 2, but this time we expect 284 | -- self to be an instance instead of a class. 285 | -- 7. Same as Dog.new(Dog), so self = Dog in new(). 286 | -- 8. Same as mrDog.makeSound(mrDog); self = mrDog. 287 | 288 | ---------------------------------------------------- 289 | 290 | -- Inheritance example: 291 | 292 | LoudDog = Dog:new() -- 1. 293 | 294 | function LoudDog:makeSound() 295 | s = self.sound .. ' ' -- 2. 296 | print(s .. s .. s) 297 | end 298 | 299 | seymour = LoudDog:new() -- 3. 300 | seymour:makeSound() -- 'woof woof woof' -- 4. 301 | 302 | -- 1. LoudDog gets Dog's methods and variables. 303 | -- 2. self has a 'sound' key from new(), see 3. 304 | -- 3. Same as LoudDog.new(LoudDog), and converted to 305 | -- Dog.new(LoudDog) as LoudDog has no 'new' key, 306 | -- but does have __index = Dog on its metatable. 307 | -- Result: seymour's metatable is LoudDog, and 308 | -- LoudDog.__index = LoudDog. So seymour.key will 309 | -- = seymour.key, LoudDog.key, Dog.key, whichever 310 | -- table is the first with the given key. 311 | -- 4. The 'makeSound' key is found in LoudDog; this 312 | -- is the same as LoudDog.makeSound(seymour). 313 | 314 | -- If needed, a subclass's new() is like the base's: 315 | function LoudDog:new() 316 | newObj = {} 317 | -- set up newObj 318 | self.__index = self 319 | return setmetatable(newObj, self) 320 | end 321 | 322 | ---------------------------------------------------- 323 | -- 4. Modules. 324 | ---------------------------------------------------- 325 | 326 | 327 | --[[ I'm commenting out this section so the rest of 328 | -- this script remains runnable. 329 | -- Suppose the file mod.lua looks like this: 330 | local M = {} 331 | 332 | local function sayMyName() 333 | print('Hrunkner') 334 | end 335 | 336 | function M.sayHello() 337 | print('Why hello there') 338 | sayMyName() 339 | end 340 | 341 | return M 342 | 343 | -- Another file can use mod.lua's functionality: 344 | local mod = require('mod') -- Run the file mod.lua. 345 | 346 | -- require is the standard way to include modules. 347 | -- require acts like: (if not cached; see below) 348 | local mod = (function () 349 | 350 | end)() 351 | -- It's like mod.lua is a function body, so that 352 | -- locals inside mod.lua are invisible outside it. 353 | 354 | -- This works because mod here = M in mod.lua: 355 | mod.sayHello() -- Says hello to Hrunkner. 356 | 357 | -- This is wrong; sayMyName only exists in mod.lua: 358 | mod.sayMyName() -- error 359 | 360 | -- require's return values are cached so a file is 361 | -- run at most once, even when require'd many times. 362 | 363 | -- Suppose mod2.lua contains print('Hi!'). 364 | local a = require('mod2') -- Prints Hi! 365 | local b = require('mod2') -- Doesn't print; a=b. 366 | 367 | -- dofile is like require without caching: 368 | dofile('mod2.lua') --> Hi! 369 | dofile('mod2.lua') --> Hi! (runs it again) 370 | 371 | -- loadfile loads a lua file but doesn't run it yet. 372 | f = loadfile('mod2.lua') -- Call f() to run it. 373 | 374 | -- loadstring is loadfile for strings. 375 | g = loadstring('print(343)') -- Returns a function. 376 | g() -- Prints out 343; nothing printed before now. 377 | 378 | --]] 379 | 380 | ---------------------------------------------------- 381 | -- 5. References. 382 | ---------------------------------------------------- 383 | 384 | --[[ 385 | 386 | I was excited to learn Lua so I could make games 387 | with the Löve 2D game engine. That's the why. 388 | 389 | I started with BlackBulletIV's Lua for programmers. 390 | Next I read the official Programming in Lua book. 391 | That's the how. 392 | 393 | It might be helpful to check out the Lua short 394 | reference on lua-users.org. 395 | 396 | The main topics not covered are standard libraries: 397 | * string library 398 | * table library 399 | * math library 400 | * io library 401 | * os library 402 | 403 | By the way, this entire file is valid Lua; save it 404 | as learn.lua and run it with "lua learn.lua" ! 405 | 406 | This was first written for tylerneylon.com. It's 407 | also available as a github gist. Tutorials for other 408 | languages, in the same style as this one, are here: 409 | 410 | http://learnxinyminutes.com/ 411 | 412 | Have fun with Lua! 413 | 414 | --]] -------------------------------------------------------------------------------- /example/resources/code_samples/python.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | 3 | def sqr_array(x): 4 | ''' 5 | Documentation for sqr_array. 6 | A multi line string!' 7 | ''' 8 | 9 | return np.array(x)**2 10 | 11 | s = 'this is a string' 12 | d = 10 13 | x = np.arange(0,d,d/10) 14 | y = sqr_array(x) 15 | -------------------------------------------------------------------------------- /example/resources/code_samples/shader.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | uniform vec2 resolution; 4 | uniform float time; 5 | 6 | vec3 trans(vec3 p) 7 | { 8 | return mod(p, 8.0)-4.0; 9 | } 10 | 11 | float distanceFunction(vec3 pos) 12 | { 13 | return length(trans(pos)) - 1.5; 14 | } 15 | 16 | vec3 getNormal(vec3 p) 17 | { 18 | const float d = 0.0001; 19 | return 20 | normalize 21 | ( 22 | vec3 23 | ( 24 | distanceFunction(p+vec3(d, 0.0, 0))-distanceFunction(p+vec3(-d,0.0,0.0)), 25 | distanceFunction(p+vec3(0.0, d, 0.0))-distanceFunction(p+vec3(0.0,-d,0.0)), 26 | distanceFunction(p+vec3(0.0, 0.0, d))-distanceFunction(p+vec3(0.0,0.0,-d)) 27 | ) 28 | ); 29 | } 30 | 31 | void main() { 32 | vec2 pos = (gl_FragCoord.xy*2.0 -resolution) / resolution.y; 33 | 34 | vec3 camPos = vec3(0.0, 0.0, 3.0); 35 | vec3 camDir = vec3(0.0, 0.0, -1.0); 36 | vec3 camUp = vec3(0.0, 1.0, 0.0); 37 | vec3 camSide = cross(camDir, camUp); 38 | float focus = sin(time)*1.5+4.0; 39 | 40 | mat3 lense = mat3(1.,0.,0., 41 | 0.,888989898989898989,0., 42 | 0.,0.,1.); 43 | vec3 pos3 = vec3(pos,camDir.z*10.); 44 | camDir = vec3( 45 | camDir.x, 46 | camDir.y, 47 | camDir.z); 48 | camDir*=normalize(dot(camDir,pos3)); 49 | vec3 rayDir = normalize(camSide*pos.x + camUp*pos.y + camDir*focus); 50 | 51 | float t = 0.0, d; 52 | vec3 posOnRay = camPos; 53 | 54 | for(int i=0; i<64; ++i) 55 | { 56 | d = distanceFunction(posOnRay); 57 | t += d; 58 | posOnRay = camPos + t*rayDir; 59 | } 60 | 61 | vec3 normal = getNormal(posOnRay); 62 | if(abs(d) < 0.001) 63 | { 64 | gl_FragColor = vec4(normal, 1.0); 65 | }else 66 | { 67 | gl_FragColor = vec4(0.0); 68 | } 69 | } -------------------------------------------------------------------------------- /example/resources/code_samples/xml.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Joe 7 | Bob 8 | Trenton Literary Review Honorable Mention 9 | 10 | 12 11 | 12 | 13 | 14 | Mary 15 | Bob 16 | Selected Short Stories of 17 | Mary 18 | Bob 19 | 20 | 21 | 22 | Britney 23 | Bob 24 | 25 | 55 26 | 27 | 28 | 2.50 29 | 30 | 31 | 32 | 33 | Toni 34 | Bob 35 | B.A. 36 | Ph.D. 37 | Pulitzer 38 | Still in Trenton 39 | Trenton Forever 40 | 41 | 6.50 42 | 43 | It was a dark and stormy night. 44 | But then all nights in Trenton seem dark and 45 | stormy to someone who has gone through what 46 | I have. 47 | 48 | Trenton 49 | misery 50 | 51 | 52 | 53 | 54 | Who's Who in Trenton 55 | Robert Bob 56 | 57 | -------------------------------------------------------------------------------- /example/resources/demo_resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | styles/drakula.xml 4 | code_samples/cxx.cpp 5 | code_samples/shader.glsl 6 | code_samples/xml.xml 7 | code_samples/java.java 8 | code_samples/js.js 9 | code_samples/json.json 10 | code_samples/lua.lua 11 | code_samples/python.py 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/resources/styles/drakula.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /example/src/MainWindow.cpp: -------------------------------------------------------------------------------- 1 | // Demo 2 | #include 3 | 4 | // QCodeEditor 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | // Qt 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | MainWindow::MainWindow(QWidget* parent) : 29 | QMainWindow(parent), 30 | m_setupLayout(nullptr), 31 | m_codeSampleCombobox(nullptr), 32 | m_highlighterCombobox(nullptr), 33 | m_completerCombobox(nullptr), 34 | m_styleCombobox(nullptr), 35 | m_readOnlyCheckBox(nullptr), 36 | m_wordWrapCheckBox(nullptr), 37 | m_tabReplaceEnabledCheckbox(nullptr), 38 | m_tabReplaceNumberSpinbox(nullptr), 39 | m_autoIndentationCheckbox(nullptr), 40 | m_codeEditor(nullptr), 41 | m_completers(), 42 | m_highlighters(), 43 | m_styles() 44 | { 45 | initData(); 46 | createWidgets(); 47 | setupWidgets(); 48 | performConnections(); 49 | } 50 | 51 | void MainWindow::initData() 52 | { 53 | m_codeSamples = { 54 | {"C++", loadCode(":/code_samples/cxx.cpp")}, 55 | {"GLSL", loadCode(":/code_samples/shader.glsl")}, 56 | {"XML", loadCode(":/code_samples/xml.xml")}, 57 | {"Java", loadCode(":/code_samples/java.java")}, 58 | {"JS", loadCode(":/code_samples/js.js")}, 59 | {"JSON", loadCode(":/code_samples/json.json")}, 60 | {"LUA", loadCode(":/code_samples/lua.lua")}, 61 | {"Python", loadCode(":/code_samples/python.py")} 62 | }; 63 | 64 | m_completers = { 65 | {"None", nullptr}, 66 | {"GLSL", new QGLSLCompleter(this)}, 67 | {"LUA", new QLuaCompleter(this)}, 68 | {"Python", new QPythonCompleter(this)}, 69 | }; 70 | 71 | m_highlighters = { 72 | {"None", nullptr}, 73 | {"C++", new QCXXHighlighter}, 74 | {"GLSL", new QGLSLHighlighter}, 75 | {"XML", new QXMLHighlighter}, 76 | {"Java", new QJavaHighlighter }, 77 | {"JS", new QJSHighlighter}, 78 | {"JSON", new QJSONHighlighter}, 79 | {"LUA", new QLuaHighlighter}, 80 | {"Python", new QPythonHighlighter}, 81 | }; 82 | 83 | m_styles = { 84 | {"Default", QSyntaxStyle::defaultStyle()} 85 | }; 86 | 87 | // Loading styles 88 | loadStyle(":/styles/drakula.xml"); 89 | } 90 | 91 | QString MainWindow::loadCode(QString path) 92 | { 93 | QFile fl(path); 94 | 95 | if (!fl.open(QIODevice::ReadOnly)) 96 | { 97 | return QString(); 98 | } 99 | 100 | return fl.readAll(); 101 | } 102 | 103 | void MainWindow::loadStyle(QString path) 104 | { 105 | QFile fl(path); 106 | 107 | if (!fl.open(QIODevice::ReadOnly)) 108 | { 109 | return; 110 | } 111 | 112 | auto style = new QSyntaxStyle(this); 113 | 114 | if (!style->load(fl.readAll())) 115 | { 116 | delete style; 117 | return; 118 | } 119 | 120 | m_styles.append({style->name(), style}); 121 | } 122 | 123 | void MainWindow::createWidgets() 124 | { 125 | // Layout 126 | auto container = new QWidget(this); 127 | setCentralWidget(container); 128 | 129 | auto hBox = new QHBoxLayout(container); 130 | 131 | auto setupGroup = new QGroupBox("Setup", container); 132 | hBox->addWidget(setupGroup); 133 | 134 | m_setupLayout = new QVBoxLayout(setupGroup); 135 | setupGroup->setLayout(m_setupLayout); 136 | setupGroup->setMaximumWidth(300); 137 | 138 | // CodeEditor 139 | m_codeEditor = new QCodeEditor(this); 140 | hBox->addWidget(m_codeEditor); 141 | 142 | m_codeSampleCombobox = new QComboBox(setupGroup); 143 | m_highlighterCombobox = new QComboBox(setupGroup); 144 | m_completerCombobox = new QComboBox(setupGroup); 145 | m_styleCombobox = new QComboBox(setupGroup); 146 | 147 | m_readOnlyCheckBox = new QCheckBox("Read Only", setupGroup); 148 | m_wordWrapCheckBox = new QCheckBox("Word Wrap", setupGroup); 149 | m_tabReplaceEnabledCheckbox = new QCheckBox("Tab Replace", setupGroup); 150 | m_tabReplaceNumberSpinbox = new QSpinBox(setupGroup); 151 | m_autoIndentationCheckbox = new QCheckBox("Auto Indentation", setupGroup); 152 | 153 | m_actionToggleComment = new QAction("Toggle comment", this); 154 | m_actionToggleBlockComment = new QAction("Toggle block comment", this); 155 | 156 | m_actionToggleComment->setShortcut(QKeySequence("Ctrl+/")); 157 | m_actionToggleBlockComment->setShortcut(QKeySequence("Shift+Ctrl+/")); 158 | 159 | connect(m_actionToggleComment, &QAction::triggered, m_codeEditor, &QCodeEditor::toggleComment); 160 | connect(m_actionToggleBlockComment, &QAction::triggered, m_codeEditor, &QCodeEditor::toggleBlockComment); 161 | 162 | m_mainMenu = new QMenu("Actions", this); 163 | m_mainMenu->addAction(m_actionToggleComment); 164 | m_mainMenu->addAction(m_actionToggleBlockComment); 165 | menuBar()->addMenu(m_mainMenu); 166 | 167 | // Adding widgets 168 | m_setupLayout->addWidget(new QLabel(tr("Code sample"), setupGroup)); 169 | m_setupLayout->addWidget(m_codeSampleCombobox); 170 | m_setupLayout->addWidget(new QLabel(tr("Completer"), setupGroup)); 171 | m_setupLayout->addWidget(m_completerCombobox); 172 | m_setupLayout->addWidget(new QLabel(tr("Highlighter"), setupGroup)); 173 | m_setupLayout->addWidget(m_highlighterCombobox); 174 | m_setupLayout->addWidget(new QLabel(tr("Style"), setupGroup)); 175 | m_setupLayout->addWidget(m_styleCombobox); 176 | m_setupLayout->addWidget(m_readOnlyCheckBox); 177 | m_setupLayout->addWidget(m_wordWrapCheckBox); 178 | m_setupLayout->addWidget(m_tabReplaceEnabledCheckbox); 179 | m_setupLayout->addWidget(m_tabReplaceNumberSpinbox); 180 | m_setupLayout->addWidget(m_autoIndentationCheckbox); 181 | m_setupLayout->addSpacerItem(new QSpacerItem(1, 2, QSizePolicy::Minimum, QSizePolicy::Expanding)); 182 | } 183 | 184 | void MainWindow::setupWidgets() 185 | { 186 | setWindowTitle("QCodeEditor Demo"); 187 | 188 | // CodeEditor 189 | m_codeEditor->setPlainText (m_codeSamples[0].second); 190 | m_codeEditor->setSyntaxStyle(m_styles[0].second); 191 | m_codeEditor->setCompleter (m_completers[0].second); 192 | m_codeEditor->setHighlighter(new QCXXHighlighter); 193 | 194 | // m_codeEditor->squiggle(QCodeEditor::SeverityLevel::Warning, {3,2}, {13,5}, "unused variable"); 195 | m_codeEditor->squiggle(QCodeEditor::SeverityLevel::Error, {7,0}, {8,0}, "Big error"); 196 | 197 | 198 | //m_codeEditor->clearSquiggle(); 199 | 200 | QStringList list; 201 | // Code samples 202 | for (auto&& el : m_codeSamples) 203 | { 204 | list << el.first; 205 | } 206 | 207 | m_codeSampleCombobox->addItems(list); 208 | list.clear(); 209 | 210 | // Highlighter 211 | for (auto&& el : m_highlighters) 212 | { 213 | list << el.first; 214 | } 215 | 216 | m_highlighterCombobox->addItems(list); 217 | list.clear(); 218 | 219 | // Completer 220 | for (auto&& el : m_completers) 221 | { 222 | list << el.first; 223 | } 224 | 225 | m_completerCombobox->addItems(list); 226 | list.clear(); 227 | 228 | // Styles 229 | for (auto&& el : m_styles) 230 | { 231 | list << el.first; 232 | } 233 | 234 | m_styleCombobox->addItems(list); 235 | list.clear(); 236 | 237 | m_tabReplaceEnabledCheckbox->setChecked(m_codeEditor->tabReplace()); 238 | m_tabReplaceNumberSpinbox->setValue(m_codeEditor->tabReplaceSize()); 239 | m_tabReplaceNumberSpinbox->setSuffix(tr(" spaces")); 240 | m_autoIndentationCheckbox->setChecked(m_codeEditor->autoIndentation()); 241 | 242 | m_wordWrapCheckBox->setChecked(m_codeEditor->wordWrapMode() != QTextOption::NoWrap); 243 | 244 | } 245 | 246 | void MainWindow::performConnections() 247 | { 248 | connect( 249 | m_codeSampleCombobox, 250 | QOverload::of(&QComboBox::currentIndexChanged), 251 | [this](int index) 252 | { m_codeEditor->setPlainText(m_codeSamples[index].second); } 253 | ); 254 | 255 | connect( 256 | m_highlighterCombobox, 257 | QOverload::of(&QComboBox::currentIndexChanged), 258 | [this](int index) 259 | { m_codeEditor->setHighlighter(m_highlighters[index].second); } 260 | ); 261 | 262 | connect( 263 | m_completerCombobox, 264 | QOverload::of(&QComboBox::currentIndexChanged), 265 | [this](int index) 266 | { m_codeEditor->setCompleter(m_completers[index].second); } 267 | ); 268 | 269 | connect( 270 | m_styleCombobox, 271 | QOverload::of(&QComboBox::currentIndexChanged), 272 | [this](int index) 273 | { m_codeEditor->setSyntaxStyle(m_styles[index].second); } 274 | ); 275 | 276 | connect( 277 | m_readOnlyCheckBox, 278 | &QCheckBox::stateChanged, 279 | [this](int state) 280 | { m_codeEditor->setReadOnly(state != 0); } 281 | ); 282 | 283 | connect( 284 | m_wordWrapCheckBox, 285 | &QCheckBox::stateChanged, 286 | [this](int state) 287 | { 288 | if (state != 0) 289 | { 290 | m_codeEditor->setWordWrapMode(QTextOption::WordWrap); 291 | } 292 | else 293 | { 294 | m_codeEditor->setWordWrapMode(QTextOption::NoWrap); 295 | } 296 | } 297 | ); 298 | 299 | connect( 300 | m_tabReplaceEnabledCheckbox, 301 | &QCheckBox::stateChanged, 302 | [this](int state) 303 | { m_codeEditor->setTabReplace(state != 0); } 304 | ); 305 | 306 | connect( 307 | m_tabReplaceNumberSpinbox, 308 | QOverload::of(&QSpinBox::valueChanged), 309 | [this](int value) 310 | { m_codeEditor->setTabReplaceSize(value); } 311 | ); 312 | 313 | connect( 314 | m_autoIndentationCheckbox, 315 | &QCheckBox::stateChanged, 316 | [this](int state) 317 | { m_codeEditor->setAutoIndentation(state != 0); } 318 | ); 319 | } 320 | -------------------------------------------------------------------------------- /example/src/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Qt 3 | #include 4 | 5 | // Demo 6 | #include 7 | 8 | const char* codeSample = R"( 9 | /**************************************************************************** 10 | ** 11 | ** Copyright (C) 2016 The Qt Company Ltd. 12 | ** Contact: https://www.qt.io/licensing/ 13 | ** 14 | ** This file is part of the examples of the Qt Toolkit. 15 | ** 16 | ** $QT_BEGIN_LICENSE:BSD$ 17 | ** Commercial License Usage 18 | ** Licensees holding valid commercial Qt licenses may use this file in 19 | ** accordance with the commercial license agreement provided with the 20 | ** Software or, alternatively, in accordance with the terms contained in 21 | ** a written agreement between you and The Qt Company. For licensing terms 22 | ** and conditions see https://www.qt.io/terms-conditions. For further 23 | ** information use the contact form at https://www.qt.io/contact-us. 24 | ** 25 | ** BSD License Usage 26 | ** Alternatively, you may use this file under the terms of the BSD license 27 | ** as follows: 28 | ** 29 | ** "Redistribution and use in source and binary forms, with or without 30 | ** modification, are permitted provided that the following conditions are 31 | ** met: 32 | ** * Redistributions of source code must retain the above copyright 33 | ** notice, this list of conditions and the following disclaimer. 34 | ** * Redistributions in binary form must reproduce the above copyright 35 | ** notice, this list of conditions and the following disclaimer in 36 | ** the documentation and/or other materials provided with the 37 | ** distribution. 38 | ** * Neither the name of The Qt Company Ltd nor the names of its 39 | ** contributors may be used to endorse or promote products derived 40 | ** from this software without specific prior written permission. 41 | ** 42 | ** 43 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 44 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 45 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 46 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 47 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 48 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 49 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 50 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 51 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 52 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 53 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 54 | ** 55 | ** $QT_END_LICENSE$ 56 | ** 57 | ****************************************************************************/ 58 | 59 | #include 60 | #include 61 | 62 | #include "game.h" 63 | //! [0] 64 | int main(int argc, char *argv[]) 65 | { 66 | )" "\t" R"(QCoreApplication app(argc, argv); 67 | QStringList args = QCoreApplication::arguments(); 68 | bool newGame = true; 69 | if (args.length() > 1) 70 | newGame = (args[1].toLower() != QStringLiteral("load")); 71 | bool json = true; 72 | if (args.length() > 2) 73 | json = (args[2].toLower() != QStringLiteral("binary")); 74 | 75 | Game game; 76 | if (newGame) 77 | game.newGame(); 78 | else if (!game.loadGame(json ? Game::Json : Game::Binary)) 79 | return 1; 80 | // Game is played; changes are made... 81 | //! [0] 82 | //! [1] 83 | QTextStream(stdout) << "Game ended in the following state:\n"; 84 | game.print(); 85 | if (!game.saveGame(json ? Game::Json : Game::Binary)) 86 | return 1; 87 | 88 | return 0; 89 | } 90 | //! [1] 91 | 92 | )"; 93 | 94 | int main(int argc, char** argv) 95 | { 96 | // Creating application 97 | QApplication a(argc, argv); 98 | 99 | // Creating main window 100 | MainWindow w; 101 | w.show(); 102 | 103 | return QApplication::exec(); 104 | } -------------------------------------------------------------------------------- /include/QCXXHighlighter: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QCodeEditor: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QGLSLCompleter: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QGLSLHighlighter: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QHighlightBlockRule: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QHighlightRule: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QJSHighlighter: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QJSONHighlighter: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QJavaHighlighter: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /include/QLanguage: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QLineNumberArea: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QLuaCompleter: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QLuaHighlighter: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /include/QPythonCompleter: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QPythonHighlighter: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QStyleSyntaxHighlighter: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QSyntaxStyle: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/QXMLHighlighter: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include -------------------------------------------------------------------------------- /include/internal/QCXXHighlighter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // QCodeEditor 4 | #include 5 | #include // Required for inheritance 6 | 7 | // Qt 8 | #include 9 | #include 10 | 11 | class QSyntaxStyle; 12 | 13 | /** 14 | * @brief Class, that describes C++ code 15 | * highlighter. 16 | */ 17 | class QCXXHighlighter : public QStyleSyntaxHighlighter 18 | { 19 | Q_OBJECT 20 | public: 21 | /** 22 | * @brief Constructor. 23 | * @param document Pointer to document. 24 | */ 25 | explicit QCXXHighlighter(QTextDocument *document = nullptr); 26 | 27 | protected: 28 | void highlightBlock(const QString &text) override; 29 | 30 | private: 31 | QVector m_highlightRules; 32 | 33 | QRegularExpression m_includePattern; 34 | QRegularExpression m_functionPattern; 35 | QRegularExpression m_defTypePattern; 36 | 37 | QRegularExpression m_commentStartPattern; 38 | QRegularExpression m_commentEndPattern; 39 | }; 40 | -------------------------------------------------------------------------------- /include/internal/QCodeEditor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Qt 4 | #include // Required for inheritance 5 | 6 | class QCompleter; 7 | class QLineNumberArea; 8 | class QSyntaxStyle; 9 | class QStyleSyntaxHighlighter; 10 | class QFramedTextAttribute; 11 | 12 | /** 13 | * @brief Class, that describes code editor. 14 | */ 15 | class QCodeEditor : public QTextEdit 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | /** 21 | * @brief The SeverityLevel enum 22 | * @note the order should be: the bigger the more important 23 | */ 24 | enum class SeverityLevel 25 | { 26 | Hint, 27 | Information, 28 | Warning, 29 | Error 30 | }; 31 | 32 | struct Parenthesis 33 | { 34 | QChar left, right; 35 | bool autoComplete, autoRemove, tabJumpOut; 36 | 37 | Parenthesis(QChar l = '(', QChar r = ')', bool complete = true, bool remove = true, bool jumpout = true) 38 | : left(l), right(r), autoComplete(complete), autoRemove(remove), tabJumpOut(jumpout) 39 | { 40 | } 41 | }; 42 | 43 | /** 44 | * @brief Constructor. 45 | * @param widget Pointer to parent widget. 46 | */ 47 | explicit QCodeEditor(QWidget *widget = nullptr); 48 | 49 | // Disable copying 50 | QCodeEditor(const QCodeEditor &) = delete; 51 | QCodeEditor &operator=(const QCodeEditor &) = delete; 52 | 53 | /** 54 | * @brief Method for getting first visible block 55 | * index. 56 | * @return Index. 57 | */ 58 | int getFirstVisibleBlock(); 59 | 60 | /** 61 | * @brief Method for setting highlighter. 62 | * @param highlighter Pointer to syntax highlighter. 63 | */ 64 | void setHighlighter(QStyleSyntaxHighlighter *highlighter); 65 | 66 | /** 67 | * @brief Method for setting syntax sty.e. 68 | * @param style Pointer to syntax style. 69 | */ 70 | void setSyntaxStyle(QSyntaxStyle *style); 71 | 72 | /** 73 | * @brief Method for setting tab replacing 74 | * enabled. 75 | */ 76 | void setTabReplace(bool enabled); 77 | 78 | /** 79 | * @brief Method for getting is tab replacing enabled. 80 | * Default value: true 81 | */ 82 | bool tabReplace() const; 83 | 84 | /** 85 | * @brief Method for setting amount of spaces, that will 86 | * replace tab. 87 | * @param val Number of spaces. 88 | */ 89 | void setTabReplaceSize(int val); 90 | 91 | /** 92 | * @brief Method for getting number of spaces, that will 93 | * replace tab if `tabReplace` is true. 94 | * Default: 4 95 | */ 96 | int tabReplaceSize() const; 97 | 98 | /** 99 | * @brief Method for setting auto indentation enabled. 100 | */ 101 | void setAutoIndentation(bool enabled); 102 | 103 | /** 104 | * @brief Method for setting the parentheses. 105 | */ 106 | void setParentheses(const QVector &parentheses); 107 | 108 | /** 109 | * @brief Method for setting extra bottom margin enabled. 110 | */ 111 | void setExtraBottomMargin(bool enabled); 112 | 113 | /** 114 | * @brief Method for getting is auto indentation enabled. 115 | * Default: true 116 | */ 117 | bool autoIndentation() const; 118 | 119 | /** 120 | * @brief Method for setting completer. 121 | * @param completer Pointer to completer object. 122 | */ 123 | void setCompleter(QCompleter *completer); 124 | 125 | /** 126 | * @brief Method for getting completer. 127 | * @return Pointer to completer. 128 | */ 129 | QCompleter *completer() const; 130 | 131 | /** 132 | * @brief squiggle Puts a underline squiggle under text ranges in Editor 133 | * @param level defines the color of the underline depending upon the severity 134 | * @param tooltipMessage The tooltip hover message to show when over selection. 135 | * @note QPair: first -> Line number in 1-based indexing 136 | * second -> Character number in 0-based indexing 137 | */ 138 | void squiggle(SeverityLevel level, QPair, QPair, const QString &tooltipMessage); 139 | 140 | /** 141 | * @brief clearSquiggle, Clears complete squiggle from editor 142 | */ 143 | void clearSquiggle(); 144 | 145 | Q_SIGNALS: 146 | /** 147 | * @brief Signal, the font is changed by the wheel event. 148 | */ 149 | void fontChanged(const QFont &newFont); 150 | 151 | public Q_SLOTS: 152 | 153 | /** 154 | * @brief Slot, that performs insertion of 155 | * completion info into code. 156 | * @param s Data. 157 | */ 158 | void insertCompletion(const QString &s); 159 | 160 | /** 161 | * @brief Slot, that performs update of 162 | * internal editor viewport based on line 163 | * number area width. 164 | */ 165 | void updateLineNumberAreaWidth(int); 166 | 167 | /** 168 | * @brief Slot, that performs update of some 169 | * part of line number area. 170 | * @param rect Area that has to be updated. 171 | */ 172 | void updateLineNumberArea(QRect rect); 173 | 174 | /** 175 | * @brief Slot, that will proceed extra selection 176 | * for current cursor position. 177 | */ 178 | void updateExtraSelection1(); 179 | void updateExtraSelection2(); 180 | 181 | /** 182 | * @brief Slot, that will update editor style. 183 | */ 184 | void updateStyle(); 185 | 186 | /** 187 | * @brief Slot, that indent the selected lines. 188 | */ 189 | void indent(); 190 | 191 | /** 192 | * @brief Slot, that unindent the selected lines. 193 | */ 194 | void unindent(); 195 | 196 | /** 197 | * @brief Slot, that swap the selected lines up. 198 | */ 199 | void swapLineUp(); 200 | 201 | /** 202 | * @brief Slot, that swap the selected lines down. 203 | */ 204 | void swapLineDown(); 205 | 206 | /** 207 | * @brief Slot, that delete the selected lines. 208 | */ 209 | void deleteLine(); 210 | 211 | /** 212 | * @brief Slot, that duplicate the current line or the selection. 213 | */ 214 | void duplicate(); 215 | 216 | /** 217 | * @brief Slot, that toggle single line comment of the 218 | * selected lines. 219 | */ 220 | void toggleComment(); 221 | 222 | /** 223 | * @brief Slot, that toggle block comment of the selection. 224 | */ 225 | void toggleBlockComment(); 226 | 227 | protected: 228 | /** 229 | * @brief Method, that's called on any text insertion of 230 | * mimedata into editor. If it's text - it inserts text 231 | * as plain text. 232 | */ 233 | void insertFromMimeData(const QMimeData *source) override; 234 | 235 | /** 236 | * @brief Method, that's called on editor painting. This 237 | * method if overloaded for line number area redraw. 238 | */ 239 | void paintEvent(QPaintEvent *e) override; 240 | 241 | /** 242 | * @brief Method, that's called on any widget resize. 243 | * This method if overloaded for line number area 244 | * resizing. 245 | */ 246 | void resizeEvent(QResizeEvent *e) override; 247 | 248 | /** 249 | * @brief Method, update the bottom margin when the font changes. 250 | */ 251 | void changeEvent(QEvent *e) override; 252 | 253 | /** 254 | * @brief Method, update the font size when the wheel is rotated with Ctrl pressed 255 | */ 256 | void wheelEvent(QWheelEvent *e) override; 257 | 258 | /** 259 | * @brief Method, that's called on any key press, posted 260 | * into code editor widget. This method is overloaded for: 261 | * 262 | * 1. Completion 263 | * 2. Tab to spaces 264 | * 3. Low indentation 265 | * 4. Auto parenthesis 266 | */ 267 | void keyPressEvent(QKeyEvent *e) override; 268 | 269 | /** 270 | * @brief Method, that's called on focus into widget. 271 | * It's required for setting this widget to set 272 | * completer. 273 | */ 274 | void focusInEvent(QFocusEvent *e) override; 275 | 276 | /** 277 | * @brief Method for tooltip generation 278 | */ 279 | bool event(QEvent *e) override; 280 | 281 | private Q_SLOTS: 282 | /** 283 | * @brief Slot, that updates the bottom margin. 284 | */ 285 | void updateBottomMargin(); 286 | 287 | private: 288 | /** 289 | * @brief Method for initializing default 290 | * monospace font. 291 | */ 292 | void initFont(); 293 | 294 | /** 295 | * @brief Method for performing connection 296 | * of objects. 297 | */ 298 | void performConnections(); 299 | 300 | /** 301 | * @brief Method for updating geometry of line number area. 302 | */ 303 | void updateLineGeometry(); 304 | 305 | /** 306 | * @brief Method, that performs completer processing. 307 | * Returns true if event has to be dropped. 308 | * @param e Pointer to key event. 309 | * @return Shall event be dropped. 310 | */ 311 | bool proceedCompleterBegin(QKeyEvent *e); 312 | void proceedCompleterEnd(QKeyEvent *e); 313 | 314 | /** 315 | * @brief Method for getting character under 316 | * cursor. 317 | * @param offset Offset to cursor. 318 | */ 319 | QChar charUnderCursor(int offset = 0) const; 320 | 321 | /** 322 | * @brief Method for getting word under 323 | * cursor. 324 | * @return Word under cursor. 325 | */ 326 | QString wordUnderCursor() const; 327 | 328 | /** 329 | * @brief Method, that adds highlighting of 330 | * currently selected line to extra selection list. 331 | */ 332 | void highlightCurrentLine(); 333 | 334 | /** 335 | * @brief Method, that adds highlighting of 336 | * parenthesis if available. 337 | */ 338 | void highlightParenthesis(); 339 | 340 | void highlightOccurrences(); 341 | 342 | /** 343 | * @brief Method for remove the first group of regex 344 | * in each line of the selection. 345 | * @param regex remove its first group 346 | * @param force if true, remove regardless of whether 347 | * all lines are begun with regex; if false remove 348 | * only when all lines are begun with regex. 349 | * @return if regex is removed 350 | */ 351 | bool removeInEachLineOfSelection(const QRegularExpression ®ex, bool force); 352 | 353 | /** 354 | * @brief Method for add the str at the begin of regex 355 | * in each line of the selection. 356 | * @param regex add at the begin of its match 357 | * @param str string to add 358 | */ 359 | void addInEachLineOfSelection(const QRegularExpression ®ex, const QString &str); 360 | 361 | /** 362 | * @brief The SquiggleInformation struct, Line number will be index of vector+1; 363 | */ 364 | struct SquiggleInformation 365 | { 366 | SquiggleInformation() = default; 367 | 368 | SquiggleInformation(QPair start, QPair stop, const QString &text) 369 | : m_startPos(start), m_stopPos(stop), m_tooltipText(text) 370 | { 371 | } 372 | 373 | QPair m_startPos; 374 | QPair m_stopPos; 375 | QString m_tooltipText; 376 | }; 377 | 378 | QStyleSyntaxHighlighter *m_highlighter; 379 | QSyntaxStyle *m_syntaxStyle; 380 | QLineNumberArea *m_lineNumberArea; 381 | QCompleter *m_completer; 382 | 383 | bool m_autoIndentation; 384 | bool m_replaceTab; 385 | bool m_extraBottomMargin; 386 | QString m_tabReplace; 387 | 388 | QList extra1, extra2, extra_squiggles; 389 | 390 | QVector m_squiggler; 391 | 392 | QVector m_parentheses; 393 | }; 394 | -------------------------------------------------------------------------------- /include/internal/QGLSLCompleter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Qt 4 | #include // Required for inheritance 5 | 6 | /** 7 | * @brief Class, that describes completer with 8 | * glsl specific types and functions. 9 | */ 10 | class QGLSLCompleter : public QCompleter 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | /** 16 | * @brief Constructor. 17 | * @param parent Pointer to parent QObject. 18 | */ 19 | explicit QGLSLCompleter(QObject *parent = nullptr); 20 | }; 21 | -------------------------------------------------------------------------------- /include/internal/QGLSLHighlighter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // QCodeEditor 4 | #include 5 | #include // Required for inheritance 6 | 7 | // Qt 8 | #include 9 | #include 10 | 11 | class QSyntaxStyle; 12 | 13 | /** 14 | * @brief Class, that describes Glsl code 15 | * highlighter. 16 | */ 17 | class QGLSLHighlighter : public QStyleSyntaxHighlighter 18 | { 19 | Q_OBJECT 20 | public: 21 | /** 22 | * @brief Constructor. 23 | * @param document Pointer to document. 24 | */ 25 | explicit QGLSLHighlighter(QTextDocument *document = nullptr); 26 | 27 | protected: 28 | void highlightBlock(const QString &text) override; 29 | 30 | private: 31 | QVector m_highlightRules; 32 | 33 | QRegularExpression m_includePattern; 34 | QRegularExpression m_functionPattern; 35 | QRegularExpression m_defTypePattern; 36 | 37 | QRegularExpression m_commentStartPattern; 38 | QRegularExpression m_commentEndPattern; 39 | }; 40 | -------------------------------------------------------------------------------- /include/internal/QHighlightBlockRule.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Qt 4 | #include 5 | #include 6 | 7 | struct QHighlightBlockRule 8 | { 9 | QHighlightBlockRule() : startPattern(), endPattern(), formatName() 10 | { 11 | } 12 | 13 | QHighlightBlockRule(QRegularExpression start, QRegularExpression end, QString format) 14 | : startPattern(std::move(start)), endPattern(std::move(end)), formatName(std::move(format)) 15 | { 16 | } 17 | 18 | QRegularExpression startPattern; 19 | QRegularExpression endPattern; 20 | QString formatName; 21 | }; 22 | -------------------------------------------------------------------------------- /include/internal/QHighlightRule.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Qt 4 | #include 5 | #include 6 | 7 | struct QHighlightRule 8 | { 9 | QHighlightRule() : pattern(), formatName() 10 | { 11 | } 12 | 13 | QHighlightRule(QRegularExpression p, QString f) : pattern(std::move(p)), formatName(std::move(f)) 14 | { 15 | } 16 | 17 | QRegularExpression pattern; 18 | QString formatName; 19 | }; -------------------------------------------------------------------------------- /include/internal/QJSHighlighter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include // Required for inheritance 5 | 6 | // Qt 7 | #include 8 | #include 9 | 10 | class QSyntaxStyle; 11 | 12 | /** 13 | * @brief Derived to implement highlighting of JavaScript code. 14 | */ 15 | class QJSHighlighter : public QStyleSyntaxHighlighter 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | /** 21 | * @brief Constructs a new instance of a JavaScript highlighter. 22 | * @param document The text document to be highlighted. 23 | * This may be a null pointer. 24 | */ 25 | explicit QJSHighlighter(QTextDocument *document = nullptr); 26 | 27 | protected: 28 | void highlightBlock(const QString &text) override; 29 | 30 | private: 31 | QVector m_highlightRules; 32 | 33 | QRegularExpression m_commentStartPattern; 34 | QRegularExpression m_commentEndPattern; 35 | }; 36 | -------------------------------------------------------------------------------- /include/internal/QJSONHighlighter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // QCodeEditor 4 | #include 5 | #include // Required for inheritance 6 | 7 | // Qt 8 | #include 9 | 10 | /** 11 | * @brief Class, that describes JSON code 12 | * highlighter. 13 | */ 14 | class QJSONHighlighter : public QStyleSyntaxHighlighter 15 | { 16 | Q_OBJECT 17 | public: 18 | /** 19 | * @brief Constructor. 20 | * @param document Pointer to document. 21 | */ 22 | explicit QJSONHighlighter(QTextDocument *document = nullptr); 23 | 24 | protected: 25 | void highlightBlock(const QString &text) override; 26 | 27 | private: 28 | QVector m_highlightRules; 29 | QRegularExpression m_keyRegex; 30 | }; 31 | -------------------------------------------------------------------------------- /include/internal/QJavaHighlighter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // QCodeEditor 4 | #include 5 | #include // Required for inheritance 6 | 7 | // Qt 8 | #include 9 | #include 10 | 11 | class QSyntaxStyle; 12 | 13 | /** 14 | * @brief Derived to implement highlighting of Java code. 15 | */ 16 | class QJavaHighlighter : public QStyleSyntaxHighlighter 17 | { 18 | Q_OBJECT 19 | public: 20 | /** 21 | * @brief Constructs a new instance of a Java highlighter. 22 | * @param document The text document to be highlighted. 23 | * This may be a null pointer. 24 | */ 25 | explicit QJavaHighlighter(QTextDocument *document = nullptr); 26 | 27 | protected: 28 | /** 29 | * @brief Derived to highlight blocks of Java code. 30 | * @param text The block of text containing Java code. 31 | */ 32 | void highlightBlock(const QString &text) override; 33 | 34 | private: 35 | QVector m_highlightRules; 36 | 37 | QRegularExpression m_commentStartPattern; 38 | QRegularExpression m_commentEndPattern; 39 | }; 40 | -------------------------------------------------------------------------------- /include/internal/QLanguage.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Qt 4 | #include 5 | #include // Required for inheritance 6 | #include 7 | 8 | class QIODevice; 9 | 10 | /** 11 | * Class, that describes object for parsing 12 | * language file. 13 | */ 14 | class QLanguage : public QObject 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | /** 20 | * @brief Constructor. 21 | * @param parent Pointer to parent QObject. 22 | */ 23 | explicit QLanguage(QIODevice *device = nullptr, QObject *parent = nullptr); 24 | 25 | /** 26 | * @brief Method for parsing. 27 | * @param device Pointer to device. 28 | * @return Success. 29 | */ 30 | bool load(QIODevice *device); 31 | 32 | /** 33 | * @brief Method for getting available keys. 34 | */ 35 | QStringList keys(); 36 | 37 | /** 38 | * @brief Method for getting names 39 | * from key. 40 | * @param name 41 | * @return 42 | */ 43 | QStringList names(const QString &key); 44 | 45 | /** 46 | * @brief Method for getting is object loaded. 47 | */ 48 | bool isLoaded() const; 49 | 50 | private: 51 | bool m_loaded; 52 | 53 | QMap m_list; 54 | }; 55 | -------------------------------------------------------------------------------- /include/internal/QLineNumberArea.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Qt 4 | #include // Required for inheritance 5 | 6 | #include 7 | 8 | class QSyntaxStyle; 9 | 10 | /** 11 | * @brief Class, that describes line number area widget. 12 | */ 13 | class QLineNumberArea : public QWidget 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | /** 19 | * @brief Constructor. 20 | * @param parent Pointer to parent QTextEdit widget. 21 | */ 22 | explicit QLineNumberArea(QCodeEditor *parent = nullptr); 23 | 24 | // Disable copying 25 | QLineNumberArea(const QLineNumberArea &) = delete; 26 | QLineNumberArea &operator=(const QLineNumberArea &) = delete; 27 | 28 | /** 29 | * @brief Overridden method for getting line number area 30 | * size. 31 | */ 32 | QSize sizeHint() const override; 33 | 34 | /** 35 | * @brief Method for setting syntax style object. 36 | * @param style Pointer to syntax style. 37 | */ 38 | void setSyntaxStyle(QSyntaxStyle *style); 39 | 40 | /** 41 | * @brief Method for getting syntax style. 42 | * @return Pointer to syntax style. 43 | */ 44 | QSyntaxStyle *syntaxStyle() const; 45 | 46 | void lint(QCodeEditor::SeverityLevel level, int from, int to); 47 | 48 | void clearLint(); 49 | 50 | protected: 51 | void paintEvent(QPaintEvent *event) override; 52 | 53 | private: 54 | QSyntaxStyle *m_syntaxStyle; 55 | 56 | QCodeEditor *m_codeEditParent; 57 | 58 | QMap m_squiggles; 59 | }; 60 | -------------------------------------------------------------------------------- /include/internal/QLuaCompleter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Qt 4 | #include // Required for inheritance 5 | 6 | /** 7 | * @brief Class, that describes completer with 8 | * glsl specific types and functions. 9 | */ 10 | class QLuaCompleter : public QCompleter 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | /** 16 | * @brief Constructor. 17 | * @param parent Pointer to parent QObject. 18 | */ 19 | explicit QLuaCompleter(QObject *parent = nullptr); 20 | }; 21 | -------------------------------------------------------------------------------- /include/internal/QLuaHighlighter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // QCodeEditor 4 | #include 5 | #include 6 | #include // Required for inheritance 7 | 8 | // Qt 9 | #include 10 | #include 11 | #include 12 | 13 | class QSyntaxStyle; 14 | 15 | /** 16 | * @brief Class, that describes C++ code 17 | * highlighter. 18 | */ 19 | class QLuaHighlighter : public QStyleSyntaxHighlighter 20 | { 21 | Q_OBJECT 22 | public: 23 | /** 24 | * @brief Constructor. 25 | * @param document Pointer to document. 26 | */ 27 | explicit QLuaHighlighter(QTextDocument *document = nullptr); 28 | 29 | protected: 30 | void highlightBlock(const QString &text) override; 31 | 32 | private: 33 | QVector m_highlightRules; 34 | QVector m_highlightBlockRules; 35 | 36 | QRegularExpression m_requirePattern; 37 | QRegularExpression m_functionPattern; 38 | QRegularExpression m_defTypePattern; 39 | }; 40 | -------------------------------------------------------------------------------- /include/internal/QPythonCompleter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Qt 4 | #include // Required for inheritance 5 | 6 | /** 7 | * @brief Class, that describes completer with 8 | * glsl specific types and functions. 9 | */ 10 | class QPythonCompleter : public QCompleter 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | /** 16 | * @brief Constructor. 17 | * @param parent Pointer to parent QObject. 18 | */ 19 | explicit QPythonCompleter(QObject *parent = nullptr); 20 | }; 21 | -------------------------------------------------------------------------------- /include/internal/QPythonHighlighter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // QCodeEditor 4 | #include 5 | #include 6 | #include // Required for inheritance 7 | 8 | // Qt 9 | #include 10 | #include 11 | 12 | class QSyntaxStyle; 13 | 14 | /** 15 | * @brief Class, that describes Glsl code 16 | * highlighter. 17 | */ 18 | class QPythonHighlighter : public QStyleSyntaxHighlighter 19 | { 20 | Q_OBJECT 21 | public: 22 | /** 23 | * @brief Constructor. 24 | * @param document Pointer to document. 25 | */ 26 | explicit QPythonHighlighter(QTextDocument *document = nullptr); 27 | 28 | protected: 29 | void highlightBlock(const QString &text) override; 30 | 31 | private: 32 | QVector m_highlightRules; 33 | QVector m_highlightBlockRules; 34 | 35 | QRegularExpression m_includePattern; 36 | QRegularExpression m_functionPattern; 37 | QRegularExpression m_defTypePattern; 38 | }; 39 | -------------------------------------------------------------------------------- /include/internal/QStyleSyntaxHighlighter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Qt 4 | #include // Required for inheritance 5 | 6 | class QSyntaxStyle; 7 | 8 | /** 9 | * @brief Class, that descrubes highlighter with 10 | * syntax style. 11 | */ 12 | class QStyleSyntaxHighlighter : public QSyntaxHighlighter 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | /** 18 | * @brief Constructor. 19 | * @param document Pointer to text document. 20 | */ 21 | explicit QStyleSyntaxHighlighter(QTextDocument *document = nullptr); 22 | 23 | // Disable copying 24 | QStyleSyntaxHighlighter(const QStyleSyntaxHighlighter &) = delete; 25 | QStyleSyntaxHighlighter &operator=(const QStyleSyntaxHighlighter &) = delete; 26 | 27 | /** 28 | * @brief Method for setting syntax style. 29 | * @param style Pointer to syntax style. 30 | */ 31 | void setSyntaxStyle(QSyntaxStyle *style); 32 | 33 | /** 34 | * @brief Method for getting syntax style. 35 | * @return Pointer to syntax style. May be nullptr. 36 | */ 37 | QSyntaxStyle *syntaxStyle() const; 38 | 39 | /** 40 | * @brief Method for getting a sequence that marks a comment line. 41 | * @return QString containing a sequence that marks a comment line. 42 | * @details Returned value can be empty meaning that this language doesn't 43 | * support single-line comments 44 | */ 45 | QString commentLineSequence() const; 46 | 47 | /** 48 | * @brief Method to set a sequence that marks a comment line. 49 | * @param commentLineSequence a sequence that marks a comment line. Can be empty. 50 | */ 51 | void setCommentLineSequence(const QString &commentLineSequence); 52 | 53 | /** 54 | * @brief Method for getting a sequence that marks a start of a multi line comment block. 55 | * @return QString containing a sequence that marks a multi line comment block. 56 | * @details Returned value can be empty meaning that this language doesn't 57 | * support multi line comments 58 | */ 59 | QString startCommentBlockSequence() const; 60 | 61 | /** 62 | * @brief Method to set a sequence that marks a start of a multi line comment block. 63 | * @param commentLineSequence a sequence that marks a start of a multi line comment block. Can be empty. 64 | */ 65 | void setStartCommentBlockSequence(const QString &startCommentBlockSequence); 66 | 67 | /** 68 | * @brief Method for getting a sequence that marks a end of a multi line comment block. 69 | * @return QString containing a sequence that marks an end multi line comment block. 70 | * @details Returned value can be empty meaning that this language doesn't 71 | * support multi line comments. 72 | */ 73 | QString endCommentBlockSequence() const; 74 | 75 | /** 76 | * @brief Method to set a sequence that marks an end of a multi line comment block. 77 | * @param commentLineSequence a sequence that marks an end of a multi line comment block. Can be empty. 78 | */ 79 | void setEndCommentBlockSequence(const QString &endCommentBlockSequence); 80 | 81 | private: 82 | QSyntaxStyle *m_syntaxStyle; 83 | 84 | protected: 85 | QString m_commentLineSequence; 86 | QString m_startCommentBlockSequence; 87 | QString m_endCommentBlockSequence; 88 | }; 89 | -------------------------------------------------------------------------------- /include/internal/QSyntaxStyle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Qt 4 | #include 5 | #include // Required for inheritance 6 | #include 7 | #include 8 | 9 | /** 10 | * @brief Class, that describes Qt style 11 | * parser for QCodeEditor. 12 | */ 13 | class QSyntaxStyle : public QObject 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | /** 19 | * @brief Constructor. 20 | * @param parent Pointer to parent QObject 21 | */ 22 | explicit QSyntaxStyle(QObject *parent = nullptr); 23 | 24 | /** 25 | * @brief Method for loading and parsing 26 | * style. 27 | * @param fl Style. 28 | * @return Success. 29 | */ 30 | bool load(const QString &fl); 31 | 32 | /** 33 | * @brief Method for getting style name. 34 | * @return Name. 35 | */ 36 | QString name() const; 37 | 38 | /** 39 | * @brief Method for checking is syntax style loaded. 40 | * @return Is loaded. 41 | */ 42 | bool isLoaded() const; 43 | 44 | /** 45 | * @brief Method for getting format for property 46 | * name. 47 | * @param name Property name. 48 | * @return Text char format. 49 | */ 50 | QTextCharFormat getFormat(const QString &name) const; 51 | 52 | /** 53 | * @brief Static method for getting default style. 54 | * @return Pointer to default style. 55 | */ 56 | static QSyntaxStyle *defaultStyle(); 57 | 58 | private: 59 | QString m_name; 60 | 61 | QMap m_data; 62 | 63 | bool m_loaded; 64 | }; 65 | -------------------------------------------------------------------------------- /include/internal/QXMLHighlighter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // QCodeEditor 4 | #include // Required for inheritance 5 | 6 | // Qt 7 | #include 8 | #include 9 | 10 | /** 11 | * @brief Class, that describes XML code 12 | * highlighter. 13 | */ 14 | class QXMLHighlighter : public QStyleSyntaxHighlighter 15 | { 16 | Q_OBJECT 17 | public: 18 | /** 19 | * @brief Constructor. 20 | * @param document Pointer to document. 21 | */ 22 | explicit QXMLHighlighter(QTextDocument *document = nullptr); 23 | 24 | protected: 25 | void highlightBlock(const QString &text) override; 26 | 27 | private: 28 | void highlightByRegex(const QTextCharFormat &format, const QRegularExpression ®ex, const QString &text); 29 | 30 | QVector m_xmlKeywordRegexes; 31 | QRegularExpression m_xmlElementRegex; 32 | QRegularExpression m_xmlAttributeRegex; 33 | QRegularExpression m_xmlValueRegex; 34 | QRegularExpression m_xmlCommentBeginRegex; 35 | QRegularExpression m_xmlCommentEndRegex; 36 | }; 37 | -------------------------------------------------------------------------------- /resources/default_style.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /resources/languages/cpp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | alignas 5 | alignof 6 | and 7 | and_eq 8 | asm 9 | atomic_cancel 10 | atomic_commit 11 | atomic_noexcept 12 | auto 13 | bitand 14 | bitor 15 | break 16 | case 17 | catch 18 | class 19 | compl 20 | concept 21 | const 22 | constexpr 23 | const_cast 24 | continue 25 | co_await 26 | co_return 27 | co_yield 28 | decltype 29 | default 30 | delete 31 | do 32 | dynamic_cast 33 | else 34 | enum 35 | explicit 36 | export 37 | extern 38 | false 39 | for 40 | friend 41 | goto 42 | if 43 | import 44 | inline 45 | module 46 | mutable 47 | namespace 48 | new 49 | noexcept 50 | not 51 | not_eq 52 | nullptr 53 | operator 54 | or 55 | or_eq 56 | private 57 | protected 58 | public 59 | reflexpr 60 | register(2) 61 | reinterpret_cast 62 | requires 63 | return 64 | sizeof 65 | static 66 | static_assert 67 | static_cast 68 | struct 69 | switch 70 | synchronized 71 | template 72 | this 73 | thread_local 74 | throw 75 | true 76 | try 77 | typedef 78 | typeid 79 | typename 80 | union 81 | using 82 | virtual 83 | volatile 84 | while 85 | xor 86 | xor_eq 87 | 88 | 89 | bool 90 | char 91 | char16_t 92 | char32_t 93 | double 94 | float 95 | int 96 | long 97 | short 98 | signed 99 | unsigned 100 | void 101 | wchar_t 102 | 103 | -------------------------------------------------------------------------------- /resources/languages/glsl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | attribute 5 | const 6 | uniform 7 | varying 8 | buffer 9 | shared 10 | coherent 11 | volatile 12 | restrict 13 | readonly 14 | writeonly 15 | layout 16 | centroid 17 | flat 18 | smooth 19 | noperspective 20 | patch 21 | sample 22 | break 23 | continue 24 | do 25 | for 26 | while 27 | switch 28 | case 29 | default 30 | if 31 | else 32 | subroutine 33 | in 34 | out 35 | inout 36 | invariant 37 | precise 38 | discard 39 | return 40 | lowp 41 | mediump 42 | highp 43 | precision 44 | struct 45 | 46 | 47 | radians 48 | degrees 49 | sin 50 | cos 51 | tan 52 | asin 53 | acos 54 | atan 55 | sinh 56 | cosh 57 | tanh 58 | asinh 59 | acosh 60 | atanh 61 | pow 62 | exp 63 | log 64 | exp2 65 | log2 66 | sqrt 67 | inversesqrt 68 | abs 69 | sign 70 | floor 71 | trunc 72 | round 73 | roundEven 74 | ceil 75 | fract 76 | mod 77 | modf 78 | min 79 | max 80 | clamp 81 | mix 82 | step 83 | smoothstep 84 | isnan 85 | isinf 86 | floatBitsToInt 87 | floatBitsToUint 88 | intBitsToFloat 89 | uintBitsToFloat 90 | fma 91 | frexp 92 | ldexp 93 | packUnorm2x16 94 | packSnorm2x16 95 | packUnorm4x8 96 | packSnorm4x8 97 | unpackUnorm2x16 98 | unpackSnorm2x16 99 | unpackUnorm4x8 100 | unpackSnorm4x8 101 | packDouble2x32 102 | unpackDouble2x32 103 | packHalf2x16 104 | unpackHalf2x16 105 | length 106 | distance 107 | dot 108 | cross 109 | normalize 110 | ftransform 111 | faceforward 112 | reflect 113 | refract 114 | matrixCompMult 115 | outerProduct 116 | transpose 117 | determinant 118 | inverse 119 | lessThan 120 | lessThanEqual 121 | greaterThan 122 | greaterThanEqual 123 | equal 124 | notEqual 125 | any 126 | all 127 | not 128 | uaddCarry 129 | usubBorrow 130 | umulExtended 131 | imulExtended 132 | bitfieldExtract 133 | bitfieldInsert 134 | bitfieldReverse 135 | bitCount 136 | findLSB 137 | findMSB 138 | textureSize 139 | textureQueryLod 140 | textureQueryLevels 141 | textureSamples 142 | texture 143 | textureProj 144 | textureLod 145 | textureOffset 146 | texelFetch 147 | texelFetchOffset 148 | textureProjOffset 149 | textureLodOffset 150 | textureProjLod 151 | textureProjLodOffset 152 | textureGrad 153 | textureGradOffset 154 | textureProjGrad 155 | textureProjGradOffset 156 | textureGather 157 | textureGatherOffset 158 | textureGatherOffsets 159 | texture1D 160 | texture1DProj 161 | texture1DLod 162 | texture1DProjLod 163 | texture2D 164 | texture2DProj 165 | texture2DLod 166 | texture2DProjLod 167 | texture3D 168 | texture3DProj 169 | texture3DLod 170 | texture3DProjLod 171 | textureCube 172 | textureCubeLod 173 | shadow1D 174 | shadow2D 175 | shadow1DProj 176 | shadow2DProj 177 | shadow1DLod 178 | shadow2DLod 179 | shadow1DProjLod 180 | shadow2DProjLod 181 | atomicCounterIncrement 182 | atomicCounterDecrement 183 | atomicCounter 184 | atomicAdd 185 | atomicMin 186 | atomicMax 187 | atomicAnd 188 | atomicOr 189 | atomicXor 190 | atomicExchange 191 | atomicCompSwap 192 | imageSize 193 | imageSamples 194 | imageLoad 195 | imageStore 196 | imageAtomicAdd 197 | imageAtomicMin 198 | imageAtomicMax 199 | imageAtomicAnd 200 | imageAtomicOr 201 | imageAtomicXor 202 | imageAtomicExchange 203 | imageAtomicCompSwap 204 | dFdx 205 | dFdy 206 | dFdxFine 207 | dFdyFine 208 | dFdxCoarse 209 | dFdyCoarse 210 | fwidth 211 | fwidthFine 212 | fwidthCoarse 213 | interpolateAtCentroid 214 | interpolateAtSample 215 | interpolateAtOffset 216 | noise1 217 | noise2 218 | noise3 219 | noise4 220 | EmitStreamVertex 221 | EndStreamPrimitive 222 | EndStreamPrimitive 223 | EndStreamPrimitive 224 | memoryBarrier 225 | memoryBarrierAtomicCounter 226 | memoryBarrierBuffer 227 | memoryBarrierShared 228 | memoryBarrierImage 229 | groupMemoryBarrier 230 | 231 | 232 | float 233 | atomic_uint 234 | double 235 | int 236 | void 237 | bool 238 | true 239 | false 240 | mat2 241 | mat3 242 | mat4 243 | dmat2 244 | dmat3 245 | dmat4 246 | mat2x2 247 | mat2x3 248 | mat2x4 249 | dmat2x2 250 | dmat2x3 251 | dmat2x4 252 | mat3x2 253 | mat3x3 254 | mat3x4 255 | dmat3x2 256 | dmat3x3 257 | dmat3x4 258 | mat4x2 259 | mat4x3 260 | mat4x4 261 | dmat4x2 262 | dmat4x3 263 | dmat4x4 264 | vec2 265 | vec3 266 | vec4 267 | ivec2 268 | ivec3 269 | ivec4 270 | bvec2 271 | bvec3 272 | bvec4 273 | dvec2 274 | dvec3 275 | dvec4 276 | uint 277 | uvec2 278 | uvec3 279 | uvec4 280 | sampler1D 281 | sampler2D 282 | sampler3D 283 | samplerCube 284 | sampler1DShadow 285 | sampler2DShadow 286 | samplerCubeShadow 287 | sampler1DArray 288 | sampler2DArray 289 | sampler1DArrayShadow 290 | sampler2DArrayShadow 291 | sampler1D 292 | isampler2D 293 | isampler3D 294 | isamplerCube 295 | isampler1DArray 296 | isampler2DArray 297 | usampler1D 298 | usampler2D 299 | usampler3D 300 | usamplerCube 301 | usampler1DArray 302 | usampler2DArray 303 | sampler2DRect 304 | sampler2DRectShadow 305 | isampler2DRect 306 | usampler2DRect 307 | samplerBuffer 308 | isamplerBuffer 309 | usamplerBuffer 310 | sampler2DMS 311 | isampler2DMS 312 | usampler2DMS 313 | sampler2DMSArray 314 | isampler2DMSArray 315 | usampler2DMSArray 316 | samplerCubeArray 317 | samplerCubeArrayShadow 318 | isamplerCubeArray 319 | usamplerCubeArray 320 | image1D 321 | iimage1D 322 | uimage1D 323 | image2D 324 | iimage2D 325 | uimage2D 326 | image3D 327 | iimage3D 328 | uimage3D 329 | image2DRect 330 | iimage2DRect 331 | uimage2DRect 332 | imageCube 333 | iimageCube 334 | uimageCube 335 | imageBuffer 336 | iimageBuffer 337 | uimageBuffer 338 | image1DArray 339 | iimage1DArray 340 | uimage1DArray 341 | image2DArray 342 | iimage2DArray 343 | uimage2DArray 344 | imageCubeArray 345 | iimageCubeArray 346 | uimageCubeArray 347 | image2DMS 348 | iimage2DMS 349 | uimage2DMS 350 | image2DMSArray 351 | iimage2DMSArray 352 | uimage2DMSArray 353 | 354 | 355 | -------------------------------------------------------------------------------- /resources/languages/java.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | abstract 5 | assert 6 | break 7 | case 8 | catch 9 | const 10 | continue 11 | default 12 | do 13 | else 14 | extends 15 | final 16 | finally 17 | for 18 | if 19 | goto 20 | implements 21 | import 22 | instanceof 23 | native 24 | new 25 | package 26 | private 27 | protected 28 | public 29 | return 30 | static 31 | strictfp 32 | super 33 | switch 34 | synchronized 35 | this 36 | throw 37 | throws 38 | transient 39 | try 40 | volatile 41 | while 42 | 43 | true 44 | false 45 | null 46 | 47 | 48 | 49 | boolean 50 | byte 51 | char 52 | class 53 | double 54 | enum 55 | float 56 | int 57 | interface 58 | long 59 | short 60 | void 61 | 62 | 63 | -------------------------------------------------------------------------------- /resources/languages/js.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | break 5 | case 6 | class 7 | catch 8 | const 9 | continue 10 | debugger 11 | default 12 | delete 13 | do 14 | else 15 | export 16 | extends 17 | finally 18 | for 19 | function 20 | if 21 | import 22 | in 23 | instanceof 24 | let 25 | new 26 | return 27 | super 28 | switch 29 | this 30 | throw 31 | try 32 | typeof 33 | var 34 | void 35 | while 36 | with 37 | yield 38 | 39 | -------------------------------------------------------------------------------- /resources/languages/lua.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | break 5 | do 6 | else 7 | elseif 8 | end 9 | false 10 | for 11 | function 12 | if 13 | in 14 | repeat 15 | return 16 | then 17 | until 18 | while 19 | 20 | 21 | local 22 | nil 23 | boolean 24 | number 25 | string 26 | function 27 | userdata 28 | thread 29 | table 30 | 31 | 32 | \+ 33 | \- 34 | \* 35 | \/ 36 | \% 37 | \^ 38 | == 39 | ~= 40 | > 41 | < 42 | >= 43 | <= 44 | and 45 | or 46 | not 47 | \.\. 48 | \# 49 | 50 | 51 | -------------------------------------------------------------------------------- /resources/languages/python.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | break 5 | continue 6 | do 7 | for 8 | while 9 | if 10 | else 11 | def 12 | import 13 | return 14 | class 15 | in 16 | is 17 | not 18 | or 19 | and 20 | enumerate 21 | 22 | 23 | min 24 | max 25 | len 26 | 27 | 28 | float 29 | int 30 | bool 31 | True 32 | False 33 | str 34 | unicode 35 | byte 36 | set 37 | dict 38 | 39 | 40 | -------------------------------------------------------------------------------- /resources/qcodeeditor_resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | default_style.xml 4 | languages/glsl.xml 5 | languages/cpp.xml 6 | languages/java.xml 7 | languages/lua.xml 8 | languages/python.xml 9 | languages/js.xml 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/internal/QCXXHighlighter.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | #include 4 | #include 5 | 6 | // Qt 7 | #include 8 | 9 | QCXXHighlighter::QCXXHighlighter(QTextDocument *document) 10 | : QStyleSyntaxHighlighter(document), m_highlightRules(), 11 | m_includePattern(QRegularExpression(R"(^\s*#\s*include\s*([<"][^:?"<>\|]+[">]))")), 12 | m_functionPattern(QRegularExpression( 13 | R"(\b([_a-zA-Z][_a-zA-Z0-9]*\s+)?((?:[_a-zA-Z][_a-zA-Z0-9]*\s*::\s*)*[_a-zA-Z][_a-zA-Z0-9]*)(?=\s*\())")), 14 | m_defTypePattern(QRegularExpression(R"(\b([_a-zA-Z][_a-zA-Z0-9]*)\s+[_a-zA-Z][_a-zA-Z0-9]*\s*[;=])")), 15 | m_commentStartPattern(QRegularExpression(R"(/\*)")), m_commentEndPattern(QRegularExpression(R"(\*/)")) 16 | { 17 | Q_INIT_RESOURCE(qcodeeditor_resources); 18 | QFile fl(":/languages/cpp.xml"); 19 | 20 | if (!fl.open(QIODevice::ReadOnly)) 21 | { 22 | return; 23 | } 24 | 25 | QLanguage language(&fl); 26 | 27 | if (!language.isLoaded()) 28 | { 29 | return; 30 | } 31 | 32 | auto keys = language.keys(); 33 | for (auto &&key : keys) 34 | { 35 | auto names = language.names(key); 36 | for (auto &&name : names) 37 | { 38 | m_highlightRules.append({QRegularExpression(QString(R"(\b%1\b)").arg(name)), key}); 39 | } 40 | } 41 | 42 | // Numbers 43 | m_highlightRules.append( 44 | {QRegularExpression( 45 | R"((?<=\b|\s|^)(?i)(?:(?:(?:(?:(?:\d+(?:'\d+)*)?\.(?:\d+(?:'\d+)*)(?:e[+-]?(?:\d+(?:'\d+)*))?)|(?:(?:\d+(?:'\d+)*)\.(?:e[+-]?(?:\d+(?:'\d+)*))?)|(?:(?:\d+(?:'\d+)*)(?:e[+-]?(?:\d+(?:'\d+)*)))|(?:0x(?:[0-9a-f]+(?:'[0-9a-f]+)*)?\.(?:[0-9a-f]+(?:'[0-9a-f]+)*)(?:p[+-]?(?:\d+(?:'\d+)*)))|(?:0x(?:[0-9a-f]+(?:'[0-9a-f]+)*)\.?(?:p[+-]?(?:\d+(?:'\d+)*))))[lf]?)|(?:(?:(?:[1-9]\d*(?:'\d+)*)|(?:0[0-7]*(?:'[0-7]+)*)|(?:0x[0-9a-f]+(?:'[0-9a-f]+)*)|(?:0b[01]+(?:'[01]+)*))(?:u?l{0,2}|l{0,2}u?)))(?=\b|\s|$))"), 46 | "Number"}); 47 | 48 | // Strings 49 | m_highlightRules.append({QRegularExpression(R"("[^\n"]*")"), "String"}); 50 | 51 | // Define 52 | m_highlightRules.append({QRegularExpression(R"(#[a-zA-Z_]+)"), "Preprocessor"}); 53 | 54 | // Single line 55 | m_highlightRules.append({QRegularExpression(R"(//[^\n]*)"), "Comment"}); 56 | 57 | // Comment sequences for toggling support 58 | m_commentLineSequence = "//"; 59 | m_startCommentBlockSequence = "/*"; 60 | m_endCommentBlockSequence = "*/"; 61 | } 62 | 63 | void QCXXHighlighter::highlightBlock(const QString &text) 64 | { 65 | // Checking for include 66 | { 67 | auto matchIterator = m_includePattern.globalMatch(text); 68 | 69 | while (matchIterator.hasNext()) 70 | { 71 | auto match = matchIterator.next(); 72 | 73 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat("Preprocessor")); 74 | 75 | setFormat(match.capturedStart(1), match.capturedLength(1), syntaxStyle()->getFormat("String")); 76 | } 77 | } 78 | // Checking for function 79 | { 80 | auto matchIterator = m_functionPattern.globalMatch(text); 81 | 82 | while (matchIterator.hasNext()) 83 | { 84 | auto match = matchIterator.next(); 85 | 86 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat("Type")); 87 | 88 | setFormat(match.capturedStart(2), match.capturedLength(2), syntaxStyle()->getFormat("Function")); 89 | } 90 | } 91 | { 92 | auto matchIterator = m_defTypePattern.globalMatch(text); 93 | 94 | while (matchIterator.hasNext()) 95 | { 96 | auto match = matchIterator.next(); 97 | 98 | setFormat(match.capturedStart(1), match.capturedLength(1), syntaxStyle()->getFormat("Type")); 99 | } 100 | } 101 | 102 | for (auto &rule : m_highlightRules) 103 | { 104 | auto matchIterator = rule.pattern.globalMatch(text); 105 | 106 | while (matchIterator.hasNext()) 107 | { 108 | auto match = matchIterator.next(); 109 | 110 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat(rule.formatName)); 111 | } 112 | } 113 | 114 | setCurrentBlockState(0); 115 | 116 | int startIndex = 0; 117 | if (previousBlockState() != 1) 118 | { 119 | startIndex = text.indexOf(m_commentStartPattern); 120 | } 121 | 122 | while (startIndex >= 0) 123 | { 124 | auto match = m_commentEndPattern.match(text, startIndex); 125 | 126 | int endIndex = match.capturedStart(); 127 | int commentLength = 0; 128 | 129 | if (endIndex == -1) 130 | { 131 | setCurrentBlockState(1); 132 | commentLength = text.length() - startIndex; 133 | } 134 | else 135 | { 136 | commentLength = endIndex - startIndex + match.capturedLength(); 137 | } 138 | 139 | setFormat(startIndex, commentLength, syntaxStyle()->getFormat("Comment")); 140 | startIndex = text.indexOf(m_commentStartPattern, startIndex + commentLength); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/internal/QCodeEditor.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // Qt 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | QCodeEditor::QCodeEditor(QWidget *widget) 28 | : QTextEdit(widget), m_highlighter(nullptr), m_syntaxStyle(nullptr), m_lineNumberArea(new QLineNumberArea(this)), 29 | m_completer(nullptr), m_autoIndentation(true), m_replaceTab(true), m_extraBottomMargin(true), 30 | m_tabReplace(QString(4, ' ')), extra1(), extra2(), extra_squiggles(), m_squiggler(), 31 | m_parentheses({{'(', ')'}, {'{', '}'}, {'[', ']'}, {'\"', '\"'}, {'\'', '\''}}) 32 | { 33 | initFont(); 34 | performConnections(); 35 | setMouseTracking(true); 36 | 37 | setSyntaxStyle(QSyntaxStyle::defaultStyle()); 38 | } 39 | 40 | void QCodeEditor::initFont() 41 | { 42 | auto fnt = QFontDatabase::systemFont(QFontDatabase::FixedFont); 43 | fnt.setFixedPitch(true); 44 | fnt.setPointSize(10); 45 | 46 | setFont(fnt); 47 | } 48 | 49 | void QCodeEditor::performConnections() 50 | { 51 | connect(document(), &QTextDocument::blockCountChanged, this, &QCodeEditor::updateLineNumberAreaWidth); 52 | connect(document(), &QTextDocument::blockCountChanged, this, &QCodeEditor::updateBottomMargin); 53 | 54 | connect(verticalScrollBar(), &QScrollBar::valueChanged, this, [this](int) { m_lineNumberArea->update(); }); 55 | 56 | connect(this, &QTextEdit::cursorPositionChanged, this, &QCodeEditor::updateExtraSelection1); 57 | connect(this, &QTextEdit::selectionChanged, this, &QCodeEditor::updateExtraSelection2); 58 | } 59 | 60 | void QCodeEditor::setHighlighter(QStyleSyntaxHighlighter *highlighter) 61 | { 62 | if (m_highlighter) 63 | { 64 | m_highlighter->setDocument(nullptr); 65 | } 66 | 67 | m_highlighter = highlighter; 68 | 69 | if (m_highlighter) 70 | { 71 | m_highlighter->setSyntaxStyle(m_syntaxStyle); 72 | m_highlighter->setDocument(document()); 73 | } 74 | } 75 | 76 | void QCodeEditor::setSyntaxStyle(QSyntaxStyle *style) 77 | { 78 | m_syntaxStyle = style; 79 | 80 | m_lineNumberArea->setSyntaxStyle(m_syntaxStyle); 81 | 82 | if (m_highlighter) 83 | { 84 | m_highlighter->setSyntaxStyle(m_syntaxStyle); 85 | } 86 | 87 | updateStyle(); 88 | } 89 | 90 | void QCodeEditor::updateStyle() 91 | { 92 | if (m_highlighter) 93 | { 94 | m_highlighter->rehighlight(); 95 | } 96 | 97 | if (m_syntaxStyle) 98 | { 99 | QString backgroundColor = m_syntaxStyle->getFormat("Text").background().color().name(); 100 | QString textColor = m_syntaxStyle->getFormat("Text").foreground().color().name(); 101 | QString selectionBackground = m_syntaxStyle->getFormat("Selection").background().color().name(); 102 | 103 | setStyleSheet(QString("QTextEdit { background-color: %1; selection-background-color: %2; color: %3; }") 104 | .arg(backgroundColor, selectionBackground, textColor)); 105 | } 106 | 107 | updateExtraSelection1(); 108 | updateExtraSelection2(); 109 | } 110 | 111 | void QCodeEditor::resizeEvent(QResizeEvent *e) 112 | { 113 | QTextEdit::resizeEvent(e); 114 | 115 | updateLineGeometry(); 116 | updateBottomMargin(); 117 | } 118 | 119 | void QCodeEditor::changeEvent(QEvent *e) 120 | { 121 | QTextEdit::changeEvent(e); 122 | if (e->type() == QEvent::FontChange) 123 | updateBottomMargin(); 124 | } 125 | 126 | void QCodeEditor::wheelEvent(QWheelEvent *e) 127 | { 128 | if (e->modifiers() == Qt::ControlModifier) 129 | { 130 | const auto sizes = QFontDatabase::standardSizes(); 131 | if (sizes.isEmpty()) 132 | { 133 | qDebug() << "QFontDatabase::standardSizes() is empty"; 134 | return; 135 | } 136 | int newSize = font().pointSize(); 137 | if (e->angleDelta().y() > 0) 138 | newSize = qMin(newSize + 1, sizes.last()); 139 | else if (e->angleDelta().y() < 0) 140 | newSize = qMax(newSize - 1, sizes.first()); 141 | if (newSize != font().pointSize()) 142 | { 143 | QFont newFont = font(); 144 | newFont.setPointSize(newSize); 145 | setFont(newFont); 146 | Q_EMIT fontChanged(newFont); 147 | } 148 | } 149 | else 150 | QTextEdit::wheelEvent(e); 151 | } 152 | 153 | void QCodeEditor::updateLineGeometry() 154 | { 155 | QRect cr = contentsRect(); 156 | m_lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), m_lineNumberArea->sizeHint().width(), cr.height())); 157 | } 158 | 159 | void QCodeEditor::updateBottomMargin() 160 | { 161 | auto doc = document(); 162 | if (doc->blockCount() > 1) 163 | { 164 | // calling QTextFrame::setFrameFormat with an empty document makes the application crash 165 | auto rf = doc->rootFrame(); 166 | auto format = rf->frameFormat(); 167 | int documentMargin = doc->documentMargin(); 168 | int bottomMargin = m_extraBottomMargin 169 | ? qMax(documentMargin, viewport()->height() - fontMetrics().height()) - documentMargin 170 | : documentMargin; 171 | if (format.bottomMargin() != bottomMargin) 172 | { 173 | format.setBottomMargin(bottomMargin); 174 | rf->setFrameFormat(format); 175 | } 176 | } 177 | } 178 | 179 | void QCodeEditor::updateLineNumberAreaWidth(int) 180 | { 181 | setViewportMargins(m_lineNumberArea->sizeHint().width(), 0, 0, 0); 182 | } 183 | 184 | void QCodeEditor::updateLineNumberArea(QRect rect) 185 | { 186 | m_lineNumberArea->update(0, rect.y(), m_lineNumberArea->sizeHint().width(), rect.height()); 187 | updateLineGeometry(); 188 | 189 | if (rect.contains(viewport()->rect())) 190 | { 191 | updateLineNumberAreaWidth(0); 192 | } 193 | } 194 | 195 | void QCodeEditor::updateExtraSelection1() 196 | { 197 | extra1.clear(); 198 | 199 | highlightCurrentLine(); 200 | highlightParenthesis(); 201 | 202 | setExtraSelections(extra1 + extra2 + extra_squiggles); 203 | } 204 | 205 | void QCodeEditor::updateExtraSelection2() 206 | { 207 | extra2.clear(); 208 | 209 | highlightOccurrences(); 210 | 211 | setExtraSelections(extra1 + extra2 + extra_squiggles); 212 | } 213 | 214 | void QCodeEditor::indent() 215 | { 216 | addInEachLineOfSelection(QRegularExpression("^"), m_replaceTab ? m_tabReplace : "\t"); 217 | } 218 | 219 | void QCodeEditor::unindent() 220 | { 221 | removeInEachLineOfSelection(QRegularExpression("^(\t| {1," + QString::number(tabReplaceSize()) + "})"), true); 222 | } 223 | 224 | void QCodeEditor::swapLineUp() 225 | { 226 | auto cursor = textCursor(); 227 | auto lines = toPlainText().remove('\r').split('\n'); 228 | int selectionStart = cursor.selectionStart(); 229 | int selectionEnd = cursor.selectionEnd(); 230 | bool cursorAtEnd = cursor.position() == selectionEnd; 231 | cursor.setPosition(selectionStart); 232 | int lineStart = cursor.blockNumber(); 233 | cursor.setPosition(selectionEnd); 234 | int lineEnd = cursor.blockNumber(); 235 | 236 | if (lineStart == 0) 237 | return; 238 | selectionStart -= lines[lineStart - 1].length() + 1; 239 | selectionEnd -= lines[lineStart - 1].length() + 1; 240 | lines.move(lineStart - 1, lineEnd); 241 | 242 | cursor.select(QTextCursor::Document); 243 | cursor.insertText(lines.join('\n')); 244 | 245 | if (cursorAtEnd) 246 | { 247 | cursor.setPosition(selectionStart); 248 | cursor.setPosition(selectionEnd, QTextCursor::KeepAnchor); 249 | } 250 | else 251 | { 252 | cursor.setPosition(selectionEnd); 253 | cursor.setPosition(selectionStart, QTextCursor::KeepAnchor); 254 | } 255 | 256 | setTextCursor(cursor); 257 | } 258 | 259 | void QCodeEditor::swapLineDown() 260 | { 261 | auto cursor = textCursor(); 262 | auto lines = toPlainText().remove('\r').split('\n'); 263 | int selectionStart = cursor.selectionStart(); 264 | int selectionEnd = cursor.selectionEnd(); 265 | bool cursorAtEnd = cursor.position() == selectionEnd; 266 | cursor.setPosition(selectionStart); 267 | int lineStart = cursor.blockNumber(); 268 | cursor.setPosition(selectionEnd); 269 | int lineEnd = cursor.blockNumber(); 270 | 271 | if (lineEnd == document()->blockCount() - 1) 272 | return; 273 | selectionStart += lines[lineEnd + 1].length() + 1; 274 | selectionEnd += lines[lineEnd + 1].length() + 1; 275 | lines.move(lineEnd + 1, lineStart); 276 | 277 | cursor.select(QTextCursor::Document); 278 | cursor.insertText(lines.join('\n')); 279 | 280 | if (cursorAtEnd) 281 | { 282 | cursor.setPosition(selectionStart); 283 | cursor.setPosition(selectionEnd, QTextCursor::KeepAnchor); 284 | } 285 | else 286 | { 287 | cursor.setPosition(selectionEnd); 288 | cursor.setPosition(selectionStart, QTextCursor::KeepAnchor); 289 | } 290 | 291 | setTextCursor(cursor); 292 | } 293 | 294 | void QCodeEditor::deleteLine() 295 | { 296 | auto cursor = textCursor(); 297 | int selectionStart = cursor.selectionStart(); 298 | int selectionEnd = cursor.selectionEnd(); 299 | cursor.setPosition(selectionStart); 300 | int lineStart = cursor.blockNumber(); 301 | cursor.setPosition(selectionEnd); 302 | int lineEnd = cursor.blockNumber(); 303 | int columnNumber = textCursor().columnNumber(); 304 | cursor.movePosition(QTextCursor::Start); 305 | if (lineEnd == document()->blockCount() - 1) 306 | { 307 | if (lineStart == 0) 308 | { 309 | cursor.select(QTextCursor::Document); 310 | } 311 | else 312 | { 313 | cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, lineStart - 1); 314 | cursor.movePosition(QTextCursor::EndOfBlock); 315 | cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); 316 | } 317 | } 318 | else 319 | { 320 | cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, lineStart); 321 | cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor, lineEnd - lineStart + 1); 322 | } 323 | cursor.removeSelectedText(); 324 | cursor.movePosition(QTextCursor::StartOfBlock); 325 | cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, 326 | qMin(columnNumber, cursor.block().text().length())); 327 | setTextCursor(cursor); 328 | } 329 | 330 | void QCodeEditor::duplicate() 331 | { 332 | auto cursor = textCursor(); 333 | if (cursor.hasSelection()) // duplicate the selection 334 | { 335 | auto text = cursor.selectedText(); 336 | bool cursorAtEnd = cursor.selectionEnd() == cursor.position(); 337 | cursor.insertText(text + text); 338 | if (cursorAtEnd) 339 | { 340 | cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, text.length()); 341 | cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, text.length()); 342 | } 343 | else 344 | { 345 | cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, text.length()); 346 | } 347 | } 348 | else // duplicate the current line 349 | { 350 | int column = cursor.columnNumber(); 351 | cursor.movePosition(QTextCursor::StartOfBlock); 352 | cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); 353 | auto text = cursor.selectedText(); 354 | cursor.insertText(text + "\n" + text); 355 | cursor.movePosition(QTextCursor::StartOfBlock); 356 | cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, column); 357 | } 358 | setTextCursor(cursor); 359 | } 360 | 361 | void QCodeEditor::toggleComment() 362 | { 363 | if (m_highlighter == nullptr) 364 | return; 365 | QString comment = m_highlighter->commentLineSequence(); 366 | if (comment.isEmpty()) 367 | return; 368 | 369 | if (!removeInEachLineOfSelection(QRegularExpression("^\\s*(" + comment + " ?)"), false)) 370 | { 371 | addInEachLineOfSelection(QRegularExpression("\\S|^\\s*$"), comment + " "); 372 | } 373 | } 374 | 375 | void QCodeEditor::toggleBlockComment() 376 | { 377 | if (m_highlighter == nullptr) 378 | return; 379 | QString commentStart = m_highlighter->startCommentBlockSequence(); 380 | QString commentEnd = m_highlighter->endCommentBlockSequence(); 381 | 382 | if (commentStart.isEmpty() || commentEnd.isEmpty()) 383 | return; 384 | 385 | auto cursor = textCursor(); 386 | int startPos = cursor.selectionStart(); 387 | int endPos = cursor.selectionEnd(); 388 | bool cursorAtEnd = cursor.position() == endPos; 389 | auto text = cursor.selectedText(); 390 | int pos1, pos2; 391 | if (text.indexOf(commentStart) == 0 && text.length() >= commentStart.length() + commentEnd.length() && 392 | text.lastIndexOf(commentEnd) + commentEnd.length() == text.length()) 393 | { 394 | insertPlainText(text.mid(commentStart.length(), text.length() - commentStart.length() - commentEnd.length())); 395 | pos1 = startPos; 396 | pos2 = endPos - commentStart.length() - commentEnd.length(); 397 | } 398 | else 399 | { 400 | insertPlainText(commentStart + text + commentEnd); 401 | pos1 = startPos; 402 | pos2 = endPos + commentStart.length() + commentEnd.length(); 403 | } 404 | if (cursorAtEnd) 405 | { 406 | cursor.setPosition(pos1); 407 | cursor.setPosition(pos2, QTextCursor::KeepAnchor); 408 | } 409 | else 410 | { 411 | cursor.setPosition(pos2); 412 | cursor.setPosition(pos1, QTextCursor::KeepAnchor); 413 | } 414 | setTextCursor(cursor); 415 | } 416 | 417 | void QCodeEditor::highlightParenthesis() 418 | { 419 | auto currentSymbol = charUnderCursor(); 420 | auto prevSymbol = charUnderCursor(-1); 421 | 422 | for (auto &p : m_parentheses) 423 | { 424 | int direction; 425 | 426 | QChar counterSymbol; 427 | QChar activeSymbol; 428 | auto position = textCursor().position(); 429 | 430 | if (p.left == currentSymbol) 431 | { 432 | direction = 1; 433 | counterSymbol = p.right; 434 | activeSymbol = currentSymbol; 435 | } 436 | else if (p.right == prevSymbol) 437 | { 438 | direction = -1; 439 | counterSymbol = p.left; 440 | activeSymbol = prevSymbol; 441 | position--; 442 | } 443 | else 444 | { 445 | continue; 446 | } 447 | 448 | auto counter = 1; 449 | 450 | while (counter != 0 && position > 0 && position < (document()->characterCount() - 1)) 451 | { 452 | // Moving position 453 | position += direction; 454 | 455 | auto character = document()->characterAt(position); 456 | // Checking symbol under position 457 | if (character == activeSymbol) 458 | { 459 | ++counter; 460 | } 461 | else if (character == counterSymbol) 462 | { 463 | --counter; 464 | } 465 | } 466 | 467 | auto format = m_syntaxStyle->getFormat("Parentheses"); 468 | 469 | // Found 470 | if (counter == 0) 471 | { 472 | ExtraSelection selection{}; 473 | 474 | auto directionEnum = direction < 0 ? QTextCursor::MoveOperation::Left : QTextCursor::MoveOperation::Right; 475 | 476 | selection.format = format; 477 | selection.cursor = textCursor(); 478 | selection.cursor.clearSelection(); 479 | selection.cursor.movePosition(directionEnum, QTextCursor::MoveMode::MoveAnchor, 480 | qAbs(textCursor().position() - position)); 481 | 482 | selection.cursor.movePosition(QTextCursor::MoveOperation::Right, QTextCursor::MoveMode::KeepAnchor, 1); 483 | 484 | extra1.append(selection); 485 | 486 | selection.cursor = textCursor(); 487 | selection.cursor.clearSelection(); 488 | selection.cursor.movePosition(directionEnum, QTextCursor::MoveMode::KeepAnchor, 1); 489 | 490 | extra1.append(selection); 491 | } 492 | 493 | break; 494 | } 495 | } 496 | 497 | void QCodeEditor::highlightCurrentLine() 498 | { 499 | if (!isReadOnly()) 500 | { 501 | QTextEdit::ExtraSelection selection{}; 502 | 503 | selection.format = m_syntaxStyle->getFormat("CurrentLine"); 504 | selection.format.setForeground(QBrush()); 505 | selection.format.setProperty(QTextFormat::FullWidthSelection, true); 506 | selection.cursor = textCursor(); 507 | selection.cursor.clearSelection(); 508 | 509 | extra1.append(selection); 510 | } 511 | } 512 | 513 | void QCodeEditor::highlightOccurrences() 514 | { 515 | auto cursor = textCursor(); 516 | if (cursor.hasSelection()) 517 | { 518 | auto text = cursor.selectedText(); 519 | if (QRegularExpression( 520 | R"((?:[_a-zA-Z][_a-zA-Z0-9]*)|(?<=\b|\s|^)(?i)(?:(?:(?:(?:(?:\d+(?:'\d+)*)?\.(?:\d+(?:'\d+)*)(?:e[+-]?(?:\d+(?:'\d+)*))?)|(?:(?:\d+(?:'\d+)*)\.(?:e[+-]?(?:\d+(?:'\d+)*))?)|(?:(?:\d+(?:'\d+)*)(?:e[+-]?(?:\d+(?:'\d+)*)))|(?:0x(?:[0-9a-f]+(?:'[0-9a-f]+)*)?\.(?:[0-9a-f]+(?:'[0-9a-f]+)*)(?:p[+-]?(?:\d+(?:'\d+)*)))|(?:0x(?:[0-9a-f]+(?:'[0-9a-f]+)*)\.?(?:p[+-]?(?:\d+(?:'\d+)*))))[lf]?)|(?:(?:(?:[1-9]\d*(?:'\d+)*)|(?:0[0-7]*(?:'[0-7]+)*)|(?:0x[0-9a-f]+(?:'[0-9a-f]+)*)|(?:0b[01]+(?:'[01]+)*))(?:u?l{0,2}|l{0,2}u?)))(?=\b|\s|$))") 521 | .match(text) 522 | .captured() == text) 523 | { 524 | auto doc = document(); 525 | cursor = doc->find(text, 0, QTextDocument::FindWholeWords | QTextDocument::FindCaseSensitively); 526 | while (!cursor.isNull()) 527 | { 528 | if (cursor != textCursor()) 529 | { 530 | QTextEdit::ExtraSelection e; 531 | e.cursor = cursor; 532 | e.format.setBackground(m_syntaxStyle->getFormat("Selection").background()); 533 | extra2.push_back(e); 534 | } 535 | cursor = doc->find(text, cursor, QTextDocument::FindWholeWords | QTextDocument::FindCaseSensitively); 536 | } 537 | } 538 | } 539 | } 540 | 541 | void QCodeEditor::paintEvent(QPaintEvent *e) 542 | { 543 | updateLineNumberArea(e->rect()); 544 | QTextEdit::paintEvent(e); 545 | } 546 | 547 | int QCodeEditor::getFirstVisibleBlock() 548 | { 549 | // Detect the first block for which bounding rect - once translated 550 | // in absolute coordinated - is contained by the editor's text area 551 | 552 | // Costly way of doing but since "blockBoundingGeometry(...)" doesn't 553 | // exists for "QTextEdit"... 554 | 555 | QTextCursor curs = QTextCursor(document()); 556 | curs.movePosition(QTextCursor::Start); 557 | for (int i = 0; i < document()->blockCount(); ++i) 558 | { 559 | QTextBlock block = curs.block(); 560 | 561 | QRect r1 = viewport()->geometry(); 562 | QRect r2 = document() 563 | ->documentLayout() 564 | ->blockBoundingRect(block) 565 | .translated(viewport()->geometry().x(), 566 | viewport()->geometry().y() - verticalScrollBar()->sliderPosition()) 567 | .toRect(); 568 | 569 | if (r1.intersects(r2)) 570 | { 571 | return i; 572 | } 573 | 574 | curs.movePosition(QTextCursor::NextBlock); 575 | } 576 | 577 | return 0; 578 | } 579 | 580 | bool QCodeEditor::proceedCompleterBegin(QKeyEvent *e) 581 | { 582 | if (m_completer && m_completer->popup()->isVisible()) 583 | { 584 | switch (e->key()) 585 | { 586 | case Qt::Key_Enter: 587 | case Qt::Key_Return: 588 | case Qt::Key_Escape: 589 | case Qt::Key_Tab: 590 | case Qt::Key_Backtab: 591 | e->ignore(); 592 | return true; // let the completer do default behavior 593 | default: 594 | break; 595 | } 596 | } 597 | 598 | // todo: Replace with modifiable QShortcut 599 | auto isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_Space); 600 | 601 | return !(!m_completer || !isShortcut); 602 | } 603 | 604 | void QCodeEditor::proceedCompleterEnd(QKeyEvent *e) 605 | { 606 | auto ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier); 607 | 608 | if (!m_completer || (ctrlOrShift && e->text().isEmpty()) || e->key() == Qt::Key_Delete) 609 | { 610 | return; 611 | } 612 | 613 | static QString eow(R"(~!@#$%^&*()_+{}|:"<>?,./;'[]\-=)"); 614 | 615 | auto isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_Space); 616 | auto completionPrefix = wordUnderCursor(); 617 | 618 | if (!isShortcut && (e->text().isEmpty() || completionPrefix.length() < 2 || eow.contains(e->text().right(1)))) 619 | { 620 | m_completer->popup()->hide(); 621 | return; 622 | } 623 | 624 | if (completionPrefix != m_completer->completionPrefix()) 625 | { 626 | m_completer->setCompletionPrefix(completionPrefix); 627 | m_completer->popup()->setCurrentIndex(m_completer->completionModel()->index(0, 0)); 628 | } 629 | 630 | auto cursRect = cursorRect(); 631 | cursRect.setWidth(m_completer->popup()->sizeHintForColumn(0) + 632 | m_completer->popup()->verticalScrollBar()->sizeHint().width()); 633 | 634 | m_completer->complete(cursRect); 635 | } 636 | 637 | void QCodeEditor::keyPressEvent(QKeyEvent *e) 638 | { 639 | auto completerSkip = proceedCompleterBegin(e); 640 | 641 | if (!completerSkip) 642 | { 643 | if ((e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) && e->modifiers() != Qt::NoModifier) 644 | { 645 | QKeyEvent pureEnter(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier); 646 | if (e->modifiers() == Qt::ControlModifier) 647 | { 648 | moveCursor(QTextCursor::EndOfBlock); 649 | keyPressEvent(&pureEnter); 650 | return; 651 | } 652 | else if (e->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) 653 | { 654 | if (textCursor().blockNumber() == 0) 655 | { 656 | moveCursor(QTextCursor::StartOfBlock); 657 | insertPlainText("\n"); 658 | moveCursor(QTextCursor::PreviousBlock); 659 | moveCursor(QTextCursor::EndOfBlock); 660 | } 661 | else 662 | { 663 | moveCursor(QTextCursor::PreviousBlock); 664 | moveCursor(QTextCursor::EndOfBlock); 665 | keyPressEvent(&pureEnter); 666 | } 667 | return; 668 | } 669 | else if (e->modifiers() == Qt::ShiftModifier) 670 | { 671 | keyPressEvent(&pureEnter); 672 | return; 673 | } 674 | } 675 | 676 | if (e->key() == Qt::Key_Tab && e->modifiers() == Qt::NoModifier) 677 | { 678 | if (textCursor().hasSelection()) 679 | { 680 | indent(); 681 | return; 682 | } 683 | 684 | auto c = charUnderCursor(); 685 | for (auto p : qAsConst(m_parentheses)) 686 | { 687 | if (p.tabJumpOut && c == p.right) 688 | { 689 | moveCursor(QTextCursor::NextCharacter); 690 | return; 691 | } 692 | } 693 | 694 | if (m_replaceTab) 695 | { 696 | insertPlainText(m_tabReplace); 697 | return; 698 | } 699 | } 700 | 701 | if (e->key() == Qt::Key_Backtab && e->modifiers() == Qt::ShiftModifier) 702 | { 703 | unindent(); 704 | return; 705 | } 706 | 707 | if (e->key() == Qt::Key_Delete && e->modifiers() == Qt::ShiftModifier) 708 | { 709 | deleteLine(); 710 | return; 711 | } 712 | 713 | // Auto indentation 714 | 715 | QString indentationSpaces = QRegularExpression("^\\s*") 716 | .match(document()->findBlockByNumber(textCursor().blockNumber()).text()) 717 | .captured(); 718 | 719 | // Have Qt Edior like behaviour, if {|} and enter is pressed indent the two 720 | // parenthesis 721 | if (m_autoIndentation && (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) && 722 | e->modifiers() == Qt::NoModifier && charUnderCursor(-1) == '{' && charUnderCursor() == '}') 723 | { 724 | insertPlainText("\n" + indentationSpaces + (m_replaceTab ? m_tabReplace : "\t") + "\n" + indentationSpaces); 725 | 726 | for (int i = 0; i <= indentationSpaces.length(); ++i) 727 | moveCursor(QTextCursor::MoveOperation::Left); 728 | 729 | return; 730 | } 731 | 732 | // Auto-indent for single "{" without "}" 733 | if (m_autoIndentation && (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) && 734 | e->modifiers() == Qt::NoModifier && charUnderCursor(-1) == '{') 735 | { 736 | insertPlainText("\n" + indentationSpaces + (m_replaceTab ? m_tabReplace : "\t")); 737 | setTextCursor(textCursor()); // scroll to the cursor 738 | return; 739 | } 740 | 741 | if (e->key() == Qt::Key_Backspace && e->modifiers() == Qt::NoModifier && !textCursor().hasSelection()) 742 | { 743 | auto pre = charUnderCursor(-1); 744 | auto nxt = charUnderCursor(); 745 | for (auto p : qAsConst(m_parentheses)) 746 | { 747 | if (p.autoRemove && p.left == pre && p.right == nxt) 748 | { 749 | auto cursor = textCursor(); 750 | cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor); 751 | cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 2); 752 | cursor.removeSelectedText(); 753 | setTextCursor(textCursor()); // scroll to the cursor 754 | return; 755 | } 756 | } 757 | 758 | if (textCursor().columnNumber() <= indentationSpaces.length() && textCursor().columnNumber() >= 1 && 759 | !m_tabReplace.isEmpty()) 760 | { 761 | auto cursor = textCursor(); 762 | int realColumn = 0, newIndentLength = 0; 763 | for (int i = 0; i < cursor.columnNumber(); ++i) 764 | { 765 | if (indentationSpaces[i] != '\t') 766 | ++realColumn; 767 | else 768 | { 769 | realColumn = 770 | (realColumn + m_tabReplace.length()) / m_tabReplace.length() * m_tabReplace.length(); 771 | } 772 | if (realColumn % m_tabReplace.length() == 0 && i < cursor.columnNumber() - 1) 773 | { 774 | newIndentLength = i + 1; 775 | } 776 | } 777 | cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, 778 | cursor.columnNumber() - newIndentLength); 779 | cursor.removeSelectedText(); 780 | setTextCursor(textCursor()); // scroll to the cursor 781 | return; 782 | } 783 | } 784 | 785 | for (auto p : qAsConst(m_parentheses)) 786 | { 787 | if (p.autoComplete) 788 | { 789 | auto cursor = textCursor(); 790 | if (cursor.hasSelection()) 791 | { 792 | if (p.left == e->text()) 793 | { 794 | // Add parentheses for selection 795 | int startPos = cursor.selectionStart(); 796 | int endPos = cursor.selectionEnd(); 797 | bool cursorAtEnd = cursor.position() == endPos; 798 | auto text = p.left + cursor.selectedText() + p.right; 799 | insertPlainText(text); 800 | if (cursorAtEnd) 801 | { 802 | cursor.setPosition(startPos + 1); 803 | cursor.setPosition(endPos + 1, QTextCursor::KeepAnchor); 804 | } 805 | else 806 | { 807 | cursor.setPosition(endPos + 1); 808 | cursor.setPosition(startPos + 1, QTextCursor::KeepAnchor); 809 | } 810 | setTextCursor(cursor); 811 | return; 812 | } 813 | } 814 | else 815 | { 816 | if (p.right == e->text()) 817 | { 818 | auto symbol = charUnderCursor(); 819 | 820 | if (symbol == p.right) 821 | { 822 | moveCursor(QTextCursor::NextCharacter); 823 | return; 824 | } 825 | } 826 | 827 | if (p.left == e->text()) 828 | { 829 | insertPlainText(QString(p.left) + p.right); 830 | moveCursor(QTextCursor::PreviousCharacter); 831 | return; 832 | } 833 | } 834 | } 835 | } 836 | 837 | if ((e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) && e->modifiers() == Qt::NoModifier) 838 | { 839 | insertPlainText("\n" + indentationSpaces.left(textCursor().columnNumber())); 840 | setTextCursor(textCursor()); // scroll to the cursor 841 | return; 842 | } 843 | 844 | if (e->key() == Qt::Key_Escape && textCursor().hasSelection()) 845 | { 846 | auto cursor = textCursor(); 847 | cursor.clearSelection(); 848 | setTextCursor(cursor); 849 | } 850 | 851 | QTextEdit::keyPressEvent(e); 852 | } 853 | 854 | proceedCompleterEnd(e); 855 | } 856 | 857 | void QCodeEditor::setAutoIndentation(bool enabled) 858 | { 859 | m_autoIndentation = enabled; 860 | } 861 | 862 | void QCodeEditor::setParentheses(const QVector &parentheses) 863 | { 864 | m_parentheses = parentheses; 865 | } 866 | 867 | void QCodeEditor::setExtraBottomMargin(bool enabled) 868 | { 869 | m_extraBottomMargin = enabled; 870 | updateBottomMargin(); 871 | } 872 | 873 | bool QCodeEditor::autoIndentation() const 874 | { 875 | return m_autoIndentation; 876 | } 877 | 878 | void QCodeEditor::setTabReplace(bool enabled) 879 | { 880 | m_replaceTab = enabled; 881 | } 882 | 883 | bool QCodeEditor::tabReplace() const 884 | { 885 | return m_replaceTab; 886 | } 887 | 888 | void QCodeEditor::setTabReplaceSize(int val) 889 | { 890 | m_tabReplace.fill(' ', val); 891 | #if QT_VERSION >= 0x050B00 892 | setTabStopDistance(fontMetrics().horizontalAdvance(QString(val * 1000, ' ')) / 1000.0); 893 | #elif QT_VERSION == 0x050A00 894 | setTabStopDistance(fontMetrics().width(QString(val * 1000, ' ')) / 1000.0); 895 | #else 896 | setTabStopWidth(fontMetrics().width(QString(val * 1000, ' ')) / 1000.0); 897 | #endif 898 | } 899 | 900 | int QCodeEditor::tabReplaceSize() const 901 | { 902 | return m_tabReplace.size(); 903 | } 904 | 905 | void QCodeEditor::setCompleter(QCompleter *completer) 906 | { 907 | if (m_completer) 908 | { 909 | disconnect(m_completer, nullptr, this, nullptr); 910 | } 911 | 912 | m_completer = completer; 913 | 914 | if (!m_completer) 915 | { 916 | return; 917 | } 918 | 919 | m_completer->setWidget(this); 920 | m_completer->setCompletionMode(QCompleter::CompletionMode::PopupCompletion); 921 | 922 | connect(m_completer, QOverload::of(&QCompleter::activated), this, &QCodeEditor::insertCompletion); 923 | } 924 | 925 | void QCodeEditor::focusInEvent(QFocusEvent *e) 926 | { 927 | if (m_completer) 928 | { 929 | m_completer->setWidget(this); 930 | } 931 | 932 | QTextEdit::focusInEvent(e); 933 | } 934 | 935 | bool QCodeEditor::event(QEvent *event) 936 | { 937 | if (event->type() == QEvent::ToolTip) 938 | { 939 | auto *helpEvent = dynamic_cast(event); 940 | auto point = helpEvent->pos(); 941 | point.setX(point.x() - m_lineNumberArea->geometry().right()); 942 | QTextCursor cursor = cursorForPosition(point); 943 | 944 | auto lineNumber = cursor.blockNumber() + 1; 945 | 946 | QTextCursor copyCursor(cursor); 947 | copyCursor.movePosition(QTextCursor::StartOfBlock); 948 | 949 | auto blockPositionStart = cursor.positionInBlock() - copyCursor.positionInBlock(); 950 | QPair positionOfTooltip{lineNumber, blockPositionStart}; 951 | 952 | QString text; 953 | for (auto const &e : qAsConst(m_squiggler)) 954 | { 955 | if (e.m_startPos <= positionOfTooltip && e.m_stopPos >= positionOfTooltip) 956 | { 957 | if (text.isEmpty()) 958 | text = e.m_tooltipText; 959 | else 960 | text += "; " + e.m_tooltipText; 961 | } 962 | } 963 | 964 | if (text.isEmpty()) 965 | QToolTip::hideText(); 966 | else 967 | QToolTip::showText(helpEvent->globalPos(), text); 968 | 969 | return true; 970 | } 971 | return QTextEdit::event(event); 972 | } 973 | 974 | void QCodeEditor::insertCompletion(const QString &s) 975 | { 976 | if (m_completer->widget() != this) 977 | { 978 | return; 979 | } 980 | 981 | auto tc = textCursor(); 982 | tc.select(QTextCursor::SelectionType::WordUnderCursor); 983 | tc.insertText(s); 984 | setTextCursor(tc); 985 | } 986 | 987 | QCompleter *QCodeEditor::completer() const 988 | { 989 | return m_completer; 990 | } 991 | 992 | void QCodeEditor::squiggle(SeverityLevel level, QPair start, QPair stop, 993 | const QString &tooltipMessage) 994 | { 995 | if (stop < start) 996 | return; 997 | 998 | SquiggleInformation info(start, stop, tooltipMessage); 999 | m_squiggler.push_back(info); 1000 | 1001 | auto cursor = textCursor(); 1002 | 1003 | cursor.movePosition(QTextCursor::Start); 1004 | cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, start.first - 1); 1005 | cursor.movePosition(QTextCursor::StartOfBlock); 1006 | cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, start.second); 1007 | 1008 | if (stop.first > start.first) 1009 | cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor, stop.first - start.first); 1010 | 1011 | cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); 1012 | cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, stop.second); 1013 | 1014 | QTextCharFormat newcharfmt = currentCharFormat(); 1015 | newcharfmt.setFontUnderline(true); 1016 | 1017 | switch (level) 1018 | { 1019 | case SeverityLevel::Error: 1020 | newcharfmt.setUnderlineColor(m_syntaxStyle->getFormat("Error").underlineColor()); 1021 | newcharfmt.setUnderlineStyle(m_syntaxStyle->getFormat("Error").underlineStyle()); 1022 | break; 1023 | case SeverityLevel::Warning: 1024 | newcharfmt.setUnderlineColor(m_syntaxStyle->getFormat("Warning").underlineColor()); 1025 | newcharfmt.setUnderlineStyle(m_syntaxStyle->getFormat("Warning").underlineStyle()); 1026 | break; 1027 | case SeverityLevel::Information: 1028 | newcharfmt.setUnderlineColor(m_syntaxStyle->getFormat("Warning").underlineColor()); 1029 | newcharfmt.setUnderlineStyle(QTextCharFormat::DotLine); 1030 | break; 1031 | case SeverityLevel::Hint: 1032 | newcharfmt.setUnderlineColor(m_syntaxStyle->getFormat("Text").foreground().color()); 1033 | newcharfmt.setUnderlineStyle(QTextCharFormat::DotLine); 1034 | } 1035 | 1036 | extra_squiggles.push_back({cursor, newcharfmt}); 1037 | 1038 | m_lineNumberArea->lint(level, start.first, stop.first); 1039 | 1040 | setExtraSelections(extra1 + extra2 + extra_squiggles); 1041 | } 1042 | 1043 | void QCodeEditor::clearSquiggle() 1044 | { 1045 | if (m_squiggler.empty()) 1046 | return; 1047 | 1048 | m_squiggler.clear(); 1049 | extra_squiggles.clear(); 1050 | 1051 | m_lineNumberArea->clearLint(); 1052 | 1053 | setExtraSelections(extra1 + extra2); 1054 | } 1055 | 1056 | QChar QCodeEditor::charUnderCursor(int offset) const 1057 | { 1058 | auto block = textCursor().blockNumber(); 1059 | auto index = textCursor().positionInBlock(); 1060 | auto text = document()->findBlockByNumber(block).text(); 1061 | 1062 | index += offset; 1063 | 1064 | if (index < 0 || index >= text.size()) 1065 | { 1066 | return {}; 1067 | } 1068 | 1069 | return text[index]; 1070 | } 1071 | 1072 | QString QCodeEditor::wordUnderCursor() const 1073 | { 1074 | auto tc = textCursor(); 1075 | tc.select(QTextCursor::WordUnderCursor); 1076 | return tc.selectedText(); 1077 | } 1078 | 1079 | void QCodeEditor::insertFromMimeData(const QMimeData *source) 1080 | { 1081 | insertPlainText(source->text()); 1082 | } 1083 | 1084 | bool QCodeEditor::removeInEachLineOfSelection(const QRegularExpression ®ex, bool force) 1085 | { 1086 | auto cursor = textCursor(); 1087 | auto lines = toPlainText().remove('\r').split('\n'); 1088 | int selectionStart = cursor.selectionStart(); 1089 | int selectionEnd = cursor.selectionEnd(); 1090 | bool cursorAtEnd = cursor.position() == selectionEnd; 1091 | cursor.setPosition(selectionStart); 1092 | int lineStart = cursor.blockNumber(); 1093 | cursor.setPosition(selectionEnd); 1094 | int lineEnd = cursor.blockNumber(); 1095 | QString newText; 1096 | QTextStream stream(&newText); 1097 | int deleteTotal = 0, deleteFirst = 0; 1098 | for (int i = lineStart; i <= lineEnd; ++i) 1099 | { 1100 | auto line = lines[i]; 1101 | auto match = regex.match(line).captured(1); 1102 | int len = match.length(); 1103 | if (len == 0 && !force) 1104 | return false; 1105 | if (i == lineStart) 1106 | deleteFirst = len; 1107 | deleteTotal += len; 1108 | stream << line.remove(line.indexOf(match), len); 1109 | if (i != lineEnd) 1110 | #if QT_VERSION >= 0x50E00 1111 | stream << Qt::endl; 1112 | #else 1113 | stream << endl; 1114 | #endif 1115 | } 1116 | cursor.movePosition(QTextCursor::Start); 1117 | cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, lineStart); 1118 | cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor, lineEnd - lineStart); 1119 | cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); 1120 | cursor.insertText(newText); 1121 | cursor.setPosition(qMax(0, selectionStart - deleteFirst)); 1122 | if (cursor.blockNumber() < lineStart) 1123 | { 1124 | cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, lineStart - cursor.blockNumber()); 1125 | cursor.movePosition(QTextCursor::StartOfBlock); 1126 | } 1127 | int pos = cursor.position(); 1128 | cursor.setPosition(selectionEnd - deleteTotal); 1129 | if (cursor.blockNumber() < lineEnd) 1130 | { 1131 | cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, lineEnd - cursor.blockNumber()); 1132 | cursor.movePosition(QTextCursor::StartOfBlock); 1133 | } 1134 | int pos2 = cursor.position(); 1135 | if (cursorAtEnd) 1136 | { 1137 | cursor.setPosition(pos); 1138 | cursor.setPosition(pos2, QTextCursor::KeepAnchor); 1139 | } 1140 | else 1141 | { 1142 | cursor.setPosition(pos2); 1143 | cursor.setPosition(pos, QTextCursor::KeepAnchor); 1144 | } 1145 | setTextCursor(cursor); 1146 | return true; 1147 | } 1148 | 1149 | void QCodeEditor::addInEachLineOfSelection(const QRegularExpression ®ex, const QString &str) 1150 | { 1151 | auto cursor = textCursor(); 1152 | auto lines = toPlainText().remove('\r').split('\n'); 1153 | int selectionStart = cursor.selectionStart(); 1154 | int selectionEnd = cursor.selectionEnd(); 1155 | bool cursorAtEnd = cursor.position() == selectionEnd; 1156 | cursor.setPosition(selectionStart); 1157 | int lineStart = cursor.blockNumber(); 1158 | cursor.setPosition(selectionEnd); 1159 | int lineEnd = cursor.blockNumber(); 1160 | QString newText; 1161 | QTextStream stream(&newText); 1162 | for (int i = lineStart; i <= lineEnd; ++i) 1163 | { 1164 | auto line = lines[i]; 1165 | stream << line.insert(line.indexOf(regex), str); 1166 | if (i != lineEnd) 1167 | #if QT_VERSION >= 0x50E00 1168 | stream << Qt::endl; 1169 | #else 1170 | stream << endl; 1171 | #endif 1172 | } 1173 | cursor.movePosition(QTextCursor::Start); 1174 | cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, lineStart); 1175 | cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor, lineEnd - lineStart); 1176 | cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); 1177 | cursor.insertText(newText); 1178 | int pos = selectionStart + str.length(); 1179 | int pos2 = selectionEnd + str.length() * (lineEnd - lineStart + 1); 1180 | if (cursorAtEnd) 1181 | { 1182 | cursor.setPosition(pos); 1183 | cursor.setPosition(pos2, QTextCursor::KeepAnchor); 1184 | } 1185 | else 1186 | { 1187 | cursor.setPosition(pos2); 1188 | cursor.setPosition(pos, QTextCursor::KeepAnchor); 1189 | } 1190 | setTextCursor(cursor); 1191 | } 1192 | -------------------------------------------------------------------------------- /src/internal/QGLSLCompleter.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | #include 4 | 5 | // Qt 6 | #include 7 | #include 8 | 9 | QGLSLCompleter::QGLSLCompleter(QObject *parent) : QCompleter(parent) 10 | { 11 | // Setting up GLSL types 12 | QStringList list; 13 | 14 | Q_INIT_RESOURCE(qcodeeditor_resources); 15 | QFile fl(":/languages/glsl.xml"); 16 | 17 | if (!fl.open(QIODevice::ReadOnly)) 18 | { 19 | return; 20 | } 21 | 22 | QLanguage language(&fl); 23 | 24 | if (!language.isLoaded()) 25 | { 26 | return; 27 | } 28 | 29 | auto keys = language.keys(); 30 | for (auto &&key : keys) 31 | { 32 | auto names = language.names(key); 33 | list.append(names); 34 | } 35 | 36 | setModel(new QStringListModel(list, this)); 37 | setCompletionColumn(0); 38 | setModelSorting(QCompleter::CaseInsensitivelySortedModel); 39 | setCaseSensitivity(Qt::CaseSensitive); 40 | setWrapAround(true); 41 | } 42 | -------------------------------------------------------------------------------- /src/internal/QGLSLHighlighter.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | #include 4 | #include 5 | 6 | // Qt 7 | #include 8 | #include 9 | 10 | QGLSLHighlighter::QGLSLHighlighter(QTextDocument *document) 11 | : QStyleSyntaxHighlighter(document), m_highlightRules(), 12 | m_includePattern(QRegularExpression(R"(#include\s+([<"][a-zA-Z0-9*._]+[">]))")), 13 | m_functionPattern(QRegularExpression(R"(\b([A-Za-z0-9_]+(?:\s+|::))*([A-Za-z0-9_]+)(?=\())")), 14 | m_defTypePattern(QRegularExpression(R"(\b([A-Za-z0-9_]+)\s+[A-Za-z]{1}[A-Za-z0-9_]+\s*[;=])")), 15 | m_commentStartPattern(QRegularExpression(R"(/\*)")), m_commentEndPattern(QRegularExpression(R"(\*/)")) 16 | { 17 | Q_INIT_RESOURCE(qcodeeditor_resources); 18 | QFile fl(":/languages/glsl.xml"); 19 | 20 | if (!fl.open(QIODevice::ReadOnly)) 21 | { 22 | return; 23 | } 24 | 25 | QLanguage language(&fl); 26 | 27 | if (!language.isLoaded()) 28 | { 29 | return; 30 | } 31 | 32 | auto keys = language.keys(); 33 | for (auto &&key : keys) 34 | { 35 | auto names = language.names(key); 36 | for (auto &&name : names) 37 | { 38 | m_highlightRules.append({QRegularExpression(QString(R"(\b%1\b)").arg(name)), key}); 39 | } 40 | } 41 | 42 | // Following rules has higher priority to display 43 | // than language specific keys 44 | // So they must be applied at last. 45 | // Numbers 46 | m_highlightRules.append({QRegularExpression(R"(\b(0b|0x){0,1}[\d.']+\b)"), "Number"}); 47 | 48 | // Define 49 | m_highlightRules.append({QRegularExpression(R"(#[a-zA-Z_]+)"), "Preprocessor"}); 50 | 51 | // Single line 52 | m_highlightRules.append({QRegularExpression("//[^\n]*"), "Comment"}); 53 | 54 | // Comment sequences for toggling support 55 | m_commentLineSequence = "//"; 56 | m_startCommentBlockSequence = "/*"; 57 | m_endCommentBlockSequence = "*/"; 58 | } 59 | 60 | void QGLSLHighlighter::highlightBlock(const QString &text) 61 | { 62 | 63 | { 64 | auto matchIterator = m_includePattern.globalMatch(text); 65 | 66 | while (matchIterator.hasNext()) 67 | { 68 | auto match = matchIterator.next(); 69 | 70 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat("Preprocessor")); 71 | 72 | setFormat(match.capturedStart(1), match.capturedLength(1), syntaxStyle()->getFormat("String")); 73 | } 74 | } 75 | // Checking for function 76 | { 77 | auto matchIterator = m_functionPattern.globalMatch(text); 78 | 79 | while (matchIterator.hasNext()) 80 | { 81 | auto match = matchIterator.next(); 82 | 83 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat("Type")); 84 | 85 | setFormat(match.capturedStart(2), match.capturedLength(2), syntaxStyle()->getFormat("Function")); 86 | } 87 | } 88 | 89 | for (auto &rule : m_highlightRules) 90 | { 91 | auto matchIterator = rule.pattern.globalMatch(text); 92 | 93 | while (matchIterator.hasNext()) 94 | { 95 | auto match = matchIterator.next(); 96 | 97 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat(rule.formatName)); 98 | } 99 | } 100 | 101 | setCurrentBlockState(0); 102 | 103 | int startIndex = 0; 104 | if (previousBlockState() != 1) 105 | { 106 | startIndex = text.indexOf(m_commentStartPattern); 107 | } 108 | 109 | while (startIndex >= 0) 110 | { 111 | auto match = m_commentEndPattern.match(text, startIndex); 112 | 113 | int endIndex = match.capturedStart(); 114 | int commentLength = 0; 115 | 116 | if (endIndex == -1) 117 | { 118 | setCurrentBlockState(1); 119 | commentLength = text.length() - startIndex; 120 | } 121 | else 122 | { 123 | commentLength = endIndex - startIndex + match.capturedLength(); 124 | } 125 | 126 | setFormat(startIndex, commentLength, syntaxStyle()->getFormat("Comment")); 127 | startIndex = text.indexOf(m_commentStartPattern, startIndex + commentLength); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/internal/QJSHighlighter.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | #include 4 | #include 5 | 6 | // Qt 7 | #include 8 | 9 | QJSHighlighter::QJSHighlighter(QTextDocument *document) 10 | : QStyleSyntaxHighlighter(document), m_highlightRules(), m_commentStartPattern(QRegularExpression(R"(/\*)")), 11 | m_commentEndPattern(QRegularExpression(R"(\*/)")) 12 | { 13 | Q_INIT_RESOURCE(qcodeeditor_resources); 14 | QFile fl(":/languages/js.xml"); 15 | 16 | if (!fl.open(QIODevice::ReadOnly)) 17 | { 18 | return; 19 | } 20 | 21 | QLanguage language(&fl); 22 | 23 | if (!language.isLoaded()) 24 | { 25 | return; 26 | } 27 | 28 | auto keys = language.keys(); 29 | for (auto &&key : keys) 30 | { 31 | auto names = language.names(key); 32 | for (auto &&name : names) 33 | { 34 | m_highlightRules.append({QRegularExpression(QString(R"(\b%1\b)").arg(name)), key}); 35 | } 36 | } 37 | 38 | // Numbers 39 | m_highlightRules.append( 40 | {QRegularExpression( 41 | R"((?<=\b|\s|^)(?i)(?:(?:(?:(?:(?:\d+(?:'\d+)*)?\.(?:\d+(?:'\d+)*)(?:e[+-]?(?:\d+(?:'\d+)*))?)|(?:(?:\d+(?:'\d+)*)\.(?:e[+-]?(?:\d+(?:'\d+)*))?)|(?:(?:\d+(?:'\d+)*)(?:e[+-]?(?:\d+(?:'\d+)*)))|(?:0x(?:[0-9a-f]+(?:'[0-9a-f]+)*)?\.(?:[0-9a-f]+(?:'[0-9a-f]+)*)(?:p[+-]?(?:\d+(?:'\d+)*)))|(?:0x(?:[0-9a-f]+(?:'[0-9a-f]+)*)\.?(?:p[+-]?(?:\d+(?:'\d+)*))))[lf]?)|(?:(?:(?:[1-9]\d*(?:'\d+)*)|(?:0[0-7]*(?:'[0-7]+)*)|(?:0x[0-9a-f]+(?:'[0-9a-f]+)*)|(?:0b[01]+(?:'[01]+)*))(?:u?l{0,2}|l{0,2}u?)))(?=\b|\s|$))"), 42 | "Number"}); 43 | 44 | // Strings 45 | m_highlightRules.append({QRegularExpression(R"("[^\n"]*")"), "String"}); 46 | 47 | // Single line 48 | m_highlightRules.append({QRegularExpression(R"(//[^\n]*)"), "Comment"}); 49 | 50 | // Comment sequences for toggling support 51 | m_commentLineSequence = "//"; 52 | m_startCommentBlockSequence = "/*"; 53 | m_endCommentBlockSequence = "*/"; 54 | } 55 | 56 | void QJSHighlighter::highlightBlock(const QString &text) 57 | { 58 | for (auto &rule : m_highlightRules) 59 | { 60 | auto matchIterator = rule.pattern.globalMatch(text); 61 | 62 | while (matchIterator.hasNext()) 63 | { 64 | auto match = matchIterator.next(); 65 | 66 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat(rule.formatName)); 67 | } 68 | } 69 | 70 | setCurrentBlockState(0); 71 | 72 | int startIndex = 0; 73 | if (previousBlockState() != 1) 74 | { 75 | startIndex = text.indexOf(m_commentStartPattern); 76 | } 77 | 78 | while (startIndex >= 0) 79 | { 80 | auto match = m_commentEndPattern.match(text, startIndex); 81 | 82 | int endIndex = match.capturedStart(); 83 | int commentLength = 0; 84 | 85 | if (endIndex == -1) 86 | { 87 | setCurrentBlockState(1); 88 | commentLength = text.length() - startIndex; 89 | } 90 | else 91 | { 92 | commentLength = endIndex - startIndex + match.capturedLength(); 93 | } 94 | 95 | setFormat(startIndex, commentLength, syntaxStyle()->getFormat("Comment")); 96 | startIndex = text.indexOf(m_commentStartPattern, startIndex + commentLength); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/internal/QJSONHighlighter.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | #include 4 | 5 | QJSONHighlighter::QJSONHighlighter(QTextDocument *document) 6 | : QStyleSyntaxHighlighter(document), m_highlightRules(), m_keyRegex(R"(("[^\r\n:]+?")\s*:)") 7 | { 8 | auto keywords = QStringList() << "null" 9 | << "true" 10 | << "false"; 11 | 12 | for (auto &&keyword : keywords) 13 | { 14 | m_highlightRules.append({QRegularExpression(QString(R"(\b%1\b)").arg(keyword)), "Keyword"}); 15 | } 16 | 17 | // Numbers 18 | m_highlightRules.append({QRegularExpression(R"(\b(0b|0x){0,1}[\d.']+\b)"), "Number"}); 19 | 20 | // Strings 21 | m_highlightRules.append({QRegularExpression(R"("[^\n"]*")"), "String"}); 22 | } 23 | 24 | void QJSONHighlighter::highlightBlock(const QString &text) 25 | { 26 | for (auto &&rule : m_highlightRules) 27 | { 28 | auto matchIterator = rule.pattern.globalMatch(text); 29 | 30 | while (matchIterator.hasNext()) 31 | { 32 | auto match = matchIterator.next(); 33 | 34 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat(rule.formatName)); 35 | } 36 | } 37 | 38 | // Special treatment for key regex 39 | auto matchIterator = m_keyRegex.globalMatch(text); 40 | 41 | while (matchIterator.hasNext()) 42 | { 43 | auto match = matchIterator.next(); 44 | 45 | setFormat(match.capturedStart(1), match.capturedLength(1), syntaxStyle()->getFormat("Keyword")); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/internal/QJavaHighlighter.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | #include 4 | #include 5 | 6 | // Qt 7 | #include 8 | 9 | QJavaHighlighter::QJavaHighlighter(QTextDocument *document) 10 | : QStyleSyntaxHighlighter(document), m_highlightRules(), m_commentStartPattern(QRegularExpression(R"(/\*)")), 11 | m_commentEndPattern(QRegularExpression(R"(\*/)")) 12 | { 13 | Q_INIT_RESOURCE(qcodeeditor_resources); 14 | 15 | QFile fl(":/languages/java.xml"); 16 | 17 | if (!fl.open(QIODevice::ReadOnly)) 18 | { 19 | return; 20 | } 21 | 22 | QLanguage language(&fl); 23 | 24 | if (!language.isLoaded()) 25 | { 26 | return; 27 | } 28 | 29 | auto keys = language.keys(); 30 | for (auto &&key : keys) 31 | { 32 | auto names = language.names(key); 33 | for (auto &&name : names) 34 | { 35 | m_highlightRules.append({QRegularExpression(QString(R"(\b%1\b)").arg(name)), key}); 36 | } 37 | } 38 | 39 | // Numbers 40 | m_highlightRules.append( 41 | {QRegularExpression( 42 | R"((?<=\b|\s|^)(?i)(?:(?:[0-9]+\.[0-9]*(?:e[+-]?[0-9]+)?[fd]?)|(?:\.[0-9]+(?:e[+-]?[0-9]+)?[fd]?)|(?:[0-9]+(?:e[+-]?[0-9]+)[fd]?)|(?:[0-9]+(?:e[+-]?[0-9]+)?[fd])|(?:(?:(?:0x[0-9a-f]+\.?)|(?:0x[0-9a-f]*\.[0-9a-f]+))p[+-]?[0-9]+[fd]?)|(?:0)|(?:[1-9][0-9]*)|(?:0x[0-9a-f]+)|(?:0[0-7]+))(?=\b|\s|$))"), 43 | "Number"}); 44 | 45 | // Strings 46 | m_highlightRules.append({QRegularExpression(R"("[^\n"]*")"), "String"}); 47 | 48 | // Single line 49 | m_highlightRules.append({QRegularExpression(R"(//[^\n]*)"), "Comment"}); 50 | 51 | // Comment sequences for toggling support 52 | m_commentLineSequence = "//"; 53 | m_startCommentBlockSequence = "/*"; 54 | m_endCommentBlockSequence = "*/"; 55 | } 56 | 57 | void QJavaHighlighter::highlightBlock(const QString &text) 58 | { 59 | for (auto &rule : m_highlightRules) 60 | { 61 | auto matchIterator = rule.pattern.globalMatch(text); 62 | 63 | while (matchIterator.hasNext()) 64 | { 65 | auto match = matchIterator.next(); 66 | 67 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat(rule.formatName)); 68 | } 69 | } 70 | 71 | setCurrentBlockState(0); 72 | 73 | int startIndex = 0; 74 | if (previousBlockState() != 1) 75 | { 76 | startIndex = text.indexOf(m_commentStartPattern); 77 | } 78 | 79 | while (startIndex >= 0) 80 | { 81 | auto match = m_commentEndPattern.match(text, startIndex); 82 | 83 | int endIndex = match.capturedStart(); 84 | int commentLength = 0; 85 | 86 | if (endIndex == -1) 87 | { 88 | setCurrentBlockState(1); 89 | commentLength = text.length() - startIndex; 90 | } 91 | else 92 | { 93 | commentLength = endIndex - startIndex + match.capturedLength(); 94 | } 95 | 96 | setFormat(startIndex, commentLength, syntaxStyle()->getFormat("Comment")); 97 | startIndex = text.indexOf(m_commentStartPattern, startIndex + commentLength); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/internal/QLanguage.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | 4 | // Qt 5 | #include 6 | #include 7 | 8 | QLanguage::QLanguage(QIODevice *device, QObject *parent) : QObject(parent), m_loaded(false), m_list() 9 | { 10 | load(device); 11 | } 12 | 13 | bool QLanguage::load(QIODevice *device) 14 | { 15 | if (device == nullptr) 16 | { 17 | return false; 18 | } 19 | 20 | QXmlStreamReader reader(device); 21 | 22 | QString name; 23 | QStringList list; 24 | bool readText = false; 25 | 26 | while (!reader.atEnd() && !reader.hasError()) 27 | { 28 | auto type = reader.readNext(); 29 | 30 | if (type == QXmlStreamReader::TokenType::StartElement) 31 | { 32 | if (reader.name() == "section") 33 | { 34 | if (!list.empty()) 35 | { 36 | m_list[name] = list; 37 | list.clear(); 38 | } 39 | 40 | name = reader.attributes().value("name").toString(); 41 | } 42 | else if (reader.name() == "name") 43 | { 44 | readText = true; 45 | } 46 | } 47 | else if (type == QXmlStreamReader::TokenType::Characters && readText) 48 | { 49 | list << reader.text().toString(); 50 | readText = false; 51 | } 52 | } 53 | 54 | if (!list.empty()) 55 | { 56 | m_list[name] = list; 57 | } 58 | 59 | m_loaded = !reader.hasError(); 60 | 61 | return m_loaded; 62 | } 63 | 64 | QStringList QLanguage::keys() 65 | { 66 | return m_list.keys(); 67 | } 68 | 69 | QStringList QLanguage::names(const QString &key) 70 | { 71 | return m_list[key]; 72 | } 73 | 74 | bool QLanguage::isLoaded() const 75 | { 76 | return m_loaded; 77 | } 78 | -------------------------------------------------------------------------------- /src/internal/QLineNumberArea.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | #include 4 | #include 5 | 6 | // Qt 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | QLineNumberArea::QLineNumberArea(QCodeEditor *parent) 15 | : QWidget(parent), m_syntaxStyle(nullptr), m_codeEditParent(parent), m_squiggles() 16 | { 17 | } 18 | 19 | QSize QLineNumberArea::sizeHint() const 20 | { 21 | if (m_codeEditParent == nullptr) 22 | { 23 | return QWidget::sizeHint(); 24 | } 25 | 26 | const int digits = QString::number(m_codeEditParent->document()->blockCount()).length(); 27 | int space; 28 | 29 | #if QT_VERSION >= 0x050B00 30 | space = 15 + m_codeEditParent->fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits; 31 | #else 32 | space = 15 + m_codeEditParent->fontMetrics().width(QLatin1Char('9')) * digits; 33 | #endif 34 | 35 | return {space, 0}; 36 | } 37 | 38 | void QLineNumberArea::setSyntaxStyle(QSyntaxStyle *style) 39 | { 40 | m_syntaxStyle = style; 41 | } 42 | 43 | QSyntaxStyle *QLineNumberArea::syntaxStyle() const 44 | { 45 | return m_syntaxStyle; 46 | } 47 | 48 | void QLineNumberArea::lint(QCodeEditor::SeverityLevel level, int from, int to) 49 | { 50 | for (int i = from - 1; i < to; ++i) 51 | { 52 | m_squiggles[i] = qMax(m_squiggles[i], level); 53 | } 54 | update(); 55 | } 56 | 57 | void QLineNumberArea::clearLint() 58 | { 59 | m_squiggles.clear(); 60 | update(); 61 | } 62 | 63 | void QLineNumberArea::paintEvent(QPaintEvent *event) 64 | { 65 | QPainter painter(this); 66 | 67 | // Clearing rect to update 68 | painter.fillRect(event->rect(), m_syntaxStyle->getFormat("Text").background().color()); 69 | 70 | auto blockNumber = m_codeEditParent->getFirstVisibleBlock(); 71 | auto block = m_codeEditParent->document()->findBlockByNumber(blockNumber); 72 | auto top = (int)m_codeEditParent->document() 73 | ->documentLayout() 74 | ->blockBoundingRect(block) 75 | .translated(0, -m_codeEditParent->verticalScrollBar()->value()) 76 | .top(); 77 | auto bottom = top + (int)m_codeEditParent->document()->documentLayout()->blockBoundingRect(block).height(); 78 | 79 | auto currentLine = m_syntaxStyle->getFormat("CurrentLineNumber").foreground().color(); 80 | auto otherLines = m_syntaxStyle->getFormat("LineNumber").foreground().color(); 81 | 82 | painter.setFont(m_codeEditParent->font()); 83 | 84 | while (block.isValid() && top <= event->rect().bottom()) 85 | { 86 | if (block.isVisible() && bottom >= event->rect().top()) 87 | { 88 | QString number = QString::number(blockNumber + 1); 89 | 90 | if (m_squiggles.contains(blockNumber)) 91 | { 92 | QColor squiggleColor; 93 | switch (m_squiggles[blockNumber]) 94 | { 95 | case QCodeEditor::SeverityLevel::Error: 96 | squiggleColor = m_syntaxStyle->getFormat("Error").underlineColor(); 97 | break; 98 | case QCodeEditor::SeverityLevel::Warning: 99 | squiggleColor = m_syntaxStyle->getFormat("Warning").underlineColor(); 100 | break; 101 | case QCodeEditor::SeverityLevel::Information: 102 | squiggleColor = m_syntaxStyle->getFormat("Warning").underlineColor(); 103 | break; 104 | case QCodeEditor::SeverityLevel::Hint: 105 | squiggleColor = m_syntaxStyle->getFormat("Text").foreground().color(); 106 | break; 107 | default: 108 | Q_UNREACHABLE(); 109 | break; 110 | } 111 | painter.fillRect(0, top, 7, m_codeEditParent->fontMetrics().height(), squiggleColor); 112 | } 113 | 114 | auto isCurrentLine = m_codeEditParent->textCursor().blockNumber() == blockNumber; 115 | painter.setPen(isCurrentLine ? currentLine : otherLines); 116 | 117 | painter.drawText(-5, top, sizeHint().width(), m_codeEditParent->fontMetrics().height(), Qt::AlignRight, 118 | number); 119 | } 120 | 121 | block = block.next(); 122 | top = bottom; 123 | bottom = top + (int)m_codeEditParent->document()->documentLayout()->blockBoundingRect(block).height(); 124 | ++blockNumber; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/internal/QLuaCompleter.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | #include 4 | 5 | // Qt 6 | #include 7 | #include 8 | 9 | QLuaCompleter::QLuaCompleter(QObject *parent) : QCompleter(parent) 10 | { 11 | // Setting up GLSL types 12 | QStringList list; 13 | 14 | Q_INIT_RESOURCE(qcodeeditor_resources); 15 | QFile fl(":/languages/lua.xml"); 16 | 17 | if (!fl.open(QIODevice::ReadOnly)) 18 | { 19 | return; 20 | } 21 | 22 | QLanguage language(&fl); 23 | 24 | if (!language.isLoaded()) 25 | { 26 | return; 27 | } 28 | 29 | auto keys = language.keys(); 30 | for (auto &&key : keys) 31 | { 32 | auto names = language.names(key); 33 | list.append(names); 34 | } 35 | 36 | setModel(new QStringListModel(list, this)); 37 | setCompletionColumn(0); 38 | setModelSorting(QCompleter::CaseInsensitivelySortedModel); 39 | setCaseSensitivity(Qt::CaseSensitive); 40 | setWrapAround(true); 41 | } 42 | -------------------------------------------------------------------------------- /src/internal/QLuaHighlighter.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | #include 4 | #include 5 | 6 | // Qt 7 | #include 8 | 9 | QLuaHighlighter::QLuaHighlighter(QTextDocument *document) 10 | : QStyleSyntaxHighlighter(document), m_highlightRules(), m_highlightBlockRules(), 11 | m_requirePattern(QRegularExpression(R"(require\s*([("'][a-zA-Z0-9*._]+['")]))")), 12 | m_functionPattern(QRegularExpression(R"(\b([A-Za-z0-9_]+(?:\s+|::))*([A-Za-z0-9_]+)(?=\())")), 13 | m_defTypePattern(QRegularExpression(R"(\b([A-Za-z0-9_]+)\s+[A-Za-z]{1}[A-Za-z0-9_]+\s*[=])")) 14 | { 15 | Q_INIT_RESOURCE(qcodeeditor_resources); 16 | QFile fl(":/languages/lua.xml"); 17 | 18 | if (!fl.open(QIODevice::ReadOnly)) 19 | { 20 | return; 21 | } 22 | 23 | QLanguage language(&fl); 24 | 25 | if (!language.isLoaded()) 26 | { 27 | return; 28 | } 29 | 30 | auto keys = language.keys(); 31 | for (auto &&key : keys) 32 | { 33 | auto names = language.names(key); 34 | for (auto &&name : names) 35 | { 36 | m_highlightRules.append({QRegularExpression(QString(R"(\b\s{0,1}%1\s{0,1}\b)").arg(name)), key}); 37 | } 38 | } 39 | 40 | // Numbers 41 | m_highlightRules.append({QRegularExpression(R"(\b(0b|0x){0,1}[\d.']+\b)"), "Number"}); 42 | 43 | // Strings 44 | m_highlightRules.append({QRegularExpression(R"(["'][^\n"]*["'])"), "String"}); 45 | 46 | // Preprocessor 47 | m_highlightRules.append({QRegularExpression(R"(#\![a-zA-Z_]+)"), "Preprocessor"}); 48 | 49 | // Single line 50 | m_highlightRules.append({QRegularExpression(R"(--[^\n]*)"), "Comment"}); 51 | 52 | // Multiline comments 53 | m_highlightBlockRules.append({QRegularExpression(R"(--\[\[)"), QRegularExpression(R"(--\]\])"), "Comment"}); 54 | 55 | // Multiline string 56 | m_highlightBlockRules.append({QRegularExpression(R"(\[\[)"), QRegularExpression(R"(\]\])"), "String"}); 57 | 58 | // Comment sequences for toggling support 59 | m_commentLineSequence = "--"; 60 | m_startCommentBlockSequence = "--[["; 61 | m_endCommentBlockSequence = "]]"; 62 | } 63 | 64 | void QLuaHighlighter::highlightBlock(const QString &text) 65 | { 66 | { // Checking for require 67 | auto matchIterator = m_requirePattern.globalMatch(text); 68 | 69 | while (matchIterator.hasNext()) 70 | { 71 | auto match = matchIterator.next(); 72 | 73 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat("Preprocessor")); 74 | 75 | setFormat(match.capturedStart(1), match.capturedLength(1), syntaxStyle()->getFormat("String")); 76 | } 77 | } 78 | { // Checking for function 79 | auto matchIterator = m_functionPattern.globalMatch(text); 80 | 81 | while (matchIterator.hasNext()) 82 | { 83 | auto match = matchIterator.next(); 84 | 85 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat("Type")); 86 | 87 | setFormat(match.capturedStart(2), match.capturedLength(2), syntaxStyle()->getFormat("Function")); 88 | } 89 | } 90 | { // checking for type 91 | auto matchIterator = m_defTypePattern.globalMatch(text); 92 | 93 | while (matchIterator.hasNext()) 94 | { 95 | auto match = matchIterator.next(); 96 | 97 | setFormat(match.capturedStart(1), match.capturedLength(1), syntaxStyle()->getFormat("Type")); 98 | } 99 | } 100 | 101 | for (auto &rule : m_highlightRules) 102 | { 103 | auto matchIterator = rule.pattern.globalMatch(text); 104 | 105 | while (matchIterator.hasNext()) 106 | { 107 | auto match = matchIterator.next(); 108 | 109 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat(rule.formatName)); 110 | } 111 | } 112 | 113 | setCurrentBlockState(0); 114 | int startIndex = 0; 115 | int highlightRuleId = previousBlockState(); 116 | if (highlightRuleId < 1 || highlightRuleId > m_highlightBlockRules.size()) 117 | { 118 | for (int i = 0; i < m_highlightBlockRules.size(); ++i) 119 | { 120 | startIndex = text.indexOf(m_highlightBlockRules.at(i).startPattern); 121 | if (startIndex >= 0) 122 | { 123 | highlightRuleId = i + 1; 124 | break; 125 | } 126 | } 127 | } 128 | 129 | while (startIndex >= 0) 130 | { 131 | const auto &blockRules = m_highlightBlockRules.at(highlightRuleId - 1); 132 | auto match = blockRules.endPattern.match(text, startIndex); 133 | 134 | int endIndex = match.capturedStart(); 135 | int matchLength = 0; 136 | 137 | if (endIndex == -1) 138 | { 139 | setCurrentBlockState(highlightRuleId); 140 | matchLength = text.length() - startIndex; 141 | } 142 | else 143 | { 144 | matchLength = endIndex - startIndex + match.capturedLength(); 145 | } 146 | 147 | setFormat(startIndex, matchLength, syntaxStyle()->getFormat(blockRules.formatName)); 148 | startIndex = text.indexOf(blockRules.startPattern, startIndex + matchLength); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/internal/QPythonCompleter.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | #include 4 | 5 | // Qt 6 | #include 7 | #include 8 | 9 | QPythonCompleter::QPythonCompleter(QObject *parent) : QCompleter(parent) 10 | { 11 | // Setting up Python types 12 | QStringList list; 13 | 14 | Q_INIT_RESOURCE(qcodeeditor_resources); 15 | QFile fl(":/languages/python.xml"); 16 | 17 | if (!fl.open(QIODevice::ReadOnly)) 18 | { 19 | return; 20 | } 21 | 22 | QLanguage language(&fl); 23 | 24 | if (!language.isLoaded()) 25 | { 26 | return; 27 | } 28 | 29 | auto keys = language.keys(); 30 | for (auto &&key : keys) 31 | { 32 | auto names = language.names(key); 33 | list.append(names); 34 | } 35 | 36 | setModel(new QStringListModel(list, this)); 37 | setCompletionColumn(0); 38 | setModelSorting(QCompleter::CaseInsensitivelySortedModel); 39 | setCaseSensitivity(Qt::CaseSensitive); 40 | setWrapAround(true); 41 | } 42 | -------------------------------------------------------------------------------- /src/internal/QPythonHighlighter.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | #include 4 | #include 5 | 6 | // Qt 7 | #include 8 | #include 9 | 10 | QPythonHighlighter::QPythonHighlighter(QTextDocument *document) 11 | : QStyleSyntaxHighlighter(document), m_highlightRules(), m_highlightBlockRules(), 12 | m_includePattern(QRegularExpression(R"(import \w+)")), 13 | m_functionPattern(QRegularExpression(R"(\b([A-Za-z0-9_]+(?:\.))*([A-Za-z0-9_]+)(?=\())")), 14 | m_defTypePattern(QRegularExpression(R"(\b([A-Za-z0-9_]+)\s+[A-Za-z]{1}[A-Za-z0-9_]+\s*[;=])")) 15 | { 16 | Q_INIT_RESOURCE(qcodeeditor_resources); 17 | QFile fl(":/languages/python.xml"); 18 | 19 | if (!fl.open(QIODevice::ReadOnly)) 20 | { 21 | return; 22 | } 23 | 24 | QLanguage language(&fl); 25 | 26 | if (!language.isLoaded()) 27 | { 28 | return; 29 | } 30 | 31 | auto keys = language.keys(); 32 | for (auto &&key : keys) 33 | { 34 | auto names = language.names(key); 35 | for (auto &&name : names) 36 | { 37 | m_highlightRules.append({QRegularExpression(QString(R"(\b%1\b)").arg(name)), key}); 38 | } 39 | } 40 | 41 | // Following rules has higher priority to display 42 | // than language specific keys 43 | // So they must be applied at last. 44 | // Numbers 45 | m_highlightRules.append({QRegularExpression(R"(\b(0b|0x){0,1}[\d.']+\b)"), "Number"}); 46 | 47 | // Strings 48 | m_highlightRules.append({QRegularExpression(R"("[^\n"]*")"), "String"}); 49 | m_highlightRules.append({QRegularExpression(R"('[^\n"]*')"), "String"}); 50 | 51 | // Single line comment 52 | m_highlightRules.append({QRegularExpression("#[^\n]*"), "Comment"}); 53 | 54 | // Multiline string 55 | m_highlightBlockRules.append({QRegularExpression("(''')"), QRegularExpression("(''')"), "String"}); 56 | m_highlightBlockRules.append({QRegularExpression("(\"\"\")"), QRegularExpression("(\"\"\")"), "String"}); 57 | 58 | // Comment sequences for toggling support 59 | m_commentLineSequence = "#"; 60 | m_startCommentBlockSequence = "'''"; 61 | m_endCommentBlockSequence = m_startCommentBlockSequence; 62 | } 63 | 64 | void QPythonHighlighter::highlightBlock(const QString &text) 65 | { 66 | // Checking for function 67 | { 68 | auto matchIterator = m_functionPattern.globalMatch(text); 69 | 70 | while (matchIterator.hasNext()) 71 | { 72 | auto match = matchIterator.next(); 73 | 74 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat("Type")); 75 | 76 | setFormat(match.capturedStart(2), match.capturedLength(2), syntaxStyle()->getFormat("Function")); 77 | } 78 | } 79 | 80 | for (auto &rule : m_highlightRules) 81 | { 82 | auto matchIterator = rule.pattern.globalMatch(text); 83 | 84 | while (matchIterator.hasNext()) 85 | { 86 | auto match = matchIterator.next(); 87 | 88 | setFormat(match.capturedStart(), match.capturedLength(), syntaxStyle()->getFormat(rule.formatName)); 89 | } 90 | } 91 | 92 | setCurrentBlockState(0); 93 | int startIndex = 0; 94 | int highlightRuleId = previousBlockState(); 95 | if (highlightRuleId < 1 || highlightRuleId > m_highlightBlockRules.size()) 96 | { 97 | for (int i = 0; i < m_highlightBlockRules.size(); ++i) 98 | { 99 | startIndex = text.indexOf(m_highlightBlockRules.at(i).startPattern); 100 | 101 | if (startIndex >= 0) 102 | { 103 | highlightRuleId = i + 1; 104 | break; 105 | } 106 | } 107 | } 108 | 109 | while (startIndex >= 0) 110 | { 111 | const auto &blockRules = m_highlightBlockRules.at(highlightRuleId - 1); 112 | auto match = blockRules.endPattern.match(text, startIndex + 1); // Should be + length of start pattern 113 | 114 | int endIndex = match.capturedStart(); 115 | int matchLength = 0; 116 | 117 | if (endIndex == -1) 118 | { 119 | setCurrentBlockState(highlightRuleId); 120 | matchLength = text.length() - startIndex; 121 | } 122 | else 123 | { 124 | matchLength = endIndex - startIndex + match.capturedLength(); 125 | } 126 | 127 | setFormat(startIndex, matchLength, syntaxStyle()->getFormat(blockRules.formatName)); 128 | startIndex = text.indexOf(blockRules.startPattern, startIndex + matchLength); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/internal/QStyleSyntaxHighlighter.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | 4 | QStyleSyntaxHighlighter::QStyleSyntaxHighlighter(QTextDocument *document) 5 | : QSyntaxHighlighter(document), m_syntaxStyle(nullptr), m_commentLineSequence(), m_startCommentBlockSequence(), 6 | m_endCommentBlockSequence() 7 | { 8 | } 9 | 10 | void QStyleSyntaxHighlighter::setSyntaxStyle(QSyntaxStyle *style) 11 | { 12 | m_syntaxStyle = style; 13 | } 14 | 15 | QSyntaxStyle *QStyleSyntaxHighlighter::syntaxStyle() const 16 | { 17 | return m_syntaxStyle; 18 | } 19 | 20 | QString QStyleSyntaxHighlighter::commentLineSequence() const 21 | { 22 | return m_commentLineSequence; 23 | } 24 | 25 | void QStyleSyntaxHighlighter::setCommentLineSequence(const QString &commentLineSequence) 26 | { 27 | m_commentLineSequence = commentLineSequence; 28 | } 29 | 30 | QString QStyleSyntaxHighlighter::startCommentBlockSequence() const 31 | { 32 | return m_startCommentBlockSequence; 33 | } 34 | 35 | void QStyleSyntaxHighlighter::setStartCommentBlockSequence(const QString &startCommentBlockSequence) 36 | { 37 | m_startCommentBlockSequence = startCommentBlockSequence; 38 | } 39 | 40 | QString QStyleSyntaxHighlighter::endCommentBlockSequence() const 41 | { 42 | return m_endCommentBlockSequence; 43 | } 44 | 45 | void QStyleSyntaxHighlighter::setEndCommentBlockSequence(const QString &endCommentBlockSequence) 46 | { 47 | m_endCommentBlockSequence = endCommentBlockSequence; 48 | } 49 | -------------------------------------------------------------------------------- /src/internal/QSyntaxStyle.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | 4 | // Qt 5 | #include 6 | #include 7 | #include 8 | 9 | QSyntaxStyle::QSyntaxStyle(QObject *parent) : QObject(parent), m_name(), m_data(), m_loaded(false) 10 | { 11 | } 12 | 13 | bool QSyntaxStyle::load(const QString &fl) 14 | { 15 | QXmlStreamReader reader(fl); 16 | 17 | while (!reader.atEnd() && !reader.hasError()) 18 | { 19 | auto token = reader.readNext(); 20 | 21 | if (token == QXmlStreamReader::StartElement) 22 | { 23 | if (reader.name() == "style-scheme") 24 | { 25 | if (reader.attributes().hasAttribute("name")) 26 | { 27 | m_name = reader.attributes().value("name").toString(); 28 | } 29 | } 30 | else if (reader.name() == "style") 31 | { 32 | auto attributes = reader.attributes(); 33 | 34 | auto name = attributes.value("name"); 35 | 36 | QTextCharFormat format; 37 | 38 | if (attributes.hasAttribute("background")) 39 | { 40 | format.setBackground(QColor(attributes.value("background").toString())); 41 | } 42 | 43 | if (attributes.hasAttribute("foreground")) 44 | { 45 | format.setForeground(QColor(attributes.value("foreground").toString())); 46 | } 47 | 48 | if (attributes.hasAttribute("bold") && attributes.value("bold") == "true") 49 | { 50 | format.setFontWeight(QFont::Weight::Bold); 51 | } 52 | 53 | if (attributes.hasAttribute("italic") && attributes.value("italic") == "true") 54 | { 55 | format.setFontItalic(true); 56 | } 57 | 58 | if (attributes.hasAttribute("underlineStyle")) 59 | { 60 | auto underline = attributes.value("underlineStyle"); 61 | 62 | auto s = QTextCharFormat::UnderlineStyle::NoUnderline; 63 | 64 | if (underline == "SingleUnderline") 65 | { 66 | s = QTextCharFormat::UnderlineStyle::SingleUnderline; 67 | } 68 | else if (underline == "DashUnderline") 69 | { 70 | s = QTextCharFormat::UnderlineStyle::DashUnderline; 71 | } 72 | else if (underline == "DotLine") 73 | { 74 | s = QTextCharFormat::UnderlineStyle::DotLine; 75 | } 76 | else if (underline == "DashDotLine") 77 | { 78 | s = QTextCharFormat::DashDotLine; 79 | } 80 | else if (underline == "DashDotDotLine") 81 | { 82 | s = QTextCharFormat::DashDotDotLine; 83 | } 84 | else if (underline == "WaveUnderline") 85 | { 86 | s = QTextCharFormat::WaveUnderline; 87 | } 88 | else if (underline == "SpellCheckUnderline") 89 | { 90 | s = QTextCharFormat::SpellCheckUnderline; 91 | } 92 | else 93 | { 94 | qDebug() << "Unknown underline value " << underline; 95 | } 96 | 97 | format.setUnderlineStyle(s); 98 | } 99 | 100 | if (attributes.hasAttribute("underlineColor")) 101 | { 102 | auto color = attributes.value("underlineColor"); 103 | 104 | format.setUnderlineColor(QColor(color.toString())); 105 | } 106 | 107 | m_data[name.toString()] = format; 108 | } 109 | } 110 | } 111 | 112 | m_loaded = !reader.hasError(); 113 | 114 | return m_loaded; 115 | } 116 | 117 | QString QSyntaxStyle::name() const 118 | { 119 | return m_name; 120 | } 121 | 122 | QTextCharFormat QSyntaxStyle::getFormat(const QString &name) const 123 | { 124 | auto result = m_data.find(name); 125 | 126 | if (result == m_data.end()) 127 | { 128 | return QTextCharFormat(); 129 | } 130 | 131 | return result.value(); 132 | } 133 | 134 | bool QSyntaxStyle::isLoaded() const 135 | { 136 | return m_loaded; 137 | } 138 | 139 | QSyntaxStyle *QSyntaxStyle::defaultStyle() 140 | { 141 | static QSyntaxStyle style; 142 | 143 | if (!style.isLoaded()) 144 | { 145 | Q_INIT_RESOURCE(qcodeeditor_resources); 146 | QFile fl(":/default_style.xml"); 147 | 148 | if (!fl.open(QIODevice::ReadOnly)) 149 | { 150 | return &style; 151 | } 152 | 153 | if (!style.load(fl.readAll())) 154 | { 155 | qDebug() << "Can't load default style."; 156 | } 157 | } 158 | 159 | return &style; 160 | } 161 | -------------------------------------------------------------------------------- /src/internal/QXMLHighlighter.cpp: -------------------------------------------------------------------------------- 1 | // QCodeEditor 2 | #include 3 | #include 4 | 5 | QXMLHighlighter::QXMLHighlighter(QTextDocument *document) 6 | : QStyleSyntaxHighlighter(document), m_xmlKeywordRegexes(), 7 | m_xmlElementRegex(R"(<[\s]*[/]?[\s]*([^\n][a-zA-Z-_:]*)(?=[\s/>]))"), m_xmlAttributeRegex(R"(\w+(?=\=))"), 8 | m_xmlValueRegex(R"("[^\n"]+"(?=\??[\s/>]))"), m_xmlCommentBeginRegex(R"()") 9 | { 10 | m_xmlKeywordRegexes << QRegularExpression("<\\?") << QRegularExpression("/>") << QRegularExpression(">") 11 | << QRegularExpression("<") << QRegularExpression("") << QRegularExpression("\\?>"); 12 | 13 | m_startCommentBlockSequence = ""; 15 | } 16 | 17 | void QXMLHighlighter::highlightBlock(const QString &text) 18 | { 19 | // Special treatment for xml element regex as we use captured text to emulate lookbehind 20 | auto matchIterator = m_xmlElementRegex.globalMatch(text); 21 | while (matchIterator.hasNext()) 22 | { 23 | auto match = matchIterator.next(); 24 | 25 | setFormat(match.capturedStart(), match.capturedLength(), 26 | syntaxStyle()->getFormat("Keyword") // XML ELEMENT FORMAT 27 | ); 28 | } 29 | 30 | // Highlight xml keywords *after* xml elements to fix any occasional / captured into the enclosing element 31 | 32 | for (auto &®ex : m_xmlKeywordRegexes) 33 | { 34 | highlightByRegex(syntaxStyle()->getFormat("Keyword"), regex, text); 35 | } 36 | 37 | highlightByRegex(syntaxStyle()->getFormat("Text"), m_xmlAttributeRegex, text); 38 | 39 | setCurrentBlockState(0); 40 | 41 | int startIndex = 0; 42 | if (previousBlockState() != 1) 43 | { 44 | startIndex = text.indexOf(m_xmlCommentBeginRegex); 45 | } 46 | 47 | while (startIndex >= 0) 48 | { 49 | auto match = m_xmlCommentEndRegex.match(text, startIndex); 50 | 51 | int endIndex = match.capturedStart(); 52 | int commentLength = 0; 53 | 54 | if (endIndex == -1) 55 | { 56 | setCurrentBlockState(1); 57 | commentLength = text.length() - startIndex; 58 | } 59 | else 60 | { 61 | commentLength = endIndex - startIndex + match.capturedLength(); 62 | } 63 | 64 | setFormat(startIndex, commentLength, syntaxStyle()->getFormat("Comment")); 65 | 66 | startIndex = text.indexOf(m_xmlCommentBeginRegex, startIndex + commentLength); 67 | } 68 | 69 | highlightByRegex(syntaxStyle()->getFormat("String"), m_xmlValueRegex, text); 70 | } 71 | 72 | void QXMLHighlighter::highlightByRegex(const QTextCharFormat &format, const QRegularExpression ®ex, 73 | const QString &text) 74 | { 75 | auto matchIterator = regex.globalMatch(text); 76 | 77 | while (matchIterator.hasNext()) 78 | { 79 | auto match = matchIterator.next(); 80 | 81 | setFormat(match.capturedStart(), match.capturedLength(), format); 82 | } 83 | } 84 | --------------------------------------------------------------------------------
It was a dark and stormy night.
But then all nights in Trenton seem dark and 45 | stormy to someone who has gone through what 46 | I have.