使用QPlainTextEdit完成一个代码编辑器

发布于:2022-12-09 ⋅ 阅读:(1706) ⋅ 点赞:(0)

使用QPlainTextEdit完成一个代码编辑器

关键词:Qt: QPlainTextEdit、CommentHighlighter、QCompleter

一个代码编辑器基本要素如下:

  • 文本编辑
  • 显示行号
  • 高亮编辑去
  • 代码自动补全

文本编辑

文本编辑肯定是一个编辑器的最基本的要素。在Qt中,有QTextEdit和QPlainTextEdit都是可以进行文本编辑的控件。但是,QPlainTextEdit控件对纯文本的支持进行了优化,并且对大文本支持比QTextEdit好,性能更优秀,因此,这里选择QPlainTextEdit作为代码编辑器的底层文本编辑组件。

显示行号

显示行号是一个编辑器比较好用的一个有力支撑功能。但是QPlainTextEdit控件本身是不支持行号显示的,但是幸好Qt的example给出了一个显示行号的例子CodeEditor,并且更好的消息是CodeEditor也是用QPlainTextEdit实现的。我这里就直接拿来用了。

声明

Qt的显示行号例子CodeEditor声明如下:

//![codeeditordefinition]

class CodeEditor : public QPlainTextEdit
{
    Q_OBJECT

public:
    CodeEditor(QWidget *parent = nullptr);

    void lineNumberAreaPaintEvent(QPaintEvent *event);
    int lineNumberAreaWidth();

protected:
    void resizeEvent(QResizeEvent *event) override;

private slots:
    void updateLineNumberAreaWidth(int newBlockCount);
    void highlightCurrentLine();
    void updateLineNumberArea(const QRect &rect, int dy);

private:
    QWidget *lineNumberArea;
};

//![codeeditordefinition]
//![extraarea]

应用

编辑器应用CodeEditor如下,直接使用了继承的方式

class SqlEditor : public CodeEditor
{
    Q_OBJECT
public:
    SqlEditor(QWidget *parent = nullptr);
    ~SqlEditor();
}

高亮编辑器

Qt有QSyntaxHighlighter对象提供实现编辑器高亮的功能,若要按照要求自定义高亮规则,则实现重新实现highlightBlock方法即可。
在代码编辑器中,最少应该有三种高亮规则:普通文本、语法、注释。这里,普通文本采用默认显示,针对语法和注释分别进行不同的高亮。

声明

封装实现自己的高亮功能如下:

// header file
class CommentHighlighter : public QSyntaxHighlighter
{
public:
    CommentHighlighter(QTextDocument *parent = 0);

protected:
    void highlightBlock(const QString &text) override;

private:
    struct HighlightRule
    {
        QRegularExpression exp;
        QTextCharFormat format;
    };
    std::vector<HighlightRule> _highlightingRules;
    QRegularExpression commentExpression;
    QTextCharFormat syntaxFormat;   // 语法高亮格式
    QTextCharFormat commentFormat;  // 注释肝功格式
};

实现

// cpp file
CommentHighlighter::CommentHighlighter(QTextDocument *parent)
    : QSyntaxHighlighter(parent)
{
    commentFormat.setForeground(Qt::gray);
    commentExpression = QRegularExpression(QStringLiteral("(%1)[^\n]*")
                                           .arg(regExpFactory().commentSymbol()));

    syntaxFormat.setForeground(Qt::blue);
    syntaxFormat.setFontWeight(QFont::Bold);

    for (auto &pattern : regExpFactory().keywords()) {
        HighlightRule rule;
        rule.exp = QRegularExpression(QString("\\b%1\\b").arg(pattern), QRegularExpression::CaseInsensitiveOption);
        rule.format = syntaxFormat;
        _highlightingRules.emplace_back(rule);
    }
}

void CommentHighlighter::highlightBlock(const QString &text)
{
    for(auto& value : _highlightingRules)
    {
        auto matchIterator = value.exp.globalMatch(text);
        while (matchIterator.hasNext()) {
            auto match = matchIterator.next();
            setFormat(match.capturedStart(), match.capturedLength(), value.format);
        }
    }

    auto matchIterator = commentExpression.globalMatch(text);
    while (matchIterator.hasNext()) {
        auto match = matchIterator.next();
        setFormat(match.capturedStart(), match.capturedLength(), commentFormat);
    }
}

应用

SqlEditor::SqlEditor(QWidget *parent)
    : CodeEditor(parent)
{
    _highligter = new CommentHighlighter(document());
}

代码自动补全

代码补充功能可以使用QCompleter对象来实现。

实现

SqlEditor::SqlEditor(QWidget *parent)
    : CodeEditor(parent)
    , _completer{nullptr}
    , _lastTableNames{}
{
    _highligter = new CommentHighlighter(document());

    // syntax complete
    _completer = new QCompleter(regExpFactory().keywords(), this);
    _completer->setCaseSensitivity(Qt::CaseInsensitive);
    _completer->setWidget(this);
    connect(_completer, SIGNAL(activated(const QString&)), this, SLOT(slotCompleterActivated(const QString&)));
}

void SqlEditor::slotCompleterActivated(const QString &text)
{
    QString completionPrefix = currentWord();
    QString shouldReplaceText = text;
    QTextCursor cursor = textCursor();
    if (!shouldReplaceText.contains(completionPrefix))
    {
        cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, completionPrefix.size());
        cursor.removeSelectedText();
    }
    else
    {
        shouldReplaceText = shouldReplaceText.replace(
                    shouldReplaceText.indexOf(completionPrefix), completionPrefix.size(), "");
    }
    cursor.insertText(shouldReplaceText);
}

QString SqlEditor::currentWord()
{
    auto cursor = textCursor();
    while (cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor)) {
        QString text = cursor.selectedText();
        if (text.contains(" ") || cursor.atBlockStart()) {
            break;
        }
    }
    return cursor.selectedText().simplified();
}

自动补全关键字更新

在以上实现中,我们完全了关键字的自动补全功能,但QCompleter的能力不止如此,比如说我们在编辑中可以更新关键字:添加或者减少关键字,形成一种智能编辑的能力,怎么实现呢?

void SqlEditor::updateCompleterMode(QStringList words)
{
    QStringList allWords = regExpFactory().keywords();
    for (auto& [key, value] : _mapTables) {
        allWords.append(value);
    }

    if(words.size() > 0)
    {
        allWords.append(words);
    }

    _completer->setModel(new QStringListModel{allWords});
}

总结

  1. 完整源代码参考github
  2. Qt Assistant真的挺好用,学会查询和尽量使用Qt Assistant,能是你在使用Qt中事半功倍。
本文含有隐藏内容,请 开通VIP 后查看

微信公众号

今日签到

点亮在社区的每一天
去签到