add QCodeEditor

This commit is contained in:
Carl Philipp Klemm 2025-10-13 12:40:46 +02:00
parent bccee9bd36
commit 2f3069a388
316 changed files with 98016 additions and 0 deletions

View file

@ -0,0 +1,179 @@
// QCodeEditor
#include <QCXXHighlighter>
#include <QSyntaxStyle>
#include <QLanguage>
// Qt
#include <QFile>
QCXXHighlighter::QCXXHighlighter(QTextDocument* document) :
QStyleSyntaxHighlighter(document),
m_highlightRules (),
m_includePattern (QRegularExpression(R"(^\s*#\s*include\s*([<"][^:?"<>\|]+[">]))")),
m_functionPattern (QRegularExpression(R"(\b([_a-zA-Z][_a-zA-Z0-9]*\s+)?((?:[_a-zA-Z][_a-zA-Z0-9]*\s*::\s*)*[_a-zA-Z][_a-zA-Z0-9]*)(?=\s*\())")),
m_defTypePattern (QRegularExpression(R"(\b([_a-zA-Z][_a-zA-Z0-9]*)\s+[_a-zA-Z][_a-zA-Z0-9]*\s*[;=])")),
m_commentStartPattern(QRegularExpression(R"(/\*)")),
m_commentEndPattern (QRegularExpression(R"(\*/)"))
{
Q_INIT_RESOURCE(qcodeeditor_resources);
QFile fl(":/languages/cpp.xml");
if (!fl.open(QIODevice::ReadOnly))
{
return;
}
QLanguage language(&fl);
if (!language.isLoaded())
{
return;
}
auto keys = language.keys();
for (auto&& key : keys)
{
auto names = language.names(key);
for (auto&& name : names)
{
m_highlightRules.append({
QRegularExpression(QString(R"(\b%1\b)").arg(name)),
key
});
}
}
// Numbers
m_highlightRules.append({
QRegularExpression(R"((?<=\b|\s|^)(?i)(?:(?:(?:(?:(?:\d+(?:'\d+)*)?\.(?:\d+(?:'\d+)*)(?:e[+-]?(?:\d+(?:'\d+)*))?)|(?:(?:\d+(?:'\d+)*)\.(?:e[+-]?(?:\d+(?:'\d+)*))?)|(?:(?:\d+(?:'\d+)*)(?:e[+-]?(?:\d+(?:'\d+)*)))|(?:0x(?:[0-9a-f]+(?:'[0-9a-f]+)*)?\.(?:[0-9a-f]+(?:'[0-9a-f]+)*)(?:p[+-]?(?:\d+(?:'\d+)*)))|(?:0x(?:[0-9a-f]+(?:'[0-9a-f]+)*)\.?(?:p[+-]?(?:\d+(?:'\d+)*))))[lf]?)|(?:(?:(?:[1-9]\d*(?:'\d+)*)|(?:0[0-7]*(?:'[0-7]+)*)|(?:0x[0-9a-f]+(?:'[0-9a-f]+)*)|(?:0b[01]+(?:'[01]+)*))(?:u?l{0,2}|l{0,2}u?)))(?=\b|\s|$))"),
"Number"
});
// Strings
m_highlightRules.append({
QRegularExpression(R"("[^\n"]*")"),
"String"
});
// Define
m_highlightRules.append({
QRegularExpression(R"(#[a-zA-Z_]+)"),
"Preprocessor"
});
// Single line
m_highlightRules.append({
QRegularExpression(R"(//[^\n]*)"),
"Comment"
});
}
void QCXXHighlighter::highlightBlock(const QString& text)
{
// Checking for include
{
auto matchIterator = m_includePattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
syntaxStyle()->getFormat("Preprocessor")
);
setFormat(
match.capturedStart(1),
match.capturedLength(1),
syntaxStyle()->getFormat("String")
);
}
}
// Checking for function
{
auto matchIterator = m_functionPattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
syntaxStyle()->getFormat("Type")
);
setFormat(
match.capturedStart(2),
match.capturedLength(2),
syntaxStyle()->getFormat("Function")
);
}
}
{
auto matchIterator = m_defTypePattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(1),
match.capturedLength(1),
syntaxStyle()->getFormat("Type")
);
}
}
for (auto& rule : m_highlightRules)
{
auto matchIterator = rule.pattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
syntaxStyle()->getFormat(rule.formatName)
);
}
}
setCurrentBlockState(0);
int startIndex = 0;
if (previousBlockState() != 1)
{
startIndex = text.indexOf(m_commentStartPattern);
}
while (startIndex >= 0)
{
auto match = m_commentEndPattern.match(text, startIndex);
int endIndex = match.capturedStart();
int commentLength = 0;
if (endIndex == -1)
{
setCurrentBlockState(1);
commentLength = text.length() - startIndex;
}
else
{
commentLength = endIndex - startIndex + match.capturedLength();
}
setFormat(
startIndex,
commentLength,
syntaxStyle()->getFormat("Comment")
);
startIndex = text.indexOf(m_commentStartPattern, startIndex + commentLength);
}
}

View file

@ -0,0 +1,742 @@
// QCodeEditor
#include <QLineNumberArea>
#include <QSyntaxStyle>
#include <QCodeEditor>
#include <QStyleSyntaxHighlighter>
#include <QFramedTextAttribute>
#include <QCXXHighlighter>
// Qt
#include <QTextBlock>
#include <QPaintEvent>
#include <QFontDatabase>
#include <QScrollBar>
#include <QAbstractTextDocumentLayout>
#include <QTextCharFormat>
#include <QCursor>
#include <QCompleter>
#include <QAbstractItemView>
#include <QShortcut>
#include <QMimeData>
static QVector<QPair<QString, QString>> parentheses = {
{"(", ")"},
{"{", "}"},
{"[", "]"},
{"\"", "\""},
{"'", "'"}
};
QCodeEditor::QCodeEditor(QWidget* widget) :
QTextEdit(widget),
m_highlighter(nullptr),
m_syntaxStyle(nullptr),
m_lineNumberArea(new QLineNumberArea(this)),
m_completer(nullptr),
m_framedAttribute(new QFramedTextAttribute(this)),
m_autoIndentation(true),
m_autoParentheses(true),
m_replaceTab(true),
m_tabReplace(QString(4, ' '))
{
initDocumentLayoutHandlers();
initFont();
performConnections();
setSyntaxStyle(QSyntaxStyle::defaultStyle());
}
void QCodeEditor::initDocumentLayoutHandlers()
{
document()
->documentLayout()
->registerHandler(
QFramedTextAttribute::type(),
m_framedAttribute
);
}
void QCodeEditor::initFont()
{
auto fnt = QFontDatabase::systemFont(QFontDatabase::FixedFont);
fnt.setFixedPitch(true);
fnt.setPointSize(10);
setFont(fnt);
}
void QCodeEditor::performConnections()
{
connect(
document(),
&QTextDocument::blockCountChanged,
this,
&QCodeEditor::updateLineNumberAreaWidth
);
connect(
verticalScrollBar(),
&QScrollBar::valueChanged,
[this](int){ m_lineNumberArea->update(); }
);
connect(
this,
&QTextEdit::cursorPositionChanged,
this,
&QCodeEditor::updateExtraSelection
);
connect(
this,
&QTextEdit::selectionChanged,
this,
&QCodeEditor::onSelectionChanged
);
}
void QCodeEditor::setHighlighter(QStyleSyntaxHighlighter* highlighter)
{
if (m_highlighter)
{
m_highlighter->setDocument(nullptr);
}
m_highlighter = highlighter;
if (m_highlighter)
{
m_highlighter->setSyntaxStyle(m_syntaxStyle);
m_highlighter->setDocument(document());
}
}
void QCodeEditor::setSyntaxStyle(QSyntaxStyle* style)
{
m_syntaxStyle = style;
m_framedAttribute->setSyntaxStyle(m_syntaxStyle);
m_lineNumberArea->setSyntaxStyle(m_syntaxStyle);
if (m_highlighter)
{
m_highlighter->setSyntaxStyle(m_syntaxStyle);
}
updateStyle();
}
void QCodeEditor::updateStyle()
{
if (m_highlighter)
{
m_highlighter->rehighlight();
}
if (m_syntaxStyle)
{
auto currentPalette = palette();
// Setting text format/color
currentPalette.setColor(
QPalette::ColorRole::Text,
m_syntaxStyle->getFormat("Text").foreground().color()
);
// Setting common background
currentPalette.setColor(
QPalette::Base,
m_syntaxStyle->getFormat("Text").background().color()
);
// Setting selection color
currentPalette.setColor(
QPalette::Highlight,
m_syntaxStyle->getFormat("Selection").background().color()
);
setPalette(currentPalette);
}
updateExtraSelection();
}
void QCodeEditor::onSelectionChanged()
{
auto selected = textCursor().selectedText();
auto cursor = textCursor();
// Cursor is null if setPlainText was called.
if (cursor.isNull())
{
return;
}
cursor.movePosition(QTextCursor::MoveOperation::Left);
cursor.select(QTextCursor::SelectionType::WordUnderCursor);
QSignalBlocker blocker(this);
m_framedAttribute->clear(cursor);
if (selected.size() > 1 &&
cursor.selectedText() == selected)
{
auto backup = textCursor();
// Perform search selecting
handleSelectionQuery(cursor);
setTextCursor(backup);
}
}
void QCodeEditor::resizeEvent(QResizeEvent* e)
{
QTextEdit::resizeEvent(e);
updateLineGeometry();
}
void QCodeEditor::updateLineGeometry()
{
QRect cr = contentsRect();
m_lineNumberArea->setGeometry(
QRect(cr.left(),
cr.top(),
m_lineNumberArea->sizeHint().width(),
cr.height()
)
);
}
void QCodeEditor::updateLineNumberAreaWidth(int)
{
setViewportMargins(m_lineNumberArea->sizeHint().width(), 0, 0, 0);
}
void QCodeEditor::updateLineNumberArea(const QRect& rect)
{
m_lineNumberArea->update(
0,
rect.y(),
m_lineNumberArea->sizeHint().width(),
rect.height()
);
updateLineGeometry();
if (rect.contains(viewport()->rect()))
{
updateLineNumberAreaWidth(0);
}
}
void QCodeEditor::handleSelectionQuery(QTextCursor cursor)
{
auto searchIterator = cursor;
searchIterator.movePosition(QTextCursor::Start);
searchIterator = document()->find(cursor.selectedText(), searchIterator);
while (searchIterator.hasSelection())
{
m_framedAttribute->frame(searchIterator);
searchIterator = document()->find(cursor.selectedText(), searchIterator);
}
}
void QCodeEditor::updateExtraSelection()
{
QList<QTextEdit::ExtraSelection> extra;
highlightCurrentLine(extra);
highlightParenthesis(extra);
setExtraSelections(extra);
}
void QCodeEditor::highlightParenthesis(QList<QTextEdit::ExtraSelection>& extraSelection)
{
auto currentSymbol = charUnderCursor();
auto prevSymbol = charUnderCursor(-1);
for (auto& pair : parentheses)
{
int direction;
QChar counterSymbol;
QChar activeSymbol;
auto position = textCursor().position();
if (pair.first == currentSymbol)
{
direction = 1;
counterSymbol = pair.second[0];
activeSymbol = currentSymbol;
}
else if (pair.second == prevSymbol)
{
direction = -1;
counterSymbol = pair.first[0];
activeSymbol = prevSymbol;
position--;
}
else
{
continue;
}
auto counter = 1;
while (counter != 0 &&
position > 0 &&
position < (document()->characterCount() - 1))
{
// Moving position
position += direction;
auto character = document()->characterAt(position);
// Checking symbol under position
if (character == activeSymbol)
{
++counter;
}
else if (character == counterSymbol)
{
--counter;
}
}
auto format = m_syntaxStyle->getFormat("Parentheses");
// Found
if (counter == 0)
{
ExtraSelection selection{};
auto directionEnum =
direction < 0 ?
QTextCursor::MoveOperation::Left
:
QTextCursor::MoveOperation::Right;
selection.format = format;
selection.cursor = textCursor();
selection.cursor.clearSelection();
selection.cursor.movePosition(
directionEnum,
QTextCursor::MoveMode::MoveAnchor,
std::abs(textCursor().position() - position)
);
selection.cursor.movePosition(
QTextCursor::MoveOperation::Right,
QTextCursor::MoveMode::KeepAnchor,
1
);
extraSelection.append(selection);
selection.cursor = textCursor();
selection.cursor.clearSelection();
selection.cursor.movePosition(
directionEnum,
QTextCursor::MoveMode::KeepAnchor,
1
);
extraSelection.append(selection);
}
break;
}
}
void QCodeEditor::highlightCurrentLine(QList<QTextEdit::ExtraSelection>& extraSelection)
{
if (!isReadOnly())
{
QTextEdit::ExtraSelection selection{};
selection.format = m_syntaxStyle->getFormat("CurrentLine");
selection.format.setForeground(QBrush());
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = textCursor();
selection.cursor.clearSelection();
extraSelection.append(selection);
}
}
void QCodeEditor::paintEvent(QPaintEvent* e)
{
updateLineNumberArea(e->rect());
QTextEdit::paintEvent(e);
}
int QCodeEditor::getFirstVisibleBlock()
{
// Detect the first block for which bounding rect - once translated
// in absolute coordinated - is contained by the editor's text area
// Costly way of doing but since "blockBoundingGeometry(...)" doesn't
// exists for "QTextEdit"...
QTextCursor curs = QTextCursor(document());
curs.movePosition(QTextCursor::Start);
for(int i=0; i < document()->blockCount(); ++i)
{
QTextBlock block = curs.block();
QRect r1 = viewport()->geometry();
QRect r2 = document()
->documentLayout()
->blockBoundingRect(block)
.translated(
viewport()->geometry().x(),
viewport()->geometry().y() - verticalScrollBar()->sliderPosition()
).toRect();
if (r1.intersects(r2))
{
return i;
}
curs.movePosition(QTextCursor::NextBlock);
}
return 0;
}
bool QCodeEditor::proceedCompleterBegin(QKeyEvent *e)
{
if (m_completer &&
m_completer->popup()->isVisible())
{
switch (e->key())
{
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Escape:
case Qt::Key_Tab:
case Qt::Key_Backtab:
e->ignore();
return true; // let the completer do default behavior
default:
break;
}
}
// todo: Replace with modifiable QShortcut
auto isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_Space);
return !(!m_completer || !isShortcut);
}
void QCodeEditor::proceedCompleterEnd(QKeyEvent *e)
{
auto ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
if (!m_completer ||
(ctrlOrShift && e->text().isEmpty()) ||
e->key() == Qt::Key_Delete)
{
return;
}
static QString eow(R"(~!@#$%^&*()_+{}|:"<>?,./;'[]\-=)");
auto isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_Space);
auto completionPrefix = wordUnderCursor();
if (!isShortcut &&
(e->text().isEmpty() ||
completionPrefix.length() < 2 ||
eow.contains(e->text().right(1))))
{
m_completer->popup()->hide();
return;
}
if (completionPrefix != m_completer->completionPrefix())
{
m_completer->setCompletionPrefix(completionPrefix);
m_completer->popup()->setCurrentIndex(m_completer->completionModel()->index(0, 0));
}
auto cursRect = cursorRect();
cursRect.setWidth(
m_completer->popup()->sizeHintForColumn(0) +
m_completer->popup()->verticalScrollBar()->sizeHint().width()
);
m_completer->complete(cursRect);
}
void QCodeEditor::keyPressEvent(QKeyEvent* e) {
#if QT_VERSION >= 0x050A00
const int defaultIndent = tabStopDistance() / fontMetrics().averageCharWidth();
#else
const int defaultIndent = tabStopWidth() / fontMetrics().averageCharWidth();
#endif
auto completerSkip = proceedCompleterBegin(e);
if (!completerSkip) {
if (m_replaceTab && e->key() == Qt::Key_Tab &&
e->modifiers() == Qt::NoModifier) {
insertPlainText(m_tabReplace);
return;
}
// Auto indentation
int indentationLevel = getIndentationSpaces();
#if QT_VERSION >= 0x050A00
int tabCounts =
indentationLevel * fontMetrics().averageCharWidth() / tabStopDistance();
#else
int tabCounts =
indentationLevel * fontMetrics().averageCharWidth() / tabStopWidth();
#endif
// Have Qt Edior like behaviour, if {|} and enter is pressed indent the two
// parenthesis
if (m_autoIndentation &&
(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) &&
charUnderCursor() == '}' && charUnderCursor(-1) == '{')
{
int charsBack = 0;
insertPlainText("\n");
if (m_replaceTab)
insertPlainText(QString(indentationLevel + defaultIndent, ' '));
else
insertPlainText(QString(tabCounts + 1, '\t'));
insertPlainText("\n");
charsBack++;
if (m_replaceTab)
{
insertPlainText(QString(indentationLevel, ' '));
charsBack += indentationLevel;
}
else
{
insertPlainText(QString(tabCounts, '\t'));
charsBack += tabCounts;
}
while (charsBack--)
moveCursor(QTextCursor::MoveOperation::Left);
return;
}
// Shortcut for moving line to left
if (m_replaceTab && e->key() == Qt::Key_Backtab) {
indentationLevel = std::min(indentationLevel, (int) m_tabReplace.size());
auto cursor = textCursor();
cursor.movePosition(QTextCursor::MoveOperation::StartOfLine);
cursor.movePosition(QTextCursor::MoveOperation::Right,
QTextCursor::MoveMode::KeepAnchor,
indentationLevel);
cursor.removeSelectedText();
return;
}
QTextEdit::keyPressEvent(e);
if (m_autoIndentation && (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter)) {
if (m_replaceTab)
insertPlainText(QString(indentationLevel, ' '));
else
insertPlainText(QString(tabCounts, '\t'));
}
if (m_autoParentheses)
{
for (auto&& el : parentheses)
{
// Inserting closed brace
if (el.first == e->text())
{
insertPlainText(el.second);
moveCursor(QTextCursor::MoveOperation::Left);
break;
}
// If it's close brace - check parentheses
if (el.second == e->text())
{
auto symbol = charUnderCursor();
if (symbol == el.second)
{
textCursor().deletePreviousChar();
moveCursor(QTextCursor::MoveOperation::Right);
}
break;
}
}
}
}
proceedCompleterEnd(e);
}
void QCodeEditor::setAutoIndentation(bool enabled)
{
m_autoIndentation = enabled;
}
bool QCodeEditor::autoIndentation() const
{
return m_autoIndentation;
}
void QCodeEditor::setAutoParentheses(bool enabled)
{
m_autoParentheses = enabled;
}
bool QCodeEditor::autoParentheses() const
{
return m_autoParentheses;
}
void QCodeEditor::setTabReplace(bool enabled)
{
m_replaceTab = enabled;
}
bool QCodeEditor::tabReplace() const
{
return m_replaceTab;
}
void QCodeEditor::setTabReplaceSize(int val)
{
m_tabReplace.clear();
m_tabReplace.fill(' ', val);
}
int QCodeEditor::tabReplaceSize() const
{
return m_tabReplace.size();
}
void QCodeEditor::setCompleter(QCompleter *completer)
{
if (m_completer)
{
disconnect(m_completer, nullptr, this, nullptr);
}
m_completer = completer;
if (!m_completer)
{
return;
}
m_completer->setWidget(this);
m_completer->setCompletionMode(QCompleter::CompletionMode::PopupCompletion);
connect(
m_completer,
QOverload<const QString&>::of(&QCompleter::activated),
this,
&QCodeEditor::insertCompletion
);
}
void QCodeEditor::focusInEvent(QFocusEvent *e)
{
if (m_completer)
{
m_completer->setWidget(this);
}
QTextEdit::focusInEvent(e);
}
void QCodeEditor::insertCompletion(QString s)
{
if (m_completer->widget() != this)
{
return;
}
auto tc = textCursor();
tc.select(QTextCursor::SelectionType::WordUnderCursor);
tc.insertText(s);
setTextCursor(tc);
}
QCompleter *QCodeEditor::completer() const
{
return m_completer;
}
QChar QCodeEditor::charUnderCursor(int offset) const
{
auto block = textCursor().blockNumber();
auto index = textCursor().positionInBlock();
auto text = document()->findBlockByNumber(block).text();
index += offset;
if (index < 0 || index >= text.size())
{
return {};
}
return text[index];
}
QString QCodeEditor::wordUnderCursor() const
{
auto tc = textCursor();
tc.select(QTextCursor::WordUnderCursor);
return tc.selectedText();
}
void QCodeEditor::insertFromMimeData(const QMimeData* source)
{
insertPlainText(source->text());
}
int QCodeEditor::getIndentationSpaces()
{
auto blockText = textCursor().block().text();
int indentationLevel = 0;
for (auto i = 0;
i < blockText.size() && QString("\t ").contains(blockText[i]);
++i)
{
if (blockText[i] == ' ')
{
indentationLevel++;
}
else
{
#if QT_VERSION >= 0x050A00
indentationLevel += tabStopDistance() / fontMetrics().averageCharWidth();
#else
indentationLevel += tabStopWidth() / fontMetrics().averageCharWidth();
#endif
}
}
return indentationLevel;
}

View file

@ -0,0 +1,112 @@
// QCodeEditor
#include <QFramedTextAttribute>
#include <QSyntaxStyle>
// Qt
#include <QFontMetrics>
#include <QPainter>
#include <QDebug>
#include <QTextBlock>
int QFramedTextAttribute::type()
{
return QTextFormat::UserFormat + 1;
}
QFramedTextAttribute::QFramedTextAttribute(QObject* parent) :
QObject(parent),
m_style(nullptr)
{
}
void QFramedTextAttribute::setSyntaxStyle(QSyntaxStyle* style)
{
m_style = style;
}
QSyntaxStyle* QFramedTextAttribute::syntaxStyle() const
{
return m_style;
}
QSizeF QFramedTextAttribute::intrinsicSize(QTextDocument*, int, const QTextFormat&)
{
return {0, 0};
}
void QFramedTextAttribute::drawObject(QPainter* painter,
const QRectF& rect,
QTextDocument*,
int,
const QTextFormat& format)
{
// Casting
auto textCharFormat = reinterpret_cast<const QTextCharFormat&>(format);
// Getting font data
auto font = textCharFormat.font();
QFontMetrics metrics(font);
// Getting required size
auto string = format.property(FramedString).toString();
auto stringSize = metrics.boundingRect(string).size();
// Creating frame rect
QRectF drawRect(rect.topLeft(), stringSize);
drawRect.moveTop(rect.top() - stringSize.height());
drawRect.adjust(0, 4, 0, 4);
// Drawing
painter->setPen(m_style->getFormat("Occurrences").background().color());
painter->setRenderHint(QPainter::Antialiasing);
painter->drawRoundedRect(drawRect, 4, 4);
}
void QFramedTextAttribute::frame(QTextCursor cursor)
{
auto text = cursor.document()->findBlockByNumber(cursor.blockNumber()).text();
QTextCharFormat format;
format.setObjectType(type());
format.setProperty(FramedString, cursor.selectedText());
if (cursor.selectionEnd() > cursor.selectionStart())
{
cursor.setPosition(cursor.selectionStart());
}
else
{
cursor.setPosition(cursor.selectionEnd());
}
cursor.insertText(
QString(QChar::ObjectReplacementCharacter),
format
);
}
void QFramedTextAttribute::clear(QTextCursor cursor)
{
auto doc = cursor.document();
for (auto blockIndex = 0;
blockIndex < doc->blockCount();
++blockIndex)
{
auto block = doc->findBlockByNumber(blockIndex);
auto formats = block.textFormats();
int offset = 0;
for (auto& format : formats)
{
if (format.format.objectType() == type())
{
cursor.setPosition(block.position() + format.start - offset);
cursor.deleteChar();
++offset;
}
}
}
}

View file

@ -0,0 +1,42 @@
// QCodeEditor
#include <QGLSLCompleter>
#include <QLanguage>
// Qt
#include <QStringListModel>
#include <QFile>
QGLSLCompleter::QGLSLCompleter(QObject *parent) :
QCompleter(parent)
{
// Setting up GLSL types
QStringList list;
Q_INIT_RESOURCE(qcodeeditor_resources);
QFile fl(":/languages/glsl.xml");
if (!fl.open(QIODevice::ReadOnly))
{
return;
}
QLanguage language(&fl);
if (!language.isLoaded())
{
return;
}
auto keys = language.keys();
for (auto&& key : keys)
{
auto names = language.names(key);
list.append(names);
}
setModel(new QStringListModel(list, this));
setCompletionColumn(0);
setModelSorting(QCompleter::CaseInsensitivelySortedModel);
setCaseSensitivity(Qt::CaseSensitive);
setWrapAround(true);
}

View file

@ -0,0 +1,162 @@
// QCodeEditor
#include <QGLSLHighlighter>
#include <QLanguage>
#include <QSyntaxStyle>
// Qt
#include <QFile>
#include <QDebug>
QGLSLHighlighter::QGLSLHighlighter(QTextDocument* document) :
QStyleSyntaxHighlighter(document),
m_highlightRules (),
m_includePattern (QRegularExpression(R"(#include\s+([<"][a-zA-Z0-9*._]+[">]))")),
m_functionPattern (QRegularExpression(R"(\b([A-Za-z0-9_]+(?:\s+|::))*([A-Za-z0-9_]+)(?=\())")),
m_defTypePattern (QRegularExpression(R"(\b([A-Za-z0-9_]+)\s+[A-Za-z]{1}[A-Za-z0-9_]+\s*[;=])")),
m_commentStartPattern(QRegularExpression(R"(/\*)")),
m_commentEndPattern (QRegularExpression(R"(\*/)"))
{
Q_INIT_RESOURCE(qcodeeditor_resources);
QFile fl(":/languages/glsl.xml");
if (!fl.open(QIODevice::ReadOnly))
{
return;
}
QLanguage language(&fl);
if (!language.isLoaded())
{
return;
}
auto keys = language.keys();
for (auto&& key : keys)
{
auto names = language.names(key);
for (auto&& name : names)
{
m_highlightRules.append({
QRegularExpression(QString(R"(\b%1\b)").arg(name)),
key
});
}
}
// Following rules has higher priority to display
// than language specific keys
// So they must be applied at last.
// Numbers
m_highlightRules.append({
QRegularExpression(R"(\b(0b|0x){0,1}[\d.']+\b)"),
"Number"
});
// Define
m_highlightRules.append({
QRegularExpression(R"(#[a-zA-Z_]+)"),
"Preprocessor"
});
// Single line
m_highlightRules.append({
QRegularExpression("//[^\n]*"),
"Comment"
});
}
void QGLSLHighlighter::highlightBlock(const QString& text)
{
{
auto matchIterator = m_includePattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
syntaxStyle()->getFormat("Preprocessor")
);
setFormat(
match.capturedStart(1),
match.capturedLength(1),
syntaxStyle()->getFormat("String")
);
}
}
// Checking for function
{
auto matchIterator = m_functionPattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
syntaxStyle()->getFormat("Type")
);
setFormat(
match.capturedStart(2),
match.capturedLength(2),
syntaxStyle()->getFormat("Function")
);
}
}
for (auto& rule : m_highlightRules)
{
auto matchIterator = rule.pattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
syntaxStyle()->getFormat(rule.formatName)
);
}
}
setCurrentBlockState(0);
int startIndex = 0;
if (previousBlockState() != 1)
{
startIndex = text.indexOf(m_commentStartPattern);
}
while (startIndex >= 0)
{
auto match = m_commentEndPattern.match(text, startIndex);
int endIndex = match.capturedStart();
int commentLength = 0;
if (endIndex == -1)
{
setCurrentBlockState(1);
commentLength = text.length() - startIndex;
}
else
{
commentLength = endIndex - startIndex + match.capturedLength();
}
setFormat(
startIndex,
commentLength,
syntaxStyle()->getFormat("Comment")
);
startIndex = text.indexOf(m_commentStartPattern, startIndex + commentLength);
}
}

View file

@ -0,0 +1,66 @@
// QCodeEditor
#include <QJSONHighlighter>
#include <QSyntaxStyle>
QJSONHighlighter::QJSONHighlighter(QTextDocument* document) :
QStyleSyntaxHighlighter(document),
m_highlightRules(),
m_keyRegex(R"(("[^\r\n:]+?")\s*:)")
{
auto keywords = QStringList()
<< "null" << "true" << "false";
for (auto&& keyword : keywords)
{
m_highlightRules.append({
QRegularExpression(QString(R"(\b%1\b)").arg(keyword)),
"Keyword"
});
}
// Numbers
m_highlightRules.append({
QRegularExpression(R"(\b(0b|0x){0,1}[\d.']+\b)"),
"Number"
});
// Strings
m_highlightRules.append({
QRegularExpression(R"("[^\n"]*")"),
"String"
});
}
void QJSONHighlighter::highlightBlock(const QString& text)
{
for (auto&& rule : m_highlightRules)
{
auto matchIterator = rule.pattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
syntaxStyle()->getFormat(rule.formatName)
);
}
}
// Special treatment for key regex
auto matchIterator = m_keyRegex.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(1),
match.capturedLength(1),
syntaxStyle()->getFormat("Keyword")
);
}
}

View file

@ -0,0 +1,79 @@
// QCodeEditor
#include <QLanguage>
// Qt
#include <QIODevice>
#include <QXmlStreamReader>
QLanguage::QLanguage(QIODevice* device, QObject* parent) :
QObject(parent),
m_loaded(false),
m_list()
{
load(device);
}
bool QLanguage::load(QIODevice* device)
{
if (device == nullptr)
{
return false;
}
QXmlStreamReader reader(device);
QString name;
QStringList list;
bool readText = false;
while (!reader.atEnd() && !reader.hasError())
{
auto type = reader.readNext();
if (type == QXmlStreamReader::TokenType::StartElement)
{
if (reader.name().toString() == "section") {
if (!list.empty())
{
m_list[name] = list;
list.clear();
}
name = reader.attributes().value("name").toString();
} else if (reader.name().toString() == "name") {
readText = true;
}
}
else if (type == QXmlStreamReader::TokenType::Characters &&
readText)
{
list << reader.text().toString();
readText = false;
}
}
if (!list.empty())
{
m_list[name] = list;
}
m_loaded = !reader.hasError();
return m_loaded;
}
QStringList QLanguage::keys()
{
return m_list.keys();
}
QStringList QLanguage::names(const QString& key)
{
return m_list[key];
}
bool QLanguage::isLoaded() const
{
return m_loaded;
}

View file

@ -0,0 +1,100 @@
// QCodeEditor
#include <QLineNumberArea>
#include <QSyntaxStyle>
#include <QCodeEditor>
// Qt
#include <QTextEdit>
#include <QPainter>
#include <QPaintEvent>
#include <QTextBlock>
#include <QScrollBar>
#include <QAbstractTextDocumentLayout>
QLineNumberArea::QLineNumberArea(QCodeEditor* parent) :
QWidget(parent),
m_syntaxStyle(nullptr),
m_codeEditParent(parent)
{
}
QSize QLineNumberArea::sizeHint() const
{
if (m_codeEditParent == nullptr)
{
return QWidget::sizeHint();
}
// Calculating width
int digits = 1;
int max = qMax(1, m_codeEditParent->document()->blockCount());
while (max >= 10) {
max /= 10;
++digits;
}
#if QT_VERSION >= 0x050B00
int space = 13 + m_codeEditParent->fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits;
#else
int space = 13 + m_codeEditParent->fontMetrics().width(QLatin1Char('9')) * digits;
#endif
return {space, 0};
}
void QLineNumberArea::setSyntaxStyle(QSyntaxStyle* style)
{
m_syntaxStyle = style;
}
QSyntaxStyle* QLineNumberArea::syntaxStyle() const
{
return m_syntaxStyle;
}
void QLineNumberArea::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
// Clearing rect to update
painter.fillRect(
event->rect(),
m_syntaxStyle->getFormat("Text").background().color()
);
auto blockNumber = m_codeEditParent->getFirstVisibleBlock();
auto block = m_codeEditParent->document()->findBlockByNumber(blockNumber);
auto top = (int) m_codeEditParent->document()->documentLayout()->blockBoundingRect(block).translated(0, -m_codeEditParent->verticalScrollBar()->value()).top();
auto bottom = top + (int) m_codeEditParent->document()->documentLayout()->blockBoundingRect(block).height();
auto currentLine = m_syntaxStyle->getFormat("CurrentLineNumber").foreground().color();
auto otherLines = m_syntaxStyle->getFormat("LineNumber").foreground().color();
painter.setFont(m_codeEditParent->font());
while (block.isValid() && top <= event->rect().bottom())
{
if (block.isVisible() && bottom >= event->rect().top())
{
QString number = QString::number(blockNumber + 1);
auto isCurrentLine = m_codeEditParent->textCursor().blockNumber() == blockNumber;
painter.setPen(isCurrentLine ? currentLine : otherLines);
painter.drawText(
-5,
top,
sizeHint().width(),
m_codeEditParent->fontMetrics().height(),
Qt::AlignRight,
number
);
}
block = block.next();
top = bottom;
bottom = top + (int) m_codeEditParent->document()->documentLayout()->blockBoundingRect(block).height();
++blockNumber;
}
}

View file

@ -0,0 +1,42 @@
// QCodeEditor
#include <QLuaCompleter>
#include <QLanguage>
// Qt
#include <QStringListModel>
#include <QFile>
QLuaCompleter::QLuaCompleter(QObject *parent) :
QCompleter(parent)
{
// Setting up GLSL types
QStringList list;
Q_INIT_RESOURCE(qcodeeditor_resources);
QFile fl(":/languages/lua.xml");
if (!fl.open(QIODevice::ReadOnly))
{
return;
}
QLanguage language(&fl);
if (!language.isLoaded())
{
return;
}
auto keys = language.keys();
for (auto&& key : keys)
{
auto names = language.names(key);
list.append(names);
}
setModel(new QStringListModel(list, this));
setCompletionColumn(0);
setModelSorting(QCompleter::CaseInsensitivelySortedModel);
setCaseSensitivity(Qt::CaseSensitive);
setWrapAround(true);
}

View file

@ -0,0 +1,196 @@
// QCodeEditor
#include <QLuaHighlighter>
#include <QSyntaxStyle>
#include <QLanguage>
// Qt
#include <QFile>
QLuaHighlighter::QLuaHighlighter(QTextDocument* document) :
QStyleSyntaxHighlighter(document),
m_highlightRules(),
m_highlightBlockRules(),
m_requirePattern(QRegularExpression(R"(require\s*([("'][a-zA-Z0-9*._]+['")]))")),
m_functionPattern(QRegularExpression(R"(\b([A-Za-z0-9_]+(?:\s+|::))*([A-Za-z0-9_]+)(?=\())")),
m_defTypePattern(QRegularExpression(R"(\b([A-Za-z0-9_]+)\s+[A-Za-z]{1}[A-Za-z0-9_]+\s*[=])"))
{
Q_INIT_RESOURCE(qcodeeditor_resources);
QFile fl(":/languages/lua.xml");
if (!fl.open(QIODevice::ReadOnly))
{
return;
}
QLanguage language(&fl);
if (!language.isLoaded())
{
return;
}
auto keys = language.keys();
for (auto&& key : keys)
{
auto names = language.names(key);
for (auto&& name : names)
{
m_highlightRules.append({
QRegularExpression(QString(R"(\b\s{0,1}%1\s{0,1}\b)").arg(name)),
key
});
}
}
// Numbers
m_highlightRules.append({
QRegularExpression(R"(\b(0b|0x){0,1}[\d.']+\b)"),
"Number"
});
// Strings
m_highlightRules.append({
QRegularExpression(R"(["'][^\n"]*["'])"),
"String"
});
// Preprocessor
m_highlightRules.append({
QRegularExpression(R"(#\![a-zA-Z_]+)"),
"Preprocessor"
});
// Single line
m_highlightRules.append({
QRegularExpression(R"(--[^\n]*)"),
"Comment"
});
// Multiline comments
m_highlightBlockRules.append({
QRegularExpression(R"(--\[\[)"),
QRegularExpression(R"(--\]\])"),
"Comment"
});
// Multiline string
m_highlightBlockRules.append({
QRegularExpression(R"(\[\[)"),
QRegularExpression(R"(\]\])"),
"String"
});
}
void QLuaHighlighter::highlightBlock(const QString& text)
{
{ // Checking for require
auto matchIterator = m_requirePattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
syntaxStyle()->getFormat("Preprocessor")
);
setFormat(
match.capturedStart(1),
match.capturedLength(1),
syntaxStyle()->getFormat("String")
);
}
}
{ // Checking for function
auto matchIterator = m_functionPattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
syntaxStyle()->getFormat("Type")
);
setFormat(
match.capturedStart(2),
match.capturedLength(2),
syntaxStyle()->getFormat("Function")
);
}
}
{ // checking for type
auto matchIterator = m_defTypePattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(1),
match.capturedLength(1),
syntaxStyle()->getFormat("Type")
);
}
}
for (auto& rule : m_highlightRules)
{
auto matchIterator = rule.pattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
syntaxStyle()->getFormat(rule.formatName)
);
}
}
setCurrentBlockState(0);
int startIndex = 0;
int highlightRuleId = previousBlockState();
if (highlightRuleId < 1 || highlightRuleId > m_highlightBlockRules.size()) {
for(int i = 0; i < m_highlightBlockRules.size(); ++i) {
startIndex = text.indexOf(m_highlightBlockRules.at(i).startPattern);
if (startIndex >= 0) {
highlightRuleId = i + 1;
break;
}
}
}
while (startIndex >= 0)
{
const auto &blockRules = m_highlightBlockRules.at(highlightRuleId - 1);
auto match = blockRules.endPattern.match(text, startIndex);
int endIndex = match.capturedStart();
int matchLength = 0;
if (endIndex == -1)
{
setCurrentBlockState(highlightRuleId);
matchLength = text.length() - startIndex;
}
else
{
matchLength = endIndex - startIndex + match.capturedLength();
}
setFormat(
startIndex,
matchLength,
syntaxStyle()->getFormat(blockRules.formatName)
);
startIndex = text.indexOf(blockRules.startPattern, startIndex + matchLength);
}
}

View file

@ -0,0 +1,42 @@
// QCodeEditor
#include <QPythonCompleter>
#include <QLanguage>
// Qt
#include <QStringListModel>
#include <QFile>
QPythonCompleter::QPythonCompleter(QObject *parent) :
QCompleter(parent)
{
// Setting up Python types
QStringList list;
Q_INIT_RESOURCE(qcodeeditor_resources);
QFile fl(":/languages/python.xml");
if (!fl.open(QIODevice::ReadOnly))
{
return;
}
QLanguage language(&fl);
if (!language.isLoaded())
{
return;
}
auto keys = language.keys();
for (auto&& key : keys)
{
auto names = language.names(key);
list.append(names);
}
setModel(new QStringListModel(list, this));
setCompletionColumn(0);
setModelSorting(QCompleter::CaseInsensitivelySortedModel);
setCaseSensitivity(Qt::CaseSensitive);
setWrapAround(true);
}

View file

@ -0,0 +1,163 @@
// QCodeEditor
#include <QPythonHighlighter>
#include <QLanguage>
#include <QSyntaxStyle>
// Qt
#include <QFile>
#include <QDebug>
QPythonHighlighter::QPythonHighlighter(QTextDocument* document) :
QStyleSyntaxHighlighter(document),
m_highlightRules (),
m_highlightBlockRules(),
m_includePattern (QRegularExpression(R"(import \w+)")),
m_functionPattern (QRegularExpression(R"(\b([A-Za-z0-9_]+(?:\.))*([A-Za-z0-9_]+)(?=\())")),
m_defTypePattern (QRegularExpression(R"(\b([A-Za-z0-9_]+)\s+[A-Za-z]{1}[A-Za-z0-9_]+\s*[;=])"))
{
Q_INIT_RESOURCE(qcodeeditor_resources);
QFile fl(":/languages/python.xml");
if (!fl.open(QIODevice::ReadOnly))
{
return;
}
QLanguage language(&fl);
if (!language.isLoaded())
{
return;
}
auto keys = language.keys();
for (auto&& key : keys)
{
auto names = language.names(key);
for (auto&& name : names)
{
m_highlightRules.append({
QRegularExpression(QString(R"(\b%1\b)").arg(name)),
key
});
}
}
// Following rules has higher priority to display
// than language specific keys
// So they must be applied at last.
// Numbers
m_highlightRules.append({
QRegularExpression(R"(\b(0b|0x){0,1}[\d.']+\b)"),
"Number"
});
// Strings
m_highlightRules.append({
QRegularExpression(R"("[^\n"]*")"),
"String"
});
m_highlightRules.append({
QRegularExpression(R"('[^\n"]*')"),
"String"
});
// Single line comment
m_highlightRules.append({
QRegularExpression("#[^\n]*"),
"Comment"
});
// Multiline string
m_highlightBlockRules.append({
QRegularExpression("(''')"),
QRegularExpression("(''')"),
"String"
});
m_highlightBlockRules.append({
QRegularExpression("(\"\"\")"),
QRegularExpression("(\"\"\")"),
"String"
});
}
void QPythonHighlighter::highlightBlock(const QString& text)
{
// Checking for function
{
auto matchIterator = m_functionPattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
syntaxStyle()->getFormat("Type")
);
setFormat(
match.capturedStart(2),
match.capturedLength(2),
syntaxStyle()->getFormat("Function")
);
}
}
for (auto& rule : m_highlightRules)
{
auto matchIterator = rule.pattern.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
syntaxStyle()->getFormat(rule.formatName)
);
}
}
setCurrentBlockState(0);
int startIndex = 0;
int highlightRuleId = previousBlockState();
if (highlightRuleId < 1 || highlightRuleId > m_highlightBlockRules.size()) {
for(int i = 0; i < m_highlightBlockRules.size(); ++i) {
startIndex = text.indexOf(m_highlightBlockRules.at(i).startPattern);
if (startIndex >= 0) {
highlightRuleId = i + 1;
break;
}
}
}
while (startIndex >= 0)
{
const auto &blockRules = m_highlightBlockRules.at(highlightRuleId - 1);
auto match = blockRules.endPattern.match(text, startIndex+1); // Should be + length of start pattern
int endIndex = match.capturedStart();
int matchLength = 0;
if (endIndex == -1)
{
setCurrentBlockState(highlightRuleId);
matchLength = text.length() - startIndex;
}
else
{
matchLength = endIndex - startIndex + match.capturedLength();
}
setFormat(
startIndex,
matchLength,
syntaxStyle()->getFormat(blockRules.formatName)
);
startIndex = text.indexOf(blockRules.startPattern, startIndex + matchLength);
}
}

View file

@ -0,0 +1,19 @@
// QCodeEditor
#include <QStyleSyntaxHighlighter>
QStyleSyntaxHighlighter::QStyleSyntaxHighlighter(QTextDocument* document) :
QSyntaxHighlighter(document),
m_syntaxStyle(nullptr)
{
}
void QStyleSyntaxHighlighter::setSyntaxStyle(QSyntaxStyle* style)
{
m_syntaxStyle = style;
}
QSyntaxStyle* QStyleSyntaxHighlighter::syntaxStyle() const
{
return m_syntaxStyle;
}

View file

@ -0,0 +1,155 @@
// QCodeEditor
#include <QSyntaxStyle>
// Qt
#include <QDebug>
#include <QXmlStreamReader>
#include <QFile>
QSyntaxStyle::QSyntaxStyle(QObject* parent) :
QObject(parent),
m_name(),
m_data(),
m_loaded(false)
{
}
bool QSyntaxStyle::load(QString fl)
{
QXmlStreamReader reader(fl);
while (!reader.atEnd() && !reader.hasError())
{
auto token = reader.readNext();
if(token == QXmlStreamReader::StartElement)
{
if (reader.name().toString() == "style-scheme") {
if (reader.attributes().hasAttribute("name"))
{
m_name = reader.attributes().value("name").toString();
}
} else if (reader.name().toString() == "style") {
auto attributes = reader.attributes();
auto name = attributes.value("name");
QTextCharFormat format;
if (attributes.hasAttribute("background"))
{
format.setBackground(QColor(attributes.value("background").toString()));
}
if (attributes.hasAttribute("foreground"))
{
format.setForeground(QColor(attributes.value("foreground").toString()));
}
if (attributes.hasAttribute("bold")
&& attributes.value("bold").toString() == "true") {
format.setFontWeight(QFont::Weight::Bold);
}
if (attributes.hasAttribute("italic")
&& attributes.value("italic").toString() == "true") {
format.setFontItalic(true);
}
if (attributes.hasAttribute("underlineStyle"))
{
auto underline = attributes.value("underlineStyle").toString();
auto s = QTextCharFormat::UnderlineStyle::NoUnderline;
if (underline == "SingleUnderline")
{
s = QTextCharFormat::UnderlineStyle::SingleUnderline;
}
else if (underline == "DashUnderline")
{
s = QTextCharFormat::UnderlineStyle::DashUnderline;
}
else if (underline == "DotLine")
{
s = QTextCharFormat::UnderlineStyle::DotLine;
}
else if (underline == "DashDotLine")
{
s = QTextCharFormat::DashDotLine;
}
else if (underline == "DashDotDotLine")
{
s = QTextCharFormat::DashDotDotLine;
}
else if (underline == "WaveUnderline")
{
s = QTextCharFormat::WaveUnderline;
}
else if (underline == "SpellCheckUnderline")
{
s = QTextCharFormat::SpellCheckUnderline;
}
else
{
qDebug() << "Unknown underline value " << underline;
}
format.setUnderlineStyle(s);
}
m_data[name.toString()] = format;
}
}
}
m_loaded = !reader.hasError();
return m_loaded;
}
QString QSyntaxStyle::name() const
{
return m_name;
}
QTextCharFormat QSyntaxStyle::getFormat(QString name) const
{
auto result = m_data.find(name);
if (result == m_data.end())
{
return QTextCharFormat();
}
return result.value();
}
bool QSyntaxStyle::isLoaded() const
{
return m_loaded;
}
QSyntaxStyle* QSyntaxStyle::defaultStyle()
{
static QSyntaxStyle style;
if (!style.isLoaded())
{
Q_INIT_RESOURCE(qcodeeditor_resources);
QFile fl(":/default_style.xml");
if (!fl.open(QIODevice::ReadOnly))
{
return &style;
}
if (!style.load(fl.readAll()))
{
qDebug() << "Can't load default style.";
}
}
return &style;
}

View file

@ -0,0 +1,111 @@
// QCodeEditor
#include <QXMLHighlighter>
#include <QSyntaxStyle>
QXMLHighlighter::QXMLHighlighter(QTextDocument* document) :
QStyleSyntaxHighlighter(document),
m_xmlKeywordRegexes (),
m_xmlElementRegex (R"(<[\s]*[/]?[\s]*([^\n][a-zA-Z-_:]*)(?=[\s/>]))"),
m_xmlAttributeRegex (R"(\w+(?=\=))"),
m_xmlValueRegex (R"("[^\n"]+"(?=\??[\s/>]))"),
m_xmlCommentBeginRegex(R"(<!--)"),
m_xmlCommentEndRegex (R"(-->)")
{
m_xmlKeywordRegexes
<< QRegularExpression("<\\?")
<< QRegularExpression("/>")
<< QRegularExpression(">")
<< QRegularExpression("<")
<< QRegularExpression("</")
<< QRegularExpression("\\?>");
}
void QXMLHighlighter::highlightBlock(const QString& text)
{
// Special treatment for xml element regex as we use captured text to emulate lookbehind
auto matchIterator = m_xmlElementRegex.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
syntaxStyle()->getFormat("Keyword") // XML ELEMENT FORMAT
);
}
// Highlight xml keywords *after* xml elements to fix any occasional / captured into the enclosing element
for (auto&& regex : m_xmlKeywordRegexes)
{
highlightByRegex(
syntaxStyle()->getFormat("Keyword"),
regex,
text
);
}
highlightByRegex(
syntaxStyle()->getFormat("Text"),
m_xmlAttributeRegex,
text
);
setCurrentBlockState(0);
int startIndex = 0;
if (previousBlockState() != 1)
{
startIndex = text.indexOf(m_xmlCommentBeginRegex);
}
while (startIndex >= 0)
{
auto match = m_xmlCommentEndRegex.match(text, startIndex);
int endIndex = match.capturedStart();
int commentLength = 0;
if (endIndex == -1)
{
setCurrentBlockState(1);
commentLength = text.length() - startIndex;
}
else
{
commentLength = endIndex - startIndex + match.capturedLength();
}
setFormat(
startIndex,
commentLength,
syntaxStyle()->getFormat("Comment")
);
startIndex = text.indexOf(m_xmlCommentBeginRegex, startIndex + commentLength);
}
highlightByRegex(
syntaxStyle()->getFormat("String"),
m_xmlValueRegex,
text
);
}
void QXMLHighlighter::highlightByRegex(const QTextCharFormat& format, const QRegularExpression& regex, const QString& text)
{
auto matchIterator = regex.globalMatch(text);
while (matchIterator.hasNext())
{
auto match = matchIterator.next();
setFormat(
match.capturedStart(),
match.capturedLength(),
format
);
}
}