QCodeEditor - Qt based code editor

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

Keywords: Qt

Added by pbeerman on Tue, 18 Jan 2022 10:45:38 +0200