2018-04-08 09:07:43 +02:00
|
|
|
|
#include "CodeEditor.h"
|
|
|
|
|
|
#include "EditorGutter.h"
|
|
|
|
|
|
#include <QPainter>
|
|
|
|
|
|
#include <QTextBlock>
|
2018-12-28 15:26:24 +01:00
|
|
|
|
#include <QDebug>
|
2018-04-08 09:07:43 +02:00
|
|
|
|
//
|
|
|
|
|
|
// Adapted from codeeditor example
|
|
|
|
|
|
// http://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html
|
|
|
|
|
|
//
|
|
|
|
|
|
// Used term gutter as I want to expand it to do other things to like error
|
|
|
|
|
|
// position marking.
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
CodeEditor::CodeEditor(QWidget *parent)
|
|
|
|
|
|
: QPlainTextEdit(parent)
|
|
|
|
|
|
, gutterArea(new EditorGutter(this))
|
2025-02-23 08:32:15 +01:00
|
|
|
|
, colorTheme(GetColorTheme())
|
2018-04-08 09:07:43 +02:00
|
|
|
|
{
|
|
|
|
|
|
connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateGutterAreaWidth(int)));
|
|
|
|
|
|
connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateGutterArea(QRect,int)));
|
|
|
|
|
|
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
|
2018-04-09 21:44:00 +02:00
|
|
|
|
connect(this, SIGNAL(textChanged()), this, SLOT(onTextChanged()));
|
2018-04-08 09:07:43 +02:00
|
|
|
|
|
2018-10-08 18:43:56 +02:00
|
|
|
|
setWordWrapMode(QTextOption::NoWrap);
|
|
|
|
|
|
|
2018-04-08 09:07:43 +02:00
|
|
|
|
updateGutterAreaWidth(0);
|
2018-12-28 15:26:24 +01:00
|
|
|
|
setTabSize(4);
|
2018-04-08 09:07:43 +02:00
|
|
|
|
highlightCurrentLine();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int CodeEditor::gutterAreaWidth()
|
|
|
|
|
|
{
|
|
|
|
|
|
int digits = 1;
|
|
|
|
|
|
int max = qMax(1, blockCount());
|
2022-04-10 09:32:02 +02:00
|
|
|
|
while (max >= 10)
|
|
|
|
|
|
{
|
2018-04-08 09:07:43 +02:00
|
|
|
|
max /= 10;
|
|
|
|
|
|
++digits;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-12-28 15:26:24 +01:00
|
|
|
|
int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * (digits + 2);
|
2018-04-08 09:07:43 +02:00
|
|
|
|
|
|
|
|
|
|
return space;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CodeEditor::updateGutterAreaWidth(int /* newBlockCount */)
|
|
|
|
|
|
{
|
|
|
|
|
|
setViewportMargins(gutterAreaWidth(), 0, 0, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CodeEditor::updateGutterArea(const QRect &rect, int dy)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (dy)
|
|
|
|
|
|
gutterArea->scroll(0, dy);
|
|
|
|
|
|
else
|
|
|
|
|
|
gutterArea->update(0, rect.y(), gutterArea->width(), rect.height());
|
|
|
|
|
|
|
|
|
|
|
|
if (rect.contains(viewport()->rect()))
|
|
|
|
|
|
updateGutterAreaWidth(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CodeEditor::resizeEvent(QResizeEvent *e)
|
|
|
|
|
|
{
|
|
|
|
|
|
QPlainTextEdit::resizeEvent(e);
|
|
|
|
|
|
|
|
|
|
|
|
QRect cr = contentsRect();
|
|
|
|
|
|
gutterArea->setGeometry(QRect(cr.left(), cr.top(), gutterAreaWidth(), cr.height()));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CodeEditor::highlightCurrentLine()
|
|
|
|
|
|
{
|
2018-04-09 21:44:00 +02:00
|
|
|
|
QTextEdit::ExtraSelection selection;
|
2025-02-23 08:32:15 +01:00
|
|
|
|
selection.format.setBackground(colorTheme.currentLine);
|
2018-04-09 21:44:00 +02:00
|
|
|
|
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
|
|
|
|
|
|
selection.cursor = textCursor();
|
|
|
|
|
|
selection.cursor.clearSelection();
|
|
|
|
|
|
currentLine = selection;
|
|
|
|
|
|
|
|
|
|
|
|
updateExtraSelections();
|
|
|
|
|
|
}
|
2018-04-08 09:07:43 +02:00
|
|
|
|
|
2018-04-09 21:44:00 +02:00
|
|
|
|
void CodeEditor::onTextChanged()
|
|
|
|
|
|
{
|
|
|
|
|
|
clearErrorMarkers();
|
|
|
|
|
|
}
|
2018-04-08 09:07:43 +02:00
|
|
|
|
|
2018-04-09 21:44:00 +02:00
|
|
|
|
void CodeEditor::addErrorMarker(int position, int length)
|
|
|
|
|
|
{
|
2022-04-10 10:29:40 +02:00
|
|
|
|
QTextEdit::ExtraSelection selection;
|
2025-02-23 08:32:15 +01:00
|
|
|
|
selection.format.setBackground(colorTheme.errorLine);
|
2018-04-09 21:44:00 +02:00
|
|
|
|
selection.format.setFontItalic(true);
|
|
|
|
|
|
selection.cursor = textCursor();
|
|
|
|
|
|
selection.cursor.setPosition(position);
|
|
|
|
|
|
int lineno = selection.cursor.blockNumber();
|
|
|
|
|
|
errorLines.insert(lineno);
|
|
|
|
|
|
selection.cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, length);
|
|
|
|
|
|
errorMarkers.append(selection);
|
|
|
|
|
|
|
|
|
|
|
|
update();
|
|
|
|
|
|
|
|
|
|
|
|
updateExtraSelections();
|
|
|
|
|
|
}
|
2018-04-08 09:07:43 +02:00
|
|
|
|
|
2018-04-09 21:44:00 +02:00
|
|
|
|
void CodeEditor::clearErrorMarkers()
|
|
|
|
|
|
{
|
|
|
|
|
|
errorMarkers.clear();
|
|
|
|
|
|
errorLines.clear();
|
|
|
|
|
|
update();
|
|
|
|
|
|
updateExtraSelections();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CodeEditor::updateExtraSelections()
|
|
|
|
|
|
{
|
|
|
|
|
|
QList<QTextEdit::ExtraSelection> extraSelections;
|
|
|
|
|
|
extraSelections.append(currentLine);
|
2018-12-16 15:38:32 +01:00
|
|
|
|
for (auto&& e : errorMarkers)
|
2018-04-09 21:44:00 +02:00
|
|
|
|
extraSelections.append(e);
|
2018-04-08 09:07:43 +02:00
|
|
|
|
|
|
|
|
|
|
setExtraSelections(extraSelections);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-10 10:29:40 +02:00
|
|
|
|
bool CodeEditor::lineHasError(int blockNumber) const
|
|
|
|
|
|
{
|
|
|
|
|
|
return errorLines.count(blockNumber) > 0;
|
2018-04-08 09:07:43 +02:00
|
|
|
|
}
|
2018-12-28 15:26:24 +01:00
|
|
|
|
|
|
|
|
|
|
void CodeEditor::keyPressEvent(QKeyEvent *e)
|
|
|
|
|
|
{
|
|
|
|
|
|
auto k = e->key();
|
2022-04-10 09:32:02 +02:00
|
|
|
|
switch (k) {
|
|
|
|
|
|
case Qt::Key_Tab:
|
|
|
|
|
|
if (indentSelection(true))
|
|
|
|
|
|
return;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case Qt::Key_Backtab:
|
|
|
|
|
|
if (indentSelection(false))
|
|
|
|
|
|
return;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case Qt::Key_Equal:
|
|
|
|
|
|
case Qt::Key_Plus:
|
|
|
|
|
|
if (e->modifiers().testFlag(Qt::ControlModifier))
|
|
|
|
|
|
{
|
|
|
|
|
|
auto f = font();
|
|
|
|
|
|
f.setPointSize(f.pointSize() + 1);
|
|
|
|
|
|
setFont(f);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case Qt::Key_Minus:
|
|
|
|
|
|
if (e->modifiers().testFlag(Qt::ControlModifier))
|
|
|
|
|
|
{
|
|
|
|
|
|
auto f = font();
|
|
|
|
|
|
f.setPointSize(f.pointSize() - 1);
|
|
|
|
|
|
setFont(f);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
2018-12-28 15:26:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
QPlainTextEdit::keyPressEvent(e);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-10 10:29:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
2018-12-28 15:26:24 +01:00
|
|
|
|
bool CodeEditor::indentSelection(bool indent)
|
|
|
|
|
|
{
|
|
|
|
|
|
auto cursor = textCursor();
|
|
|
|
|
|
|
|
|
|
|
|
if(!cursor.hasSelection())
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
auto first_pos = cursor.anchor();
|
|
|
|
|
|
auto end_pos = cursor.position();
|
|
|
|
|
|
|
|
|
|
|
|
if(first_pos > end_pos)
|
|
|
|
|
|
std::swap(first_pos, end_pos);
|
|
|
|
|
|
|
|
|
|
|
|
cursor.setPosition(first_pos, QTextCursor::MoveAnchor);
|
|
|
|
|
|
auto start_block = cursor.block().blockNumber();
|
|
|
|
|
|
|
|
|
|
|
|
cursor.setPosition(end_pos, QTextCursor::MoveAnchor);
|
|
|
|
|
|
auto end_block = cursor.block().blockNumber();
|
|
|
|
|
|
if (end_block == start_block)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
cursor.beginEditBlock();
|
|
|
|
|
|
cursor.setPosition(first_pos, QTextCursor::MoveAnchor);
|
|
|
|
|
|
const auto block_count = end_block - start_block;
|
2022-04-10 09:32:02 +02:00
|
|
|
|
for(int block = 0; block <= block_count; ++block)
|
|
|
|
|
|
{
|
2018-12-28 15:26:24 +01:00
|
|
|
|
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
|
2022-04-10 09:32:02 +02:00
|
|
|
|
if (indent)
|
2022-04-10 10:29:40 +02:00
|
|
|
|
insertIndentation(cursor);
|
2022-04-10 09:32:02 +02:00
|
|
|
|
else
|
2022-04-10 10:29:40 +02:00
|
|
|
|
removeIndentation(cursor);
|
2018-12-28 15:26:24 +01:00
|
|
|
|
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor);
|
|
|
|
|
|
}
|
|
|
|
|
|
cursor.endEditBlock();
|
|
|
|
|
|
|
2022-04-10 10:29:40 +02:00
|
|
|
|
makeSelection(cursor, first_pos, end_block);
|
2018-12-28 15:26:24 +01:00
|
|
|
|
|
|
|
|
|
|
setTextCursor(cursor);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-04-10 10:29:40 +02:00
|
|
|
|
void CodeEditor::insertIndentation(QTextCursor &cursor)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (m_useTab)
|
|
|
|
|
|
cursor.insertText("\t");
|
|
|
|
|
|
else
|
|
|
|
|
|
cursor.insertText(QString(m_tabSize, ' '));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CodeEditor::removeIndentation(QTextCursor &cursor)
|
|
|
|
|
|
{
|
|
|
|
|
|
// remove tab if there is a tab
|
|
|
|
|
|
cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
|
|
|
|
|
|
const QString text = cursor.selectedText();
|
|
|
|
|
|
int index = 0;
|
|
|
|
|
|
int pos = 0;
|
|
|
|
|
|
while (pos < m_tabSize && index < text.length())
|
|
|
|
|
|
{
|
|
|
|
|
|
QChar c = text[index++];
|
|
|
|
|
|
if (c == ' ')
|
|
|
|
|
|
++pos;
|
|
|
|
|
|
else if (c == '\t')
|
|
|
|
|
|
pos = ((pos + m_tabSize) / m_tabSize) * m_tabSize;
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
--index;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
|
|
|
|
|
|
for (int i = 0; i < index; ++i)
|
|
|
|
|
|
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
|
|
|
|
|
|
cursor.removeSelectedText();
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CodeEditor::makeSelection(QTextCursor &cursor, int first_pos, int end_block)
|
|
|
|
|
|
{
|
|
|
|
|
|
cursor.setPosition(first_pos, QTextCursor::MoveAnchor);
|
|
|
|
|
|
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
|
|
|
|
|
|
while(cursor.block().blockNumber() < end_block)
|
|
|
|
|
|
cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
|
|
|
|
|
|
|
|
|
|
|
|
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-12-28 15:26:24 +01:00
|
|
|
|
void CodeEditor::setFont(const QFont &f)
|
|
|
|
|
|
{
|
2022-04-10 10:29:40 +02:00
|
|
|
|
QWidget::setFont(f);
|
|
|
|
|
|
auto orig_tab = m_tabSize;
|
|
|
|
|
|
m_tabSize = 0;
|
|
|
|
|
|
setTabSize(orig_tab);
|
2018-12-28 15:26:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CodeEditor::setTabSize(int chars)
|
|
|
|
|
|
{
|
2022-04-10 10:29:40 +02:00
|
|
|
|
m_tabSize = chars;
|
|
|
|
|
|
int pixels = fontMetrics().horizontalAdvance(QString(chars, '0'));
|
|
|
|
|
|
this->setTabStopDistance(pixels);
|
2018-12-28 15:26:24 +01:00
|
|
|
|
}
|