I. Basic Introduction
QCodeEditor is a code editor that supports autocompletion, syntax highlighting, and line numbers.
It is for anyone who wants to support multiple languages, from programming languages to markup languages, and even custom scripting languages. In its current state, it can handle all the functions mentioned above perfectly, but there are still many things to be realized. QCodeEditor is Qt based QPlainTextEdit, which already includes an interface for adding syntax highlighting and autocompletion.
II. Code usage
Using the editor itself is very simple. You can add a to the form through QPlainTextEdit and promote it to kgl::QCodeEditor (note that "include/KGL/Widgets" must be added to the include variable and the "" check box must be selected for global include) or programmatically add it to the form:
using namespace kgl; // ## MainWindow::MainWindow QCodeEditor *editor = new QCodeEditor; setCentralWidget(editor); // or: ui->someLayout->addWidget(editor);
This class can change the visual appearance of the editor by using QCodeEditorDesign. In the following example, we assume that the code editor is surrounded by multiple widgets, so a border is added to it. We also modified the appearance to have a "dark" style:
using namespace kgl; // ## MainWindow::MainWindow QCodeEditorDesign design; design.setLineColumnVisible(false); design.setEditorBackColor(0xff333333); design.setEditorTextColor(0xffdddddd); design.setEditorBorderColor(0xff999999); design.setEditorBorder(QMargins(1,1,1,1)); // l t r b editor->setDesign(design);
But how to actually add some syntax highlighting rules, as shown in the figure above? There are two ways to do this: add them programmatically or extract them from XML files.
Programmatically:
using namespace kgl; // ## MainWindow::MainWindow QList<QSyntaxRule> rules; QSyntaxRule rule; rule.setForeColor(QColor(Qt::blue)); rule.setRegex("\\bFoo\\b"); rule.setId("Foo"); rules.push_back(rule); editor->setRules(rules);
XML file:
using namespace kgl; // ## MainWindow::MainWindow QCodeEditorDesign design; // modify design ... QList<QSyntaxRule> rules = QSyntaxRules::loadFromFile(":/rule_cpp.xml", design); editor->setRules(rules); // Note: ':/rule_cpp.xml' refers to the path of a QRC resource
The next chapter provides guidance on how to create these XML rules. But first, our editor needs some auto completion Keywords:
// ## MainWindow::MainWindow QStringList keywords = { "printf", "scanf" }; editor->setKeywords(keywords);
If you want to add an icon to imply that the keyword is a function / Member / macro /... QStandardItemModel, you need to create a custom and pass it to "QCodeEditor::setKeywordModel(model)".
Third, create an XML rule file
The XML rule file contains the topmost element composed of multiple child elements. Each child element must contain a regular expression or keyword list, and all other attributes are optional:
<rules> <rule> <regex>\bFoo\b</regex> <keywords>foo bar key</keywords> </rule> </rules>
For a reference to all available attributes, go to rules_template.xml github page. QCodeEditor even supports rules that contain more than one row. While they are useful for implementing multiline annotations, they can also be used for other purposes.
IV. use of ID
From rules_ template. As you can see from the XML, rules can even define custom IDs. In this section, I will explain how to use IDs and why they are so useful. People may have noticed that adding keywords statically is not a good practice, especially if your language allows you to include other files or define variables.
"onMatch" signal
QCodeEditorHighlighteronMatch sends a signal named '' as long as a string - found through regular expression - is highlighted. This enables us to retrieve the string problem:
// ## MainWindow.h using namespace kgl; class MainWindow : public QMainWindow { Q_OBJECT public: ... private slots: void addMacro(const QSyntaxRule &rule, QString seq, QTextBlock block); private: QMap<QTextBlock, QSyntaxRule> m_RuleMap; QMap<QTextBlock, QString> m_MacroMap; QCodeEditor *m_Editor; }; // ## MainWindow::MainWindow QSyntaxRule defineRule; defineRule.setRegex("(#define\\s+)\\K(\\D\\w*)(?=\s+\S+)"); defineRule.setId("define"); editor->setRules({ defineRule }); connect(m_Editor->highlighter(), SIGNAL(onMatch(QSyntaxRule,QString,QTextBlock)), this, SLOT(addMacro(QSyntaxRule,QString,QTextBlock))); // ## MainWindow::addMacro if (rule.id() == "define") { foreach (const QTextBlock &b, m_RuleMap.keys()) { if (b.userData() != NULL && block.userData() != NULL) { auto *d1 = static_cast<QCodeEditorBlockData *>(block.userData()); auto *d2 = static_cast<QCodeEditorBlockData *>(b.userData()); if (d1->id == d2->id) { return; } } } // Not existing yet; add it QString def = seq.split(' ').at(0); m_RuleMap.insert(block, rule); m_MacroMap.insert(block, def); m_Editor->addKeyword(def); }
In this way, you can provide autocomplete for custom classes, variables, and definitions, or include other files and import symbols from them.
"onRemove" signal
Deleting macros that have been added can be tricky because QTextBlock is designed to make it almost impossible for us to track it. QCodeEditorHighlighter provides' onRemove 'signal. Once the highlighter detects that the previously matched rules no longer match, it will send this signal:
// ## MainWindow.h private slots: void addMacro(const QSyntaxRule &rule, QString seq, QTextBlock block); void removeMacro(QCodeEditorBlockData *data); // Add this to the slots // ## MainWindow::MainWindow // Add another connection connect(m_Editor->highlighter(), SIGNAL(onRemove(QCodeEditorBlockData*)), this, SLOT(removeMacro(QCodeEditorBlockData*))); // ## MainWindow::removeMacro foreach (const QTextBlock &b, m_RuleMap.keys()) { if (b.userData()) { auto *d = static_cast<QCodeEditorBlockData *>(b.userData()); if (d->id == data->id) { // Data is the same; block must be the one from before! m_Editor->removeKeyword(m_MacroMap.value(b)); m_RuleMap.remove(b); m_MacroMap.remove(b); } } }
Implementing such a relatively simple signal is a difficult task. See the chapter "points of interest" for more information.
V. compilation description
In order to compile QCodeEditor, you need to define 'KGL'_ Build 'to export symbols to the dynamic library. If you want to build a static library, just define 'KGL'_ STATIC’. Also make sure you are using Qt5 or later.
Six points of concern
One of the biggest obstacles is to render line numbers correctly. While it's easy to add rows and columns as child widgets, it's not so easy to determine all the line numbers visible when scrolling. After reading the Qt document for a long time, I think I can skip to the next line in the iteration and stop the iteration immediately when the current line is no longer visible.
Another big obstacle is indenting multiple selected rows when the tab key is pressed. I solved this problem by using the really amazing 'QTextCursor::movePosition' method, which makes it easy to implement this function (and post indentation).
QTextBlock
Although QTextBlock has amazing functions and possibilities, it still has one weakness: it is almost impossible for us to track QTextBlock in text widgets. In order to implement a signal that allows the deletion of keywords, I first try to copy the problematic QTextBlock, and then use the overloaded '= =' operator to check whether it is equal. This does not work because the line number may change and cause the equation to fail. The only possibility of tracking QTextBlock is to assign QTextBlockUserData to it using the 'setUserData' function. To achieve this, I inherited QTextBlockUserData and stored auuid and the regex string in it. uuid (+ some do while loops) ensures that the block is truly unique throughout the application. Through these measurements, the 'onRemove' signal is finally reliable and error free.
Article reference link: https://www.codeproject.com/Articles/1139741/QCodeEditor-Widget-for-Qt