- Fehler in mehrzeiligen Kommentaren behoben
- Klammerpaare werden farblich hervorgehoben - Beim einrücken oder ausrücken eines markierten Blocks wird die Zeile, in der der Coursor steht, nicht mehr mitgerückt - Beim eintippen von Variablen wird eine Liste mit bereits deklarierten Variablen angezeigt
This commit is contained in:
243
src/editor/VariableCompleter.cpp
Normal file
243
src/editor/VariableCompleter.cpp
Normal file
@@ -0,0 +1,243 @@
|
||||
#include "VariableCompleter.h"
|
||||
#include "CodeEditor.h"
|
||||
|
||||
#include <QTextCursor>
|
||||
#include <QTextBlock>
|
||||
#include <QKeyEvent>
|
||||
#include <QListWidgetItem>
|
||||
#include <QAbstractItemView>
|
||||
#include <QScrollBar>
|
||||
#include <QApplication>
|
||||
#include <QScreen>
|
||||
|
||||
VariableCompleter::VariableCompleter(CodeEditor *editor)
|
||||
: QObject(editor)
|
||||
, m_editor(editor)
|
||||
{
|
||||
// Popup als eigenständiges, rahmenloses Werkzeugfenster
|
||||
m_popup = new QListWidget(nullptr);
|
||||
m_popup->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint |
|
||||
Qt::WindowStaysOnTopHint);
|
||||
m_popup->setAttribute(Qt::WA_ShowWithoutActivating);
|
||||
m_popup->setFocusPolicy(Qt::NoFocus);
|
||||
m_popup->setMaximumHeight(200);
|
||||
m_popup->setMinimumWidth(180);
|
||||
m_popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_popup->setStyleSheet(
|
||||
"QListWidget {"
|
||||
" border: 1px solid palette(mid);"
|
||||
" background: palette(toolTipBase);"
|
||||
" color: palette(toolTipText);"
|
||||
" font-family: Monospace;"
|
||||
"}"
|
||||
"QListWidget::item:selected {"
|
||||
" background: palette(highlight);"
|
||||
" color: palette(highlightedText);"
|
||||
"}"
|
||||
);
|
||||
|
||||
connect(m_popup, &QListWidget::itemActivated,
|
||||
this, &VariableCompleter::onItemActivated);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Nach jedem Tastendruck aufrufen
|
||||
// ---------------------------------------------------------------------------
|
||||
void VariableCompleter::handleKeyPress(QKeyEvent *event)
|
||||
{
|
||||
// Popup-Navigation
|
||||
if (m_popup->isVisible())
|
||||
{
|
||||
if (event->key() == Qt::Key_Escape)
|
||||
{
|
||||
hidePopup();
|
||||
return;
|
||||
}
|
||||
if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
|
||||
{
|
||||
if (m_popup->currentItem())
|
||||
{
|
||||
onItemActivated(m_popup->currentItem());
|
||||
}
|
||||
hidePopup();
|
||||
return;
|
||||
}
|
||||
if (event->key() == Qt::Key_Down)
|
||||
{
|
||||
const int next = qMin(m_popup->currentRow() + 1,
|
||||
m_popup->count() - 1);
|
||||
m_popup->setCurrentRow(next);
|
||||
return;
|
||||
}
|
||||
if (event->key() == Qt::Key_Up)
|
||||
{
|
||||
const int prev = qMax(m_popup->currentRow() - 1, 0);
|
||||
m_popup->setCurrentRow(prev);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Nach dem normalen Tastendruck Popup aktualisieren
|
||||
// (der Event wurde bereits an QPlainTextEdit weitergegeben)
|
||||
updatePopup();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Popup aktualisieren
|
||||
// ---------------------------------------------------------------------------
|
||||
void VariableCompleter::updatePopup()
|
||||
{
|
||||
const QString prefix = currentPrefix();
|
||||
|
||||
// Nur bei $ und mindestens einem weiteren Zeichen anzeigen
|
||||
if (prefix.length() < 2 || !prefix.startsWith('$'))
|
||||
{
|
||||
hidePopup();
|
||||
return;
|
||||
}
|
||||
|
||||
const QStringList allVars = collectVariables();
|
||||
const QString filter = prefix.toLower();
|
||||
|
||||
QStringList matches;
|
||||
for (const QString &var : allVars)
|
||||
{
|
||||
if (var.toLower().startsWith(filter) && var != prefix)
|
||||
{
|
||||
matches.append(var);
|
||||
}
|
||||
}
|
||||
|
||||
if (matches.isEmpty())
|
||||
{
|
||||
hidePopup();
|
||||
return;
|
||||
}
|
||||
|
||||
// Popup befüllen
|
||||
m_popup->clear();
|
||||
for (const QString &var : matches)
|
||||
{
|
||||
m_popup->addItem(var);
|
||||
}
|
||||
m_popup->setCurrentRow(0);
|
||||
|
||||
// Größe anpassen
|
||||
const int itemHeight = m_popup->sizeHintForRow(0) + 2;
|
||||
const int height = qMin(matches.size() * itemHeight + 4, 200);
|
||||
m_popup->setFixedHeight(height);
|
||||
|
||||
// Position unter dem Cursor
|
||||
const QRect cursorRect = m_editor->cursorRect();
|
||||
QPoint pos = m_editor->viewport()->mapToGlobal(
|
||||
QPoint(cursorRect.left(), cursorRect.bottom() + 2)
|
||||
);
|
||||
|
||||
// Nicht aus dem Bildschirm herausragen
|
||||
const QRect screen = QApplication::primaryScreen()->availableGeometry();
|
||||
if (pos.x() + m_popup->width() > screen.right())
|
||||
{
|
||||
pos.setX(screen.right() - m_popup->width());
|
||||
}
|
||||
if (pos.y() + height > screen.bottom())
|
||||
{
|
||||
pos.setY(cursorRect.top() - height - 2);
|
||||
}
|
||||
|
||||
m_popup->move(pos);
|
||||
m_popup->show();
|
||||
m_popup->raise();
|
||||
}
|
||||
|
||||
void VariableCompleter::hidePopup()
|
||||
{
|
||||
m_popup->hide();
|
||||
m_popup->clear();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Variablen im aktuellen Dokument sammeln
|
||||
// ---------------------------------------------------------------------------
|
||||
QStringList VariableCompleter::collectVariables() const
|
||||
{
|
||||
static const QRegularExpression varRegex(R"(\$[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)");
|
||||
|
||||
QSet<QString> seen;
|
||||
QStringList result;
|
||||
|
||||
const QString text = m_editor->toPlainText();
|
||||
QRegularExpressionMatchIterator it = varRegex.globalMatch(text);
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
const QString var = it.next().captured(0);
|
||||
if (!seen.contains(var))
|
||||
{
|
||||
seen.insert(var);
|
||||
result.append(var);
|
||||
}
|
||||
}
|
||||
|
||||
result.sort(Qt::CaseInsensitive);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Text links vom Cursor — das angefangene $variable
|
||||
// ---------------------------------------------------------------------------
|
||||
QString VariableCompleter::currentPrefix() const
|
||||
{
|
||||
const QTextCursor cursor = m_editor->textCursor();
|
||||
const QString line = cursor.block().text();
|
||||
const int col = cursor.columnNumber();
|
||||
|
||||
if (col == 0)
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
// Rückwärts gehen bis zum $ oder einem Nicht-Bezeichner-Zeichen
|
||||
int start = col - 1;
|
||||
while (start > 0)
|
||||
{
|
||||
const QChar c = line[start - 1];
|
||||
if (!c.isLetterOrNumber() && c != '_' && c != '$')
|
||||
{
|
||||
break;
|
||||
}
|
||||
--start;
|
||||
}
|
||||
|
||||
const QString token = line.mid(start, col - start);
|
||||
return token.startsWith('$') ? token : QString();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Klick oder Enter — Variable einfügen
|
||||
// ---------------------------------------------------------------------------
|
||||
void VariableCompleter::onItemActivated(QListWidgetItem *item)
|
||||
{
|
||||
if (!item)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QString selected = item->text();
|
||||
const QString prefix = currentPrefix();
|
||||
|
||||
if (prefix.isEmpty())
|
||||
{
|
||||
hidePopup();
|
||||
return;
|
||||
}
|
||||
|
||||
// Prefix durch den vollständigen Variablennamen ersetzen
|
||||
QTextCursor cursor = m_editor->textCursor();
|
||||
cursor.movePosition(QTextCursor::Left,
|
||||
QTextCursor::KeepAnchor,
|
||||
prefix.length());
|
||||
cursor.insertText(selected);
|
||||
|
||||
m_editor->setTextCursor(cursor);
|
||||
hidePopup();
|
||||
}
|
||||
Reference in New Issue
Block a user