我有两个答案:
- 一个纯QML的答案
- 一个涉及QSyntaxHighlighter的C++答案
对于纯QML答案,我们可以使用
TextArea,并将
TextArea::textFormat设置为
textFormat: TextEdit.RichText
以进行格式化。我们可以使用
TextArea::getText()获取纯文本,并使用富文本设置
TextArea::text。这是一个模拟示例:
- 将大写标识符(例如 Button)标记为紫色
- 将小写标识符(例如 x y z)标记为红色
- 将数字(例如 123 456)标记为蓝色
- 但保留符号(例如 = + ;) 为黑色
这是代码片段:
TextArea {
id: output
property bool processing: false
text: "<p>x = 123;</p><p>y = 456;</p><p>z = x + y;</p>"
textFormat: TextEdit.RichText
selectByMouse: true
onTextChanged: {
if (!processing) {
processing = true;
let p = cursorPosition;
let markUp = getText(0, length).replace(
/([A-Z][A-Za-z]*|[a-z][A-Za-z]*|[0-9]+|[ \t\n]|['][^']*[']|[^A-Za-z0-9\t\n ])/g,
function (f) {
console.log("f: ", JSON.stringify(f));
if (f.match(/^[A-Z][A-Za-z]*$/))
return "<span style='color:#800080'>" + f + "</span>";
if (f.match(/^[a-z][A-Za-z]*$/))
return "<span style='color:#800000'>" + f + "</span>";
else if (f.match(/^[0-9]+$/))
return "<span style='color:#0000ff'>" + f + "</span>";
else if (f.match(/^[ ]/))
return " "
else if (f.match(/^[\t\n]/))
return f;
else if (f.match(/^[']/))
return "<span style='color:#008000'>" + f + "</span>";
else
return f;
}
);
text = markUp;
cursorPosition = p;
processing = false;
}
}
}
要使用Qt的QSyntaxHighlighter,您需要以下内容:
- 在QML中,在您的应用程序中使用TextEdit QML类型
- 在C++中,定义一个QSyntaxHighlighter并通过TextEdit::textDocument属性将TextEdit QML类型连接到它
- 在C++中,实现您的派生类中的QSyntaxHighlighter::highlightBlock(const QString& text),根据需要多次调用QSyntaxHighlighter::setFormat()来分词找到的文本。
为了让事情更简单,我创建了一个示例应用程序
https://github.com/stephenquan/QtSyntaxHighlighterApp,它将
QSyntaxHighlighter和
QTextFormat封装为
SyntaxHighlighter
和
TextFormat
QML类型。这样,可以处理onHighlightBlock信号,并将语法高亮器的业务逻辑放在JavaScript中,而不是C++中:
TextEdit {
id: textEdit
selectByMouse: true
text: [
"import QtQuick 2.12",
"",
"Item {",
" Rectangle {",
" width: 50",
" height: 50",
" color: '#800000'",
" }",
"}",
].join("\n") + "\n"
font.pointSize: 12
}
SyntaxHighlighter {
id: syntaxHighlighter
textDocument: textEdit.textDocument
onHighlightBlock: {
let rx = /\/\/.*|[A-Za-z.]+(\s*:)?|\d+(.\d*)?|'[^']*?'|"[^"]*?"/g;
let m;
while ( ( m = rx.exec(text) ) !== null ) {
if (m[0].match(/^\/\/.*/)) {
setFormat(m.index, m[0].length, commentFormat);
continue;
}
if (m[0].match(/^[a-z][A-Za-z.]*\s*:/)) {
setFormat(m.index, m[0].match(/^[a-z][A-Za-z.]*/)[0].length, propertyFormat);
continue;
}
if (m[0].match(/^[a-z]/)) {
let keywords = [ 'import', 'function', 'bool', 'var',
'int', 'string', 'let', 'const', 'property',
'if', 'continue', 'for', 'break', 'while',
];
if (keywords.includes(m[0])) {
setFormat(m.index, m[0].length, keywordFormat);
continue;
}
continue;
}
if (m[0].match(/^[A-Z]/)) {
setFormat(m.index, m[0].length, componentFormat);
continue;
}
if (m[0].match(/^\d/)) {
setFormat(m.index, m[0].length, numberFormat);
continue;
}
if (m[0].match(/^'/)) {
setFormat(m.index, m[0].length, stringFormat);
continue;
}
if (m[0].match(/^"/)) {
setFormat(m.index, m[0].length, stringFormat);
continue;
}
}
}
}
TextCharFormat { id: keywordFormat; foreground: "#808000" }
TextCharFormat { id: componentFormat; foreground: "#aa00aa"; font.pointSize: 12; font.bold: true; font.italic: true }
TextCharFormat { id: numberFormat; foreground: "#0055af" }
TextCharFormat { id: propertyFormat; foreground: "#800000" }
TextCharFormat { id: stringFormat; foreground: "green" }
TextCharFormat { id: commentFormat; foreground: "green" }