Add code editor syntax highlighter & validate customized frame parser code

This commit is contained in:
Alex Spataru 2022-06-10 06:42:31 -05:00
parent 52f14939bc
commit c512b6e8f8
23 changed files with 8226 additions and 19 deletions

View File

@ -18,6 +18,7 @@
<file>icons/compass.svg</file>
<file>icons/connect.svg</file>
<file>icons/copy.svg</file>
<file>icons/cut.svg</file>
<file>icons/dashboard.svg</file>
<file>icons/dataset.svg</file>
<file>icons/delete-item.svg</file>
@ -39,6 +40,7 @@
<file>icons/graphs.svg</file>
<file>icons/group.svg</file>
<file>icons/gyro.svg</file>
<file>icons/heart-broken.svg</file>
<file>icons/help.svg</file>
<file>icons/hide-all.svg</file>
<file>icons/info.svg</file>
@ -59,6 +61,7 @@
<file>icons/points.svg</file>
<file>icons/power.svg</file>
<file>icons/ram.svg</file>
<file>icons/redo.svg</file>
<file>icons/refresh.svg</file>
<file>icons/registration.svg</file>
<file>icons/restore.svg</file>
@ -75,7 +78,9 @@
<file>icons/settings.svg</file>
<file>icons/show-all.svg</file>
<file>icons/start-sequence.svg</file>
<file>icons/template.svg</file>
<file>icons/thermometer.svg</file>
<file>icons/undo.svg</file>
<file>icons/up.svg</file>
<file>icons/update.svg</file>
<file>icons/usb.svg</file>
@ -107,12 +112,6 @@
<file>qml/FramelessWindow/Titlebar.qml</file>
<file>qml/FramelessWindow/WindowButton.qml</file>
<file>qml/FramelessWindow/WindowButtonMacOS.qml</file>
<file>qml/ProjectEditor/Footer.qml</file>
<file>qml/ProjectEditor/GroupEditor.qml</file>
<file>qml/ProjectEditor/Header.qml</file>
<file>qml/ProjectEditor/JsonDatasetDelegate.qml</file>
<file>qml/ProjectEditor/JsonGroupDelegate.qml</file>
<file>qml/ProjectEditor/TreeView.qml</file>
<file>qml/Panes/SetupPanes/Devices/BluetoothLE.qml</file>
<file>qml/Panes/SetupPanes/Devices/Network.qml</file>
<file>qml/Panes/SetupPanes/Devices/Serial.qml</file>
@ -126,6 +125,12 @@
<file>qml/PlatformDependent/DecentMenuItem.qml</file>
<file>qml/PlatformDependent/Menubar.qml</file>
<file>qml/PlatformDependent/MenubarMacOS.qml</file>
<file>qml/ProjectEditor/Footer.qml</file>
<file>qml/ProjectEditor/GroupEditor.qml</file>
<file>qml/ProjectEditor/Header.qml</file>
<file>qml/ProjectEditor/JsonDatasetDelegate.qml</file>
<file>qml/ProjectEditor/JsonGroupDelegate.qml</file>
<file>qml/ProjectEditor/TreeView.qml</file>
<file>qml/Widgets/Icon.qml</file>
<file>qml/Widgets/JSONDropArea.qml</file>
<file>qml/Widgets/Shadow.qml</file>
@ -135,9 +140,9 @@
<file>qml/Windows/Acknowledgements.qml</file>
<file>qml/Windows/CsvPlayer.qml</file>
<file>qml/Windows/Donate.qml</file>
<file>qml/Windows/ProjectEditor.qml</file>
<file>qml/Windows/MainWindow.qml</file>
<file>qml/Windows/MQTTConfiguration.qml</file>
<file>qml/Windows/ProjectEditor.qml</file>
<file>qml/main.qml</file>
<file>themes/1_Flat.json</file>
<file>themes/2_Dark.json</file>
@ -173,6 +178,6 @@
<file>window-border/minimize.svg</file>
<file>window-border/restore.svg</file>
<file>window-border/unmaximize.svg</file>
<file>icons/heart-broken.svg</file>
<file>icons/paste.svg</file>
</qresource>
</RCC>

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z"/></svg>
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" /></svg>

Before

Width:  |  Height:  |  Size: 274 B

After

Width:  |  Height:  |  Size: 417 B

1
assets/icons/cut.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M19,3L13,9L15,11L22,4V3M12,12.5A0.5,0.5 0 0,1 11.5,12A0.5,0.5 0 0,1 12,11.5A0.5,0.5 0 0,1 12.5,12A0.5,0.5 0 0,1 12,12.5M6,20A2,2 0 0,1 4,18C4,16.89 4.9,16 6,16A2,2 0 0,1 8,18C8,19.11 7.1,20 6,20M6,8A2,2 0 0,1 4,6C4,4.89 4.9,4 6,4A2,2 0 0,1 8,6C8,7.11 7.1,8 6,8M9.64,7.64C9.87,7.14 10,6.59 10,6A4,4 0 0,0 6,2A4,4 0 0,0 2,6A4,4 0 0,0 6,10C6.59,10 7.14,9.87 7.64,9.64L10,12L7.64,14.36C7.14,14.13 6.59,14 6,14A4,4 0 0,0 2,18A4,4 0 0,0 6,22A4,4 0 0,0 10,18C10,17.41 9.87,16.86 9.64,16.36L12,14L19,21H22V20L9.64,7.64Z" /></svg>

After

Width:  |  Height:  |  Size: 806 B

1
assets/icons/paste.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M19,20H5V4H7V7H17V4H19M12,2A1,1 0 0,1 13,3A1,1 0 0,1 12,4A1,1 0 0,1 11,3A1,1 0 0,1 12,2M19,2H14.82C14.4,0.84 13.3,0 12,0C10.7,0 9.6,0.84 9.18,2H5A2,2 0 0,0 3,4V20A2,2 0 0,0 5,22H19A2,2 0 0,0 21,20V4A2,2 0 0,0 19,2Z" /></svg>

After

Width:  |  Height:  |  Size: 509 B

1
assets/icons/redo.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M18.4,10.6C16.55,9 14.15,8 11.5,8C6.85,8 2.92,11.03 1.54,15.22L3.9,16C4.95,12.81 7.95,10.5 11.5,10.5C13.45,10.5 15.23,11.22 16.62,12.38L13,16H22V7L18.4,10.6Z" /></svg>

After

Width:  |  Height:  |  Size: 452 B

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/></svg>
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z" /></svg>

Before

Width:  |  Height:  |  Size: 280 B

After

Width:  |  Height:  |  Size: 445 B

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M14 2H6C4.89 2 4 2.9 4 4V20C4 21.11 4.89 22 6 22H18C19.11 22 20 21.11 20 20V8L14 2M18 20H6V4H13V9H18V20M9.54 15.65L11.63 17.74L10.35 19L7 15.65L10.35 12.3L11.63 13.56L9.54 15.65M17 15.65L13.65 19L12.38 17.74L14.47 15.65L12.38 13.56L13.65 12.3L17 15.65Z" /></svg>

After

Width:  |  Height:  |  Size: 547 B

1
assets/icons/undo.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M12.5,8C9.85,8 7.45,9 5.6,10.6L2,7V16H11L7.38,12.38C8.77,11.22 10.54,10.5 12.5,10.5C16.04,10.5 19.05,12.81 20.1,16L22.47,15.22C21.08,11.03 17.15,8 12.5,8Z" /></svg>

After

Width:  |  Height:  |  Size: 449 B

View File

@ -27,13 +27,13 @@ along with Foobar. If not, see <http://www.gnu.org/licenses/>
## FFTReal
This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.
## KLed
## KLed
Copyright © 1998 Jörg Habenicht <j.habenicht@europemail.com>
@ -52,6 +52,28 @@ License along with this library; if not, write to the
Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
Boston, MA 02110-1301, USA.
## QSourceHighlite
Copyright (c) 2019-2020 Waqar Ahmed -- <waqar.17a@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## QMapControl
Copyright (C) 2007 - 2008 Kai Winter

1
libs/Libraries.pri vendored
View File

@ -48,6 +48,7 @@ include($$PWD/qmqtt/qmqtt.pri)
include($$PWD/QMapControl/QMapControl.pri)
include($$PWD/QRealFourier/QRealFourier.pri)
include($$PWD/QSimpleUpdater/QSimpleUpdater.pri)
include($$PWD/QSourceHighlite/QSourceHighlite.pri)
macx* {
DEFINES += KDMACTOUCHBAR_BUILD_KDMACTOUCHBAR_SRC

2
libs/QSourceHighlite/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
build-*
*.user

20
libs/QSourceHighlite/LICENSE Executable file
View File

@ -0,0 +1,20 @@
Copyright (c) 2019-2020 Waqar Ahmed -- <waqar.17a@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,11 @@
INCLUDEPATH += $$PWD
HEADERS += \
$$PWD/qsourcehighliter.h \
$$PWD/qsourcehighliterthemes.h \
$$PWD/languagedata.h
SOURCES += \
$$PWD/qsourcehighliter.cpp \
$$PWD/languagedata.cpp \
$$PWD/qsourcehighliterthemes.cpp

65
libs/QSourceHighlite/README.md Executable file
View File

@ -0,0 +1,65 @@
# Background
It started as an internal component of [qmarkdowntextedit](https://github.com/pbek/qmarkdowntextedit) to provide syntax highlighting(kind of like **highlight.js**). Ithttps://github.com/pbek/qmarkdowntextedit is currently being used in QOwnNotes and you can test it there or run the demo here.
It doesn't use any regex because I want it to be fast. It can currently load a 100,000 lines of source code in ~0.4 seconds on my 4th Gen Intel Core i5 4300U.
# Screenshot
![Cpp](screenshot/syntax.png)
# Usage
Add the `.pri` file to your project. Then initialize it like this:
```cpp
highlighter = new QSourceHighliter(plainTextEdit->document());
highlighter->setCurrentLanguage(QSourceHighlighter::CodeCpp);
```
# Themes
Currently there is only one theme 'Monokai' apart from the one that is created during highlighter initialization. More themes will be added soon. You can add more themes in QSourceHighlighterThemes.
## Supported Languages
Currently the following languages are supported (more being added):
- Bash script
- C
- C++
- C#
- CMake
- CSS
- Go
- Html
- INI
- Java
- Javascript
- JSON
- Make
- PHP
- Python
- QML
- Rust
- SQL
- Typescript
- V lang
- XML
- YAML
- Houdini Vex
## Adding more languages
If you want to add a language, collect the language data like keywords and types and add it to the `languagedata.h` file. For some languages it may not work, so create an issue and I will write a separate parser for that language.
## Dependencies
It has no dependency except Qt ofcourse. It should work with any Qt version > 5 but if it fails please create an issue.
## Building
Load the project into Qt Creator and click run.
## LICENSE
MIT License

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,222 @@
/*
* Copyright (c) 2019-2020 Waqar Ahmed -- <waqar.17a@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef QOWNLANGUAGEDATA_H
#define QOWNLANGUAGEDATA_H
template<typename key, typename val>
class QMultiHash;
class QLatin1String;
namespace QSourceHighlite {
using LanguageData = QMultiHash<char, QLatin1String>;
/**********************************************************/
/* LuaData ************************************************/
/**********************************************************/
void loadLuaData(LanguageData &typess,
LanguageData &keywordss,
LanguageData &builtins,
LanguageData &literalss,
LanguageData &others);
/**********************************************************/
/* C/C++ Data *********************************************/
/**********************************************************/
void loadCppData(LanguageData &typess,
LanguageData &keywordss,
LanguageData &builtins,
LanguageData &literalss,
LanguageData &others);
/**********************************************************/
/* Shell Data *********************************************/
/**********************************************************/
void loadShellData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/**********************************************************/
/* JS Data *********************************************/
/**********************************************************/
void loadJSData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/**********************************************************/
/* PHP Data *********************************************/
/**********************************************************/
void loadPHPData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/**********************************************************/
/* QML Data *********************************************/
/**********************************************************/
void loadQMLData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/**********************************************************/
/* Python Data *********************************************/
/**********************************************************/
void loadPythonData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/********************************************************/
/*** Rust DATA ***********************************/
/********************************************************/
void loadRustData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/********************************************************/
/*** Java DATA ***********************************/
/********************************************************/
void loadJavaData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/********************************************************/
/*** C# DATA *************************************/
/********************************************************/
void loadCSharpData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/********************************************************/
/*** Go DATA *************************************/
/********************************************************/
void loadGoData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/********************************************************/
/*** V DATA **************************************/
/********************************************************/
void loadVData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/********************************************************/
/*** SQL DATA ************************************/
/********************************************************/
void loadSQLData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/********************************************************/
/*** JSON DATA ***********************************/
/********************************************************/
void loadJSONData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/********************************************************/
/*** CSS DATA ***********************************/
/********************************************************/
void loadCSSData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/********************************************************/
/*** Typescript DATA *********************************/
/********************************************************/
void loadTypescriptData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/********************************************************/
/*** YAML DATA ***************************************/
/********************************************************/
void loadYAMLData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/********************************************************/
/*** VEX DATA ***************************************/
/********************************************************/
void loadVEXData(LanguageData &types,
LanguageData &keywords,
LanguageData &builtin,
LanguageData &literals,
LanguageData &other);
/********************************************************/
/*** CMake DATA **************************************/
/********************************************************/
void loadCMakeData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** Make DATA ***************************************/
/********************************************************/
void loadMakeData(QMultiHash<char, QLatin1String>& types,
QMultiHash<char, QLatin1String>& keywords,
QMultiHash<char, QLatin1String>& builtin,
QMultiHash<char, QLatin1String>& literals,
QMultiHash<char, QLatin1String>& other);
void loadAsmData(QMultiHash<char, QLatin1String>& types,
QMultiHash<char, QLatin1String>& keywords,
QMultiHash<char, QLatin1String>& builtin,
QMultiHash<char, QLatin1String>& literals,
QMultiHash<char, QLatin1String>& other);
}
#endif

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string/>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Language:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QComboBox" name="langComboBox">
<property name="currentText">
<string/>
</property>
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Theme</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QComboBox" name="themeComboBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="plainTextEdit"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>23</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,940 @@
/*
* Copyright (c) 2019-2020 Waqar Ahmed -- <waqar.17a@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "qsourcehighliter.h"
#include "languagedata.h"
#include "qsourcehighliterthemes.h"
#include <QDebug>
#include <algorithm>
#include <QTextDocument>
namespace QSourceHighlite {
QSourceHighliter::QSourceHighliter(QTextDocument *doc)
: QSyntaxHighlighter(doc),
_language(CodeC)
{
initFormats();
}
QSourceHighliter::QSourceHighliter(QTextDocument *doc, QSourceHighliter::Themes theme)
: QSyntaxHighlighter(doc),
_language(CodeC)
{
setTheme(theme);
}
void QSourceHighliter::initFormats() {
/****************************************
* Formats for syntax highlighting
***************************************/
QTextCharFormat format = QTextCharFormat();
_formats[Token::CodeBlock] = format;
format = QTextCharFormat();
format.setForeground(QColor("#F92672"));
_formats[Token::CodeKeyWord] = format;
format = QTextCharFormat();
format.setForeground(QColor("#a39b4e"));
_formats[Token::CodeString] = format;
format = QTextCharFormat();
format.setForeground(QColor("#75715E"));
_formats[Token::CodeComment] = format;
format = QTextCharFormat();
format.setForeground(QColor("#54aebf"));
_formats[Token::CodeType] = format;
format = QTextCharFormat();
format.setForeground(QColor("#db8744"));
_formats[Token::CodeOther] = format;
format = QTextCharFormat();
format.setForeground(QColor("#AE81FF"));
_formats[Token::CodeNumLiteral] = format;
format = QTextCharFormat();
format.setForeground(QColor("#018a0f"));
_formats[Token::CodeBuiltIn] = format;
}
void QSourceHighliter::setCurrentLanguage(Language language) {
if (language != _language)
_language = language;
}
QSourceHighliter::Language QSourceHighliter::currentLanguage() {
return _language;
}
void QSourceHighliter::setTheme(QSourceHighliter::Themes theme)
{
_formats = QSourceHighliterTheme::theme(theme);
rehighlight();
}
void QSourceHighliter::highlightBlock(const QString &text)
{
if (currentBlock() == document()->firstBlock()) {
setCurrentBlockState(_language);
} else {
previousBlockState() == _language ?
setCurrentBlockState(_language) :
setCurrentBlockState(_language + 1);
}
highlightSyntax(text);
}
/**
* @brief Does the code syntax highlighting
* @param text
*/
void QSourceHighliter::highlightSyntax(const QString &text)
{
if (text.isEmpty()) return;
const auto textLen = text.length();
QChar comment;
bool isCSS = false;
bool isYAML = false;
bool isMake = false;
bool isAsm = false;
bool isSQL = false;
LanguageData keywords{},
others{},
types{},
builtin{},
literals{};
switch (currentBlockState()) {
case CodeLua :
case CodeLuaComment :
loadLuaData(types, keywords, builtin, literals, others);
break;
case CodeCpp :
case CodeCppComment :
loadCppData(types, keywords, builtin, literals, others);
break;
case CodeJs :
case CodeJsComment :
loadJSData(types, keywords, builtin, literals, others);
break;
case CodeC :
case CodeCComment :
loadCppData(types, keywords, builtin, literals, others);
break;
case CodeBash :
loadShellData(types, keywords, builtin, literals, others);
comment = QLatin1Char('#');
break;
case CodePHP :
case CodePHPComment :
loadPHPData(types, keywords, builtin, literals, others);
break;
case CodeQML :
case CodeQMLComment :
loadQMLData(types, keywords, builtin, literals, others);
break;
case CodePython :
loadPythonData(types, keywords, builtin, literals, others);
comment = QLatin1Char('#');
break;
case CodeRust :
case CodeRustComment :
loadRustData(types, keywords, builtin, literals, others);
break;
case CodeJava :
case CodeJavaComment :
loadJavaData(types, keywords, builtin, literals, others);
break;
case CodeCSharp :
case CodeCSharpComment :
loadCSharpData(types, keywords, builtin, literals, others);
break;
case CodeGo :
case CodeGoComment :
loadGoData(types, keywords, builtin, literals, others);
break;
case CodeV :
case CodeVComment :
loadVData(types, keywords, builtin, literals, others);
break;
case CodeSQL :
isSQL = true;
loadSQLData(types, keywords, builtin, literals, others);
break;
case CodeJSON :
loadJSONData(types, keywords, builtin, literals, others);
break;
case CodeXML :
xmlHighlighter(text);
return;
case CodeCSS :
case CodeCSSComment :
isCSS = true;
loadCSSData(types, keywords, builtin, literals, others);
break;
case CodeTypeScript:
case CodeTypeScriptComment:
loadTypescriptData(types, keywords, builtin, literals, others);
break;
case CodeYAML:
isYAML = true;
loadYAMLData(types, keywords, builtin, literals, others);
comment = QLatin1Char('#');
break;
case CodeINI:
comment = QLatin1Char('#');
break;
case CodeVex:
case CodeVexComment:
loadVEXData(types, keywords, builtin, literals, others);
break;
case CodeCMake:
loadCMakeData(types, keywords, builtin, literals, others);
comment = QLatin1Char('#');
break;
case CodeMake:
isMake = true;
loadMakeData(types, keywords, builtin, literals, others);
comment = QLatin1Char('#');
break;
case CodeAsm:
isAsm = true;
loadAsmData(types, keywords, builtin, literals, others);
comment = QLatin1Char('#');
break;
default:
break;
}
// keep the default code block format
// this statement is very slow
// TODO: do this formatting when necessary instead of
// applying it to the whole block in the beginning
setFormat(0, textLen, _formats[CodeBlock]);
auto applyCodeFormat =
[this](int i, const LanguageData &data,
const QString &text, const QTextCharFormat &fmt) -> int {
// check if we are at the beginning OR if this is the start of a word
if (i == 0 || (!text.at(i - 1).isLetterOrNumber() &&
text.at(i-1) != QLatin1Char('_'))) {
const auto wordList = data.values(text.at(i).toLatin1());
for (const QLatin1String &word : wordList) {
// we have a word match check
// 1. if we are at the end
// 2. if we have a complete word
if (word == strMidRef(text, i, word.size()) &&
(i + word.size() == text.length() ||
(!text.at(i + word.size()).isLetterOrNumber() &&
text.at(i + word.size()) != QLatin1Char('_')))) {
setFormat(i, word.size(), fmt);
i += word.size();
}
}
}
return i;
};
const QTextCharFormat &formatType = _formats[CodeType];
const QTextCharFormat &formatKeyword = _formats[CodeKeyWord];
const QTextCharFormat &formatComment = _formats[CodeComment];
const QTextCharFormat &formatNumLit = _formats[CodeNumLiteral];
const QTextCharFormat &formatBuiltIn = _formats[CodeBuiltIn];
const QTextCharFormat &formatOther = _formats[CodeOther];
for (int i = 0; i < textLen; ++i) {
if (currentBlockState() % 2 != 0) goto Comment;
while (i < textLen && !text[i].isLetter()) {
if (text[i].isSpace()) {
++i;
//make sure we don't cross the bound
if (i == textLen) return;
if (text[i].isLetter()) break;
else continue;
}
//inline comment
if (comment.isNull() && text[i] == QLatin1Char('/')) {
if((i+1) < textLen){
if(text[i+1] == QLatin1Char('/')) {
setFormat(i, textLen, formatComment);
return;
} else if(text[i+1] == QLatin1Char('*')) {
Comment:
int next = text.indexOf(QLatin1String("*/"));
if (next == -1) {
//we didn't find a comment end.
//Check if we are already in a comment block
if (currentBlockState() % 2 == 0)
setCurrentBlockState(currentBlockState() + 1);
setFormat(i, textLen, formatComment);
return;
} else {
//we found a comment end
//mark this block as code if it was previously comment
//first check if the comment ended on the same line
//if modulo 2 is not equal to zero, it means we are in a comment
//-1 will set this block's state as language
if (currentBlockState() % 2 != 0) {
setCurrentBlockState(currentBlockState() - 1);
}
next += 2;
setFormat(i, next - i, formatComment);
i = next;
if (i >= textLen) return;
}
}
}
} else if (isSQL && comment.isNull() && text[i] == QLatin1Char('-')) {
if((i+1) < textLen){
if(text[i+1] == QLatin1Char('-')) {
setFormat(i, textLen, formatComment);
return;
}
}
} else if (text[i] == comment) {
setFormat(i, textLen, formatComment);
i = textLen;
//integer literal
} else if (text[i].isNumber()) {
i = highlightNumericLiterals(text, i);
//string literals
} else if (text[i] == QLatin1Char('\"')) {
i = highlightStringLiterals('\"', text, i);
} else if (text[i] == QLatin1Char('\'')) {
i = highlightStringLiterals('\'', text, i);
}
if (i >= textLen) {
break;
}
++i;
}
const int pos = i;
if (i == textLen || !text[i].isLetter()) continue;
/* Highlight Types */
i = applyCodeFormat(i, types, text, formatType);
/************************************************
next letter is usually a space, in that case
going forward is useless, so continue;
We can ++i here and go to the beginning of the next word
so that the next formatter can check for formatting but this will
cause problems in case the next word is also of 'Type' or the current
type(keyword/builtin). We can work around it and reset the value of i
in the beginning of the loop to the word's first letter but I am not
sure about its efficiency yet.
************************************************/
if (i == textLen || !text[i].isLetter()) continue;
/* Highlight Keywords */
i = applyCodeFormat(i, keywords, text, formatKeyword);
if (i == textLen || !text[i].isLetter()) continue;
/* Highlight Literals (true/false/NULL,nullptr) */
i = applyCodeFormat(i, literals, text, formatNumLit);
if (i == textLen || !text[i].isLetter()) continue;
/* Highlight Builtin library stuff */
i = applyCodeFormat(i, builtin, text, formatBuiltIn);
if (i == textLen || !text[i].isLetter()) continue;
/* Highlight other stuff (preprocessor etc.) */
if (( i == 0 || !text.at(i-1).isLetter()) && others.contains(text[i].toLatin1())) {
const QList<QLatin1String> wordList = others.values(text[i].toLatin1());
for(const QLatin1String &word : wordList) {
if (word == strMidRef(text, i, word.size()) // we have a word match
&&
(i + word.size() == text.length() // check if we are at the end
||
!text.at(i + word.size()).isLetter()) //OR if we have a complete word
) {
currentBlockState() == CodeCpp ?
setFormat(i - 1, word.size() + 1, formatOther) :
setFormat(i, word.size(), formatOther);
i += word.size();
}
}
}
//we were unable to find any match, lets skip this word
if (pos == i) {
int count = i;
while (count < textLen) {
if (!text[count].isLetter()) break;
++count;
}
i = count;
}
}
if (isCSS) cssHighlighter(text);
if (isYAML) ymlHighlighter(text);
if (isMake) makeHighlighter(text);
if (isAsm) asmHighlighter(text);
}
/**
* @brief Highlight string literals in code
* @param strType str type i.e., ' or "
* @param text the text being scanned
* @param i pos of i in loop
* @return pos of i after the string
*/
int QSourceHighliter::highlightStringLiterals(const QChar strType, const QString &text, int i) {
setFormat(i, 1, _formats[CodeString]);
++i;
while (i < text.length()) {
//look for string end
//make sure it's not an escape seq
if (text.at(i) == strType && text.at(i-1) != QLatin1Char('\\')) {
setFormat(i, 1, _formats[CodeString]);
++i;
break;
}
//look for escape sequence
if (text.at(i) == QLatin1Char('\\') && (i+1) < text.length()) {
int len = 0;
switch(text.at(i+1).toLatin1()) {
case 'a':
case 'b':
case 'e':
case 'f':
case 'n':
case 'r':
case 't':
case 'v':
case '\'':
case '"':
case '\\':
case '\?':
//2 because we have to highlight \ as well as the following char
len = 2;
break;
//octal esc sequence \123
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
{
if (i + 4 <= text.length()) {
bool isCurrentOctal = true;
if (!isOctal(text.at(i+2).toLatin1())) {
isCurrentOctal = false;
break;
}
if (!isOctal(text.at(i+3).toLatin1())) {
isCurrentOctal = false;
break;
}
len = isCurrentOctal ? 4 : 0;
}
break;
}
//hex numbers \xFA
case 'x':
{
if (i + 3 <= text.length()) {
bool isCurrentHex = true;
if (!isHex(text.at(i+2).toLatin1())) {
isCurrentHex = false;
break;
}
if (!isHex(text.at(i+3).toLatin1())) {
isCurrentHex = false;
break;
}
len = isCurrentHex ? 4 : 0;
}
break;
}
//TODO: implement unicode code point escaping
default:
break;
}
//if len is zero, that means this wasn't an esc seq
//increment i so that we skip this backslash
if (len == 0) {
setFormat(i, 1, _formats[CodeString]);
++i;
continue;
}
setFormat(i, len, _formats[CodeNumLiteral]);
i += len;
continue;
}
setFormat(i, 1, _formats[CodeString]);
++i;
}
return i;
}
/**
* @brief Highlight number literals in code
* @param text the text being scanned
* @param i pos of i in loop
* @return pos of i after the number
*/
int QSourceHighliter::highlightNumericLiterals(const QString &text, int i)
{
bool isPreAllowed = false;
if (i == 0) isPreAllowed = true;
else {
//these values are allowed before a number
switch(text.at(i - 1).toLatin1()) {
//css number
case ':':
if (currentBlockState() == CodeCSS)
isPreAllowed = true;
break;
case '$':
if (currentBlockState() == CodeAsm)
isPreAllowed = true;
break;
case '[':
case '(':
case '{':
case ' ':
case ',':
case '=':
case '+':
case '-':
case '*':
case '/':
case '%':
case '<':
case '>':
isPreAllowed = true;
break;
}
}
if (!isPreAllowed) return i;
const int start = i;
if ((i+1) >= text.length()) {
setFormat(i, 1, _formats[CodeNumLiteral]);
return ++i;
}
++i;
//hex numbers highlighting (only if there's a preceding zero)
if (text.at(i) == QChar('x') && text.at(i - 1) == QChar('0'))
++i;
while (i < text.length()) {
if (!text.at(i).isNumber() && text.at(i) != QChar('.') &&
text.at(i) != QChar('e')) //exponent
break;
++i;
}
bool isPostAllowed = false;
if (i == text.length()) {
//cant have e at the end
if (text.at(i - 1) != QChar('e'))
isPostAllowed = true;
} else {
//these values are allowed after a number
switch(text.at(i).toLatin1()) {
case ']':
case ')':
case '}':
case ' ':
case ',':
case '=':
case '+':
case '-':
case '*':
case '/':
case '%':
case '>':
case '<':
case ';':
isPostAllowed = true;
break;
// for 100u, 1.0F
case 'p':
if (currentBlockState() == CodeCSS)
if (i + 1 < text.length() && text.at(i+1) == QChar('x')) {
if (i + 2 == text.length() || !text.at(i+2).isLetterOrNumber())
isPostAllowed = true;
}
break;
case 'e':
if (currentBlockState() == CodeCSS)
if (i + 1 < text.length() && text.at(i+1) == QChar('m')) {
if (i + 2 == text.length() || !text.at(i+2).isLetterOrNumber())
isPostAllowed = true;
}
break;
case 'u':
case 'l':
case 'f':
case 'U':
case 'L':
case 'F':
if (i + 1 == text.length() || !text.at(i+1).isLetterOrNumber()) {
isPostAllowed = true;
++i;
}
break;
}
}
if (isPostAllowed) {
int end = i;
setFormat(start, end - start, _formats[CodeNumLiteral]);
}
//decrement so that the index is at the last number, not after it
return --i;
}
/**
* @brief The YAML highlighter
* @param text
* @details This function post processes a line after the main syntax
* highlighter has run for additional highlighting. It does these things
*
* If the current line is a comment, skip it
*
* Highlight all the words that have a colon after them as 'keyword' except:
* If the word is a string, skip it.
* If the colon is in between a path, skip it (C:\)
*
* Once the colon is found, the function will skip every character except 'h'
*
* If an h letter is found, check the next 4/5 letters for http/https and
* highlight them as a link (underlined)
*/
void QSourceHighliter::ymlHighlighter(const QString &text) {
if (text.isEmpty()) return;
const auto textLen = text.length();
bool colonNotFound = false;
//if this is a comment don't do anything and just return
if (text.trimmed().at(0) == QLatin1Char('#'))
return;
for (int i = 0; i < textLen; ++i) {
if (!text.at(i).isLetter()) continue;
if (colonNotFound && text.at(i) != QLatin1Char('h')) continue;
//we found a string literal, skip it
if (i != 0 && (text.at(i-1) == QLatin1Char('"') || text.at(i-1) == QLatin1Char('\''))) {
const int next = text.indexOf(text.at(i-1), i);
if (next == -1) break;
i = next;
continue;
}
const int colon = text.indexOf(QLatin1Char(':'), i);
//if colon isn't found, we set this true
if (colon == -1) colonNotFound = true;
if (!colonNotFound) {
//if the line ends here, format and return
if (colon+1 == textLen) {
setFormat(i, colon - i, _formats[CodeKeyWord]);
return;
} else {
//colon is found, check if it isn't some path or something else
if (!(text.at(colon+1) == QLatin1Char('\\') && text.at(colon+1) == QLatin1Char('/'))) {
setFormat(i, colon - i, _formats[CodeKeyWord]);
}
}
}
//underlined links
if (text.at(i) == QLatin1Char('h')) {
if (strMidRef(text, i, 5) == QLatin1String("https") ||
strMidRef(text, i, 4) == QLatin1String("http")) {
int space = text.indexOf(QChar(' '), i);
if (space == -1) space = textLen;
QTextCharFormat f = _formats[CodeString];
f.setUnderlineStyle(QTextCharFormat::SingleUnderline);
setFormat(i, space - i, f);
i = space;
}
}
}
}
void QSourceHighliter::cssHighlighter(const QString &text)
{
if (text.isEmpty()) return;
const auto textLen = text.length();
for (int i = 0; i<textLen; ++i) {
if (text[i] == QLatin1Char('.') || text[i] == QLatin1Char('#')) {
if (i+1 >= textLen) return;
if (text[i + 1].isSpace() || text[i+1].isNumber()) continue;
int space = text.indexOf(QLatin1Char(' '), i);
if (space < 0) {
space = text.indexOf('{');
if (space < 0) {
space = textLen;
}
}
setFormat(i, space - i, _formats[CodeKeyWord]);
i = space;
} else if (text[i] == QLatin1Char('c')) {
if (strMidRef(text, i, 5) == QLatin1String("color")) {
i += 5;
int colon = text.indexOf(QLatin1Char(':'), i);
if (colon < 0) continue;
i = colon;
i++;
while(i < textLen) {
if (!text[i].isSpace()) break;
i++;
}
int semicolon = text.indexOf(QLatin1Char(';'));
if (semicolon < 0) semicolon = textLen;
const QString color = text.mid(i, semicolon-i);
QTextCharFormat f = _formats[CodeBlock];
QColor c(color);
if (color.startsWith(QLatin1String("rgb"))) {
int t = text.indexOf(QLatin1Char('('), i);
int rPos = text.indexOf(QLatin1Char(','), t);
int gPos = text.indexOf(QLatin1Char(','), rPos+1);
int bPos = text.indexOf(QLatin1Char(')'), gPos);
if (rPos > -1 && gPos > -1 && bPos > -1) {
const auto r = strMidRef(text, t+1, rPos - (t+1));
const auto g = strMidRef(text, rPos+1, gPos - (rPos + 1));
const auto b = strMidRef(text, gPos+1, bPos - (gPos+1));
c.setRgb(r.toInt(), g.toInt(), b.toInt());
} else {
c = _formats[CodeBlock].background().color();
}
}
if (!c.isValid()) {
continue;
}
int lightness{};
QColor foreground;
//really dark
if (c.lightness() <= 20) {
foreground = Qt::white;
} else if (c.lightness() > 20 && c.lightness() <= 51){
foreground = QColor("#ccc");
} else if (c.lightness() > 51 && c.lightness() <= 78){
foreground = QColor("#bbb");
} else if (c.lightness() > 78 && c.lightness() <= 110){
foreground = QColor("#bbb");
} else if (c.lightness() > 127) {
lightness = c.lightness() + 100;
foreground = c.darker(lightness);
}
else {
lightness = c.lightness() + 100;
foreground = c.lighter(lightness);
}
f.setBackground(c);
f.setForeground(foreground);
setFormat(i, semicolon - i, QTextCharFormat()); //clear prev format
setFormat(i, semicolon - i, f);
i = semicolon;
}
}
}
}
void QSourceHighliter::xmlHighlighter(const QString &text) {
if (text.isEmpty()) return;
const auto textLen = text.length();
setFormat(0, textLen, _formats[CodeBlock]);
for (int i = 0; i < textLen; ++i) {
if (text[i] == QLatin1Char('<') && text[i+1] != QLatin1Char('!')) {
const int found = text.indexOf(QLatin1Char('>'), i);
if (found > 0) {
++i;
if (text[i] == QLatin1Char('/')) ++i;
setFormat(i, found - i, _formats[CodeKeyWord]);
}
}
if (text[i] == QLatin1Char('=')) {
int lastSpace = text.lastIndexOf(QLatin1Char(' '), i);
if (lastSpace == i-1) lastSpace = text.lastIndexOf(QLatin1Char(' '), i-2);
if (lastSpace > 0) {
setFormat(lastSpace, i - lastSpace, _formats[CodeBuiltIn]);
}
}
if (text[i] == QLatin1Char('\"')) {
const int pos = i;
int cnt = 1;
++i;
//bound check
if ( (i+1) >= textLen) return;
while (i < textLen) {
if (text[i] == QLatin1Char('\"')) {
++cnt;
++i;
break;
}
++i; ++cnt;
//bound check
if ( (i+1) >= textLen) {
++cnt;
break;
}
}
setFormat(pos, cnt, _formats[CodeString]);
}
}
}
void QSourceHighliter::makeHighlighter(const QString &text)
{
int colonPos = text.indexOf(QLatin1Char(':'));
if (colonPos == -1)
return;
setFormat(0, colonPos, _formats[Token::CodeBuiltIn]);
}
/**
* @brief highlight inline labels such as 'func()' in "call func()"
* @param text
*/
void QSourceHighliter::highlightInlineAsmLabels(const QString &text)
{
#define Q(s) QStringLiteral(s)
static const QString jumps[27] = {
//0 - 19
Q("jmp"), Q("je"), Q("jne"), Q("jz"), Q("jnz"), Q("ja"), Q("jb"), Q("jg"), Q("jge"), Q("jae"), Q("jl"), Q("jle"),
Q("jbe"), Q("jo"), Q("jno"), Q("js"), Q("jns"), Q("jcxz"), Q("jecxz"), Q("jrcxz"),
//20 - 24
Q("loop"), Q("loope"), Q("loopne"), Q("loopz"), Q("loopnz"),
//25 - 26
Q("call"), Q("callq")
};
#undef Q
auto format = _formats[Token::CodeBuiltIn];
format.setFontUnderline(true);
const QString trimmed = text.trimmed();
int start = -1;
int end = -1;
char c{};
if (!trimmed.isEmpty())
c = trimmed.at(0).toLatin1();
if (c == 'j') {
start = 0; end = 20;
} else if (c == 'c') {
start = 25; end = 27;
} else if (c == 'l') {
start = 20; end = 25;
} else {
return;
}
auto skipSpaces = [&text](int& j){
while (text.at(j).isSpace()) j++;
return j;
};
for (int i = start; i < end; ++i) {
if (trimmed.startsWith(jumps[i])) {
int j = 0;
skipSpaces(j);
j = j + jumps[i].length() + 1;
skipSpaces(j);
int len = text.length() - j;
setFormat(j, len, format);
}
}
}
void QSourceHighliter::asmHighlighter(const QString& text)
{
highlightInlineAsmLabels(text);
//label highlighting
//examples:
//L1:
//LFB1: # local func begin
//
//following e.gs are not a label
//mov %eax, Count::count(%rip)
//.string ": #%s"
//look for the last occurence of a colon
int colonPos = text.lastIndexOf(QLatin1Char(':'));
if (colonPos == -1)
return;
//check if this colon is in a comment maybe?
bool isComment = text.lastIndexOf('#', colonPos) != -1;
if (isComment) {
int commentPos = text.lastIndexOf('#', colonPos);
colonPos = text.lastIndexOf(':', commentPos);
}
auto format = _formats[Token::CodeBuiltIn];
format.setFontUnderline(true);
if (colonPos >= text.length() - 1) {
setFormat(0, colonPos, format);
}
int i = 0;
bool isLabel = true;
for (i = colonPos + 1; i < text.length(); ++i) {
if (!text.at(i).isSpace()) {
isLabel = false;
break;
}
}
if (!isLabel && i < text.length() && text.at(i) == QLatin1Char('#'))
setFormat(0, colonPos, format);
}
}

View File

@ -0,0 +1,169 @@
/*
* Copyright (c) 2019-2020 Waqar Ahmed -- <waqar.17a@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef QSOURCEHIGHLITER_H
#define QSOURCEHIGHLITER_H
#include <QSyntaxHighlighter>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QStringView>
#endif
namespace QSourceHighlite {
class QSourceHighliter : public QSyntaxHighlighter
{
public:
enum Themes {
Monokai = 1
};
explicit QSourceHighliter(QTextDocument *doc);
QSourceHighliter(QTextDocument *doc, Themes theme);
//languages
/*********
* When adding a language make sure that its value is a multiple of 2
* This is because we use the next number as comment for that language
* In case the language doesn't support multiline comments in the traditional C++
* sense, leave the next value empty. Otherwise mark the next value as comment for
* that language.
* e.g
* CodeCpp = 200
* CodeCppComment = 201
*/
enum Language {
//languages
CodeCpp = 200,
CodeCppComment = 201,
CodeJs = 202,
CodeJsComment = 203,
CodeC = 204,
CodeCComment = 205,
CodeBash = 206,
CodePHP = 208,
CodePHPComment = 209,
CodeQML = 210,
CodeQMLComment = 211,
CodePython = 212,
CodeRust = 214,
CodeRustComment = 215,
CodeJava = 216,
CodeJavaComment = 217,
CodeCSharp = 218,
CodeCSharpComment = 219,
CodeGo = 220,
CodeGoComment = 221,
CodeV = 222,
CodeVComment = 223,
CodeSQL = 224,
CodeJSON = 226,
CodeXML = 228,
CodeCSS = 230,
CodeCSSComment = 231,
CodeTypeScript = 232,
CodeTypeScriptComment = 233,
CodeYAML = 234,
CodeINI = 236,
CodeVex = 238,
CodeVexComment = 239,
CodeCMake = 240,
CodeMake = 242,
CodeAsm = 244,
CodeLua = 246,
CodeLuaComment = 247
};
Q_ENUM(Language)
enum Token {
CodeBlock,
CodeKeyWord,
CodeString,
CodeComment,
CodeType,
CodeOther,
CodeNumLiteral,
CodeBuiltIn,
};
Q_ENUM(Token)
void setCurrentLanguage(Language language);
Q_REQUIRED_RESULT Language currentLanguage();
void setTheme(Themes theme);
protected:
void highlightBlock(const QString &text) override;
private:
void highlightSyntax(const QString &text);
Q_REQUIRED_RESULT int highlightNumericLiterals(const QString &text, int i);
Q_REQUIRED_RESULT int highlightStringLiterals(const QChar strType, const QString &text, int i);
/**
* @brief returns true if c is octal
* @param c the char being checked
* @returns true if the number is octal, false otherwise
*/
Q_REQUIRED_RESULT static constexpr inline bool isOctal(const char c) {
return (c >= '0' && c <= '7');
}
/**
* @brief returns true if c is hex
* @param c the char being checked
* @returns true if the number is hex, false otherwise
*/
Q_REQUIRED_RESULT static constexpr inline bool isHex(const char c) {
return (
(c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F')
);
}
void cssHighlighter(const QString &text);
void ymlHighlighter(const QString &text);
void xmlHighlighter(const QString &text);
void makeHighlighter(const QString &text);
void highlightInlineAsmLabels(const QString& text);
void asmHighlighter(const QString& text);
void initFormats();
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
static inline QStringView strMidRef(const QString& str, qsizetype position, qsizetype n = -1)
{
return QStringView(str).mid(position, n);
}
#else
static inline QStringRef strMidRef(const QString& str, int position, int n = -1)
{
return str.midRef(position, n);
}
#endif
QHash<Token, QTextCharFormat> _formats;
Language _language;
};
}
#endif // QSOURCEHIGHLITER_H

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2019-2020 Waqar Ahmed -- <waqar.17a@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "qsourcehighliterthemes.h"
namespace QSourceHighlite {
static QHash<QSourceHighliter::Token, QTextCharFormat> formats()
{
QHash<QSourceHighliter::Token, QTextCharFormat> _formats;
QTextCharFormat defaultFormat = QTextCharFormat();
_formats[QSourceHighliter::Token::CodeBlock] = defaultFormat;
_formats[QSourceHighliter::Token::CodeKeyWord] = defaultFormat;
_formats[QSourceHighliter::Token::CodeString] = defaultFormat;
_formats[QSourceHighliter::Token::CodeComment] = defaultFormat;
_formats[QSourceHighliter::Token::CodeType] = defaultFormat;
_formats[QSourceHighliter::Token::CodeOther] = defaultFormat;
_formats[QSourceHighliter::Token::CodeNumLiteral] = defaultFormat;
_formats[QSourceHighliter::Token::CodeBuiltIn] = defaultFormat;
return _formats;
}
static QHash<QSourceHighliter::Token, QTextCharFormat> monokai()
{
QHash<QSourceHighliter::Token, QTextCharFormat> _formats = formats();
_formats[QSourceHighliter::Token::CodeBlock].setForeground(QColor(227, 226, 214));
_formats[QSourceHighliter::Token::CodeKeyWord].setForeground(QColor(249, 38, 114));
_formats[QSourceHighliter::Token::CodeString].setForeground(QColor(230, 219, 116));
_formats[QSourceHighliter::Token::CodeComment].setForeground(QColor(117, 113, 94));
_formats[QSourceHighliter::Token::CodeType].setForeground(QColor(102, 217, 239));
_formats[QSourceHighliter::Token::CodeOther].setForeground(QColor(249, 38, 114));
_formats[QSourceHighliter::Token::CodeNumLiteral].setForeground(QColor(174, 129, 255));
_formats[QSourceHighliter::Token::CodeBuiltIn].setForeground(QColor(166, 226, 46));
return _formats;
}
QHash<QSourceHighliter::Token, QTextCharFormat>
QSourceHighliterTheme::theme(QSourceHighliter::Themes theme) {
switch (theme) {
case QSourceHighliter::Themes::Monokai:
return monokai();
default:
return {};
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2019-2020 Waqar Ahmed -- <waqar.17a@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef QSOURCEHIGHLITERTHEMES_H
#define QSOURCEHIGHLITERTHEMES_H
#include "qsourcehighliter.h"
namespace QSourceHighlite {
namespace QSourceHighliterTheme
{
QHash<QSourceHighliter::Token, QTextCharFormat> theme(QSourceHighliter::Themes);
} // namespace QSourceHighliterTheme
} // namespace QSourceHighlite
#endif // QSOURCEHIGHLITERTHEMES_H

View File

@ -22,6 +22,12 @@
#include "CodeEditor.h"
#include <QFile>
#include <QJSEngine>
#include <QFileDialog>
#include <Misc/Utilities.h>
#include <Misc/ThemeManager.h>
static const QString DEFAULT_CODE
= "/* \n"
" * Frame parsing function, you can modify this to suit your\n"
@ -42,15 +48,67 @@ static const QString DEFAULT_CODE
Project::CodeEditor::CodeEditor()
{
setWindowTitle(tr("Frame parser code"));
m_textEdit.setPlainText(DEFAULT_CODE);
// Setup syntax highlighter
m_highlighter = new QSourceHighlite::QSourceHighliter(m_textEdit.document());
m_highlighter->setCurrentLanguage(QSourceHighlite::QSourceHighliter::CodeJs);
// Setup text editor
onNewClicked();
m_textEdit.setFont(QFont("Roboto Mono"));
auto layout = new QVBoxLayout(this);
layout->addWidget(&m_textEdit);
// Setup toolbar
auto acNew = m_toolbar.addAction(QIcon(":/icons/template.svg"), tr("New"));
auto acOpen = m_toolbar.addAction(QIcon(":/icons/open.svg"), tr("Open"));
auto acSave = m_toolbar.addAction(QIcon(":/icons/save.svg"), tr("Save"));
m_toolbar.addSeparator();
auto acUndo = m_toolbar.addAction(QIcon(":/icons/undo.svg"), tr("Undo"));
auto acRedo = m_toolbar.addAction(QIcon(":/icons/redo.svg"), tr("Redo"));
m_toolbar.addSeparator();
auto acCut = m_toolbar.addAction(QIcon(":/icons/cut.svg"), tr("Cut"));
auto acCopy = m_toolbar.addAction(QIcon(":/icons/copy.svg"), tr("Copy"));
auto acPaste = m_toolbar.addAction(QIcon(":/icons/paste.svg"), tr("Paste"));
m_toolbar.addSeparator();
auto acHelp = m_toolbar.addAction(QIcon(":/icons/help.svg"), tr("Help"));
// Connect action signals/slots
connect(acUndo, &QAction::triggered, &m_textEdit, &QPlainTextEdit::undo);
connect(acRedo, &QAction::triggered, &m_textEdit, &QPlainTextEdit::redo);
connect(acCut, &QAction::triggered, &m_textEdit, &QPlainTextEdit::cut);
connect(acCopy, &QAction::triggered, &m_textEdit, &QPlainTextEdit::copy);
connect(acPaste, &QAction::triggered, &m_textEdit, &QPlainTextEdit::paste);
connect(acNew, &QAction::triggered, this, &Project::CodeEditor::onNewClicked);
connect(acNew, &QAction::triggered, this, &Project::CodeEditor::onNewClicked);
connect(acOpen, &QAction::triggered, this, &Project::CodeEditor::onOpenClicked);
connect(acSave, &QAction::triggered, this, &Project::CodeEditor::onSaveClicked);
connect(acHelp, &QAction::triggered, this, &Project::CodeEditor::onHelpClicked);
// Set widget palette
QPalette palette;
auto theme = &Misc::ThemeManager::instance();
palette.setColor(QPalette::Text, theme->consoleText());
palette.setColor(QPalette::Base, theme->consoleBase());
palette.setColor(QPalette::Button, theme->consoleButton());
palette.setColor(QPalette::Window, theme->consoleWindow());
palette.setColor(QPalette::Highlight, theme->consoleHighlight());
palette.setColor(QPalette::HighlightedText, theme->consoleHighlightedText());
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
palette.setColor(QPalette::PlaceholderText, theme->consolePlaceholderText());
#endif
m_textEdit.setPalette(palette);
// Setup layout
auto layout = new QVBoxLayout(this);
layout->addWidget(&m_toolbar);
layout->addWidget(&m_textEdit);
layout->setStretch(0, 0);
layout->setStretch(1, 1);
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
// Setup window
setMinimumSize(QSize(640, 480));
setWindowTitle(tr("Customize frame parser"));
}
Project::CodeEditor::~CodeEditor() { }
@ -70,3 +128,155 @@ void Project::CodeEditor::displayWindow()
{
showNormal();
}
void Project::CodeEditor::onNewClicked()
{
// Document has been modified, ask user if he/she wants to continue
if (m_textEdit.document()->isModified())
{
auto ret = Misc::Utilities::showMessageBox(
tr("The document has been modified!"),
tr("Are you sure you want to continue?"), qAppName(),
QMessageBox::Yes | QMessageBox::No);
if (ret == QMessageBox::No)
return;
}
// Load default template
m_textEdit.setPlainText(DEFAULT_CODE);
save(true);
}
void Project::CodeEditor::onOpenClicked()
{
// Document has been modified, ask user if he/she wants to continue
if (m_textEdit.document()->isModified())
{
auto ret = Misc::Utilities::showMessageBox(
tr("The document has been modified!"),
tr("Are you sure you want to continue?"), qAppName(),
QMessageBox::Yes | QMessageBox::No);
if (ret == QMessageBox::No)
return;
}
// Get file from system
auto path = QFileDialog::getOpenFileName(
Q_NULLPTR, tr("Select Javascript file to import"), QDir::homePath(), "*.js");
// Load file into code editor
if (!path.isEmpty())
{
QFile file(path);
if (file.open(QFile::ReadOnly))
{
auto data = file.readAll();
m_textEdit.setPlainText(QString::fromUtf8(data));
save(true);
}
}
}
void Project::CodeEditor::onSaveClicked()
{
if (save(false))
close();
}
void Project::CodeEditor::onHelpClicked() { }
bool Project::CodeEditor::save(const bool silent)
{
// Validate code
QJSEngine engine;
QStringList errors;
QJSValueList args = { "", "," };
auto fun = engine.evaluate("(" + m_textEdit.toPlainText() + ")", "", 1, &errors);
auto ret = fun.call(args);
// Error on engine evaluation
if (!errors.isEmpty())
{
Misc::Utilities::showMessageBox(tr("Frame parser syntax error!"),
tr("Error on line %1.").arg(errors.first()));
return false;
}
// Error on function execution
else if (ret.isError())
{
QString errorStr;
switch (ret.errorType())
{
case QJSValue::GenericError:
errorStr = tr("Generic error");
break;
case QJSValue::EvalError:
errorStr = tr("Evaluation error");
break;
case QJSValue::RangeError:
errorStr = tr("Range error");
break;
case QJSValue::ReferenceError:
errorStr = tr("Reference error");
break;
case QJSValue::SyntaxError:
errorStr = tr("Syntax error");
break;
case QJSValue::TypeError:
errorStr = tr("Type error");
break;
case QJSValue::URIError:
errorStr = tr("URI error");
break;
default:
errorStr = tr("Unknown error");
break;
}
Misc::Utilities::showMessageBox(tr("Frame parser error detected!"), errorStr);
return false;
}
// Update text edit
m_textEdit.document()->setModified(false);
// Show save messagebox
if (!silent)
Misc::Utilities::showMessageBox(tr("Frame parser code updated successfully!"),
tr("No errors have been detected in the code."));
// Everything good
return true;
}
void Project::CodeEditor::closeEvent(QCloseEvent *event)
{
if (m_textEdit.document()->isModified())
{
// Ask user if he/she wants to save changes
auto ret = Misc::Utilities::showMessageBox(
tr("The document has been modified!"), tr("Do you want to save the changes?"),
qAppName(), QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel);
// User wants to save changes, validate code & apply
if (ret == QMessageBox::Yes)
{
if (!save(true))
{
event->ignore();
return;
}
}
// User wants to continue editing the code
if (ret == QMessageBox::Cancel)
{
event->ignore();
return;
}
}
// User saved changes (with no errors) or discarded the changes
event->accept();
}

View File

@ -24,11 +24,14 @@
#include <QObject>
#include <QDialog>
#include <QToolBar>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QSourceHighlite/qsourcehighliter.h>
namespace Project
{
class CodeEditor : public QDialog
@ -50,7 +53,20 @@ public:
public Q_SLOTS:
void displayWindow();
private Q_SLOTS:
void onNewClicked();
void onOpenClicked();
void onSaveClicked();
void onHelpClicked();
private:
bool checkModified();
bool save(const bool silent = false);
void closeEvent(QCloseEvent *event) override;
private:
QToolBar m_toolbar;
QPlainTextEdit m_textEdit;
QSourceHighlite::QSourceHighliter *m_highlighter;
};
}