I am working on a QtQuick 2.0 presentation and I would like to embed some code samples. is it possible easily to create a syntax highlighting qml element.
C
I have two answers:
For a pure QML answer, we can use a TextArea one can use textFormat: TextEdit.RichText for formatting. We can use TextArea::getText() to get the plain text and set TextArea::text with the rich text. Here's a mock example that:
Here's the snippet:
TextArea {
id: output
property bool processing: false
text: "x = 123;
y = 456;
z = x + y;
"
textFormat: TextEdit.RichText
selectByMouse: true
onTextChanged: {
if (!processing) {
processing = true;
var p = cursorPosition;
var mu = getText(0, length);
mu = mu.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 "" + f + "";
if (f.match(/^[a-z][A-Za-z]*$/))
return "" + f + "";
else if (f.match(/^[0-9]+$/))
return "" + f + "";
else if (f.match(/^[ ]/))
return " "
else if (f.match(/^[\t\n]/))
return f;
else if (f.match(/^[']/))
return "" + f + "";
else
return f;
} );
text = mu;
cursorPosition = p;
processing = false;
}
}
}
To use Qt's QSyntaxHighlighter, you need the following:
QSyntaxHighlighter::highlightBlock( const QString& text ) in your derived class, calling setFormat() as often as needed to tokenize the text found.To make things easier, I create a sample app https://github.com/stephenquan/QtSyntaxHighlighterApp which wraps QSyntaxHighlighter and QTextFormat as SyntaxHighlighter and TextFormat QML Types. That way, one can handle onHighlightBlock signal and put the business logic of the syntax highlighter in Javascript instead of 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" }