#include "SignatureHelper.h" #include "SignatureTooltip.h" #include "CodeEditor.h" #include #include #include #include #include #include #include #include SignatureHelper::SignatureHelper(CodeEditor *editor) : QObject(editor) , m_editor(editor) { // Tooltip als Kind des Viewports — bleibt im Fenster m_tooltip = new SignatureTooltip(editor->window()); loadDatabase(); connect(m_editor, &CodeEditor::cursorPositionChanged, this, &SignatureHelper::onCursorPositionChanged); } // --------------------------------------------------------------------------- // Datenbank laden // --------------------------------------------------------------------------- void SignatureHelper::loadDatabase() { QFile f(":/php_functions.json"); if (!f.open(QIODevice::ReadOnly)) { return; } const QJsonDocument doc = QJsonDocument::fromJson(f.readAll()); if (!doc.isArray()) { return; } for (const QJsonValue &val : doc.array()) { const QJsonObject obj = val.toObject(); const QString name = obj["name"].toString(); if (name.isEmpty()) { continue; } FunctionInfo info; info.signature = obj["signature"].toString(); info.description = obj["desc"].toString(); m_functions.insert(name.toLower(), info); } } // --------------------------------------------------------------------------- // Editor hat den Fokus verloren — Tooltip schließen, damit es nicht // als verwaistes Fenster über allen Workspaces stehen bleibt. // --------------------------------------------------------------------------- void SignatureHelper::notifyFocusLost() { m_tooltip->hide(); } // --------------------------------------------------------------------------- // Tooltip sofort schließen (z. B. per Escape) // --------------------------------------------------------------------------- void SignatureHelper::dismiss() { m_tooltip->hide(); } // --------------------------------------------------------------------------- // Cursor-Bewegung auswerten // --------------------------------------------------------------------------- void SignatureHelper::onCursorPositionChanged() { const QTextCursor cursor = m_editor->textCursor(); const QString block = cursor.block().text(); const int col = cursor.columnNumber(); const QString leftText = block.left(col); const QString funcName = extractFunctionName(leftText); if (funcName.isEmpty()) { m_tooltip->hide(); return; } // Klammern zählen — wenn alle geschlossen, Tooltip ausblenden if (countOpenParens(leftText) <= 0) { m_tooltip->hide(); return; } const QString key = funcName.toLower(); if (!m_functions.contains(key)) { m_tooltip->hide(); return; } const FunctionInfo &info = m_functions[key]; // Position unter dem Cursor berechnen const QRect cursorRect = m_editor->cursorRect(cursor); const QPoint globalPos = m_editor->viewport()->mapToGlobal( QPoint(cursorRect.left(), cursorRect.bottom() + 4) ); m_tooltip->showSignature(info.signature, info.description, globalPos); } // --------------------------------------------------------------------------- // Funktionsnamen links vor der öffnenden Klammer extrahieren // --------------------------------------------------------------------------- QString SignatureHelper::extractFunctionName(const QString &text) const { // Wir suchen das letzte '(' das zu einem Funktionsnamen gehört. // Dabei müssen wir verschachtelte Klammern korrekt behandeln. int depth = 0; int openPos = -1; for (int i = text.length() - 1; i >= 0; --i) { const QChar ch = text[i]; if (ch == ')') { ++depth; } else if (ch == '(') { if (depth == 0) { openPos = i; break; } --depth; } } if (openPos <= 0) { return QString(); } // Funktionsnamen direkt links von '(' lesen int end = openPos - 1; // Leerzeichen überspringen while (end >= 0 && text[end].isSpace()) { --end; } if (end < 0) { return QString(); } // Bezeichner-Zeichen sammeln (Buchstaben, Ziffern, _, :, \) int start = end; while (start > 0 && (text[start - 1].isLetterOrNumber() || text[start - 1] == '_' || text[start - 1] == ':' || text[start - 1] == '\\')) { --start; } const QString name = text.mid(start, end - start + 1); // Schlüsselwörter und leere Namen ausschließen static const QStringList keywords = { "if", "else", "elseif", "while", "for", "foreach", "switch", "match", "catch", "function", "fn" }; if (name.isEmpty() || keywords.contains(name.toLower())) { return QString(); } // Nur den letzten Teil nach :: oder -> nehmen const int colonPos = name.lastIndexOf("::"); const int arrowPos = name.lastIndexOf("->"); const int backslashPos = name.lastIndexOf("\\"); const int splitPos = qMax(backslashPos, qMax(colonPos, arrowPos)); if (splitPos >= 0) { return name.mid(splitPos + (name[splitPos] == ':' ? 2 : (name[splitPos] == '\\' ? 1 : 2))); } return name; } // --------------------------------------------------------------------------- // Offene Klammern zählen // --------------------------------------------------------------------------- int SignatureHelper::countOpenParens(const QString &text) const { int depth = 0; bool inString = false; QChar stringChar; for (int i = 0; i < text.length(); ++i) { const QChar ch = text[i]; // Einfache String-Erkennung (kein vollständiger PHP-Parser) if (!inString && (ch == '\'' || ch == '"')) { inString = true; stringChar = ch; continue; } if (inString) { if (ch == stringChar && (i == 0 || text[i - 1] != '\\')) { inString = false; } continue; } if (ch == '(') { ++depth; } else if (ch == ')') { --depth; } } return depth; }