- Bug in Funktionanzeige behoben

- Auszuschliessende Verzeichnisse in Projektfunktionen hinzugefügt
- Dateien können als Paramter übergeben werden
- Englisch hinzugefügt
This commit is contained in:
2026-08-25 21:57:23 +02:00
parent 04c5212d54
commit cee8f57ab7
14 changed files with 233 additions and 14 deletions

View File

@@ -14,6 +14,7 @@
#include <QResizeEvent>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QFocusEvent>
#include <QScrollBar>
#include <QFile>
#include <QTextStream>
@@ -747,3 +748,15 @@ void CodeEditor::keyPressEvent(QKeyEvent *event)
m_varCompleter->handleKeyPress(event);
}
// ---------------------------------------------------------------------------
// Fokus verloren — z. B. beim Wechsel zu einem anderen Tab.
// Ein noch offenes Variablen-Popup muss hier geschlossen werden, sonst
// bleibt es als verwaistes Fenster stehen und kann bei mehreren offenen
// Dateien den Fokus blockieren.
// ---------------------------------------------------------------------------
void CodeEditor::focusOutEvent(QFocusEvent *event)
{
QPlainTextEdit::focusOutEvent(event);
m_varCompleter->notifyFocusLost();
}

View File

@@ -56,11 +56,13 @@ signals:
void fileSaved(const QString &filePath);
void navigateToRequested(const QString &filePath, int line);
protected: void resizeEvent(QResizeEvent *event) override;
protected:
void resizeEvent(QResizeEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
void focusOutEvent(QFocusEvent *event) override;
private slots:
void updateLineNumberAreaWidth(int newBlockCount);

View File

@@ -4,6 +4,9 @@
#include <QFileInfo>
#include <QFont>
#include <QProgressBar>
#include <QSettings>
#include <QFileDialog>
#include <QDir>
FunctionListPanel::FunctionListPanel(QWidget *parent)
: QWidget(parent)
@@ -67,6 +70,78 @@ void FunctionListPanel::setupUi()
root->addLayout(toolRow);
// ---- Exclude-Verzeichnisse ----
QHBoxLayout *excludeRow = new QHBoxLayout();
excludeRow->setContentsMargins(6, 0, 6, 4);
excludeRow->addWidget(new QLabel(tr("Ausschließen:"), this));
m_excludeEdit = new QLineEdit(this);
m_excludeEdit->setPlaceholderText(tr("vendor lib node_modules (leerzeichen-getrennt)"));
m_excludeEdit->setToolTip(tr(
"Verzeichnisnamen die beim Scan übersprungen werden.\n"
"Leerzeichen-getrennt, z.B.: vendor lib node_modules cache"
));
m_excludeEdit->setText(tr("vendor lib node_modules"));
excludeRow->addWidget(m_excludeEdit, 1);
// Verzeichnis-Auswahl Button
QPushButton *btnBrowse = new QPushButton(tr("+ Verzeichnis"), this);
btnBrowse->setToolTip(tr("Verzeichnis aus dem Projekt auswählen und zur Ausschlussliste hinzufügen"));
connect(btnBrowse, &QPushButton::clicked, this, [this]()
{
const QString startPath = m_projectRoot.isEmpty()
? QDir::homePath()
: m_projectRoot;
const QString dir = QFileDialog::getExistingDirectory(
this,
tr("Verzeichnis ausschließen"),
startPath,
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks
);
if (dir.isEmpty())
{
return;
}
// Nur den letzten Verzeichnisnamen verwenden, nicht den vollen Pfad
// So funktioniert der Exclude auch für gleichnamige Unterverzeichnisse
const QString dirName = QFileInfo(dir).fileName();
// Prüfen ob bereits in der Liste
const QStringList current = m_excludeEdit->text()
.simplified()
.split(' ', Qt::SkipEmptyParts);
if (current.contains(dirName, Qt::CaseInsensitive))
{
return;
}
// Anhängen
QString newText = m_excludeEdit->text().trimmed();
if (!newText.isEmpty())
{
newText += ' ';
}
newText += dirName;
m_excludeEdit->setText(newText);
});
excludeRow->addWidget(btnBrowse);
// Löschen-Button — ganzes Feld leeren
QPushButton *btnClear = new QPushButton(tr(""), this);
btnClear->setFixedWidth(28);
btnClear->setFlat(true);
btnClear->setToolTip(tr("Ausschlussliste leeren"));
connect(btnClear, &QPushButton::clicked, this, [this]()
{
m_excludeEdit->clear();
});
excludeRow->addWidget(btnClear);
root->addLayout(excludeRow);
// ---- Status ----
m_statusLabel = new QLabel(this);
m_statusLabel->setContentsMargins(6, 0, 6, 2);
@@ -107,9 +182,14 @@ void FunctionListPanel::setupUi()
// ---------------------------------------------------------------------------
void FunctionListPanel::setProjectRoot(const QString &path)
{
// Alte Exclude-Liste für das vorherige Projekt speichern
saveExcludeDirsForProject();
m_projectRoot = path;
if (!path.isEmpty())
{
loadExcludeDirsForProject();
refresh();
}
else
@@ -147,16 +227,21 @@ void FunctionListPanel::refresh()
m_watcher->waitForFinished();
}
saveExcludeDirsForProject();
m_statusLabel->setText(tr("Scanne…"));
m_btnRefresh->setEnabled(false);
const QString root = m_projectRoot;
const QString root = m_projectRoot;
const QStringList excludeDirs = m_excludeEdit->text()
.simplified()
.split(' ', Qt::SkipEmptyParts);
FunctionScanner *scanner = m_scanner;
QFuture<QList<FunctionScanner::FunctionInfo>> future =
QtConcurrent::run([scanner, root]()
QtConcurrent::run([scanner, root, excludeDirs]()
{
return scanner->scanDirectory(root);
return scanner->scanDirectory(root, {"*.php"}, excludeDirs);
});
m_watcher->setFuture(future);
@@ -330,3 +415,35 @@ void FunctionListPanel::onGroupingChanged(int /*index*/)
populateTree(m_lastResult);
}
}
// ---------------------------------------------------------------------------
// Ausschlussliste projektspezifisch speichern/laden
// (gleiches Schema wie bei der Suche nach toten Funktionen)
// ---------------------------------------------------------------------------
void FunctionListPanel::saveExcludeDirsForProject()
{
if (m_projectRoot.isEmpty())
{
return;
}
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
const QString key = "funclist/exclude/" +
QString(m_projectRoot).replace('/', '_').replace('\\', '_');
s.setValue(key, m_excludeEdit->text());
s.setValue("funclist/excludeDirs", m_excludeEdit->text()); // globaler Fallback
}
void FunctionListPanel::loadExcludeDirsForProject()
{
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
const QString key = "funclist/exclude/" +
QString(m_projectRoot).replace('/', '_').replace('\\', '_');
// Projektspezifisch vorhanden? Sonst globalen Fallback nehmen
const QString saved = s.value(key,
s.value("funclist/excludeDirs", "vendor lib node_modules").toString()
).toString();
m_excludeEdit->setText(saved);
}

View File

@@ -11,6 +11,7 @@
#include <QComboBox>
#include <QFutureWatcher>
#include <QString>
#include <QStringList>
#include "FunctionScanner.h"
@@ -21,6 +22,10 @@
// Gruppierung: nach Datei oder nach Klasse
// Klick: öffnet Datei und springt zur Definition
// Aktualisierung: automatisch nach jedem Speichern
//
// Verzeichnisse wie vendor/ oder node_modules/ lassen sich über eine
// Ausschlussliste vom Scan ausnehmen (gleiches Prinzip wie bei der
// Suche nach toten Funktionen).
// ---------------------------------------------------------------------------
class FunctionListPanel : public QWidget
{
@@ -48,6 +53,8 @@ private:
void setupUi();
void populateTree(const QList<FunctionScanner::FunctionInfo> &functions);
void applyFilter(const QString &text);
void loadExcludeDirsForProject();
void saveExcludeDirsForProject();
static void formatFunctionItem(QTreeWidgetItem *item,
const FunctionScanner::FunctionInfo &f);
@@ -59,6 +66,9 @@ private:
QLabel *m_statusLabel = nullptr;
QTreeWidget *m_tree = nullptr;
// Ausschlussliste — Verzeichnisnamen die beim Scan übersprungen werden
QLineEdit *m_excludeEdit = nullptr;
FunctionScanner *m_scanner = nullptr;
QFutureWatcher<QList<FunctionScanner::FunctionInfo>> *m_watcher = nullptr;

View File

@@ -16,7 +16,8 @@ FunctionScanner::FunctionScanner(QObject *parent)
// ---------------------------------------------------------------------------
QList<FunctionScanner::FunctionInfo> FunctionScanner::scanDirectory(
const QString &rootPath,
const QStringList &extensions) const
const QStringList &extensions,
const QStringList &excludeDirs) const
{
QList<FunctionInfo> results;
@@ -28,6 +29,27 @@ QList<FunctionScanner::FunctionInfo> FunctionScanner::scanDirectory(
while (it.hasNext())
{
const QString path = it.next();
// Exclude-Verzeichnisse prüfen (gleiche Logik wie DeadCodeAnalyzer)
bool excluded = false;
if (!excludeDirs.isEmpty())
{
const QStringList parts = path.split('/');
for (const QString &excl : excludeDirs)
{
if (parts.contains(excl.trimmed(), Qt::CaseInsensitive))
{
excluded = true;
break;
}
}
}
if (excluded)
{
continue;
}
results.append(scanFile(path));
}

View File

@@ -35,8 +35,11 @@ public:
explicit FunctionScanner(QObject *parent = nullptr);
// Synchroner Scan — für direkten Aufruf aus Threads
// excludeDirs: Verzeichnisnamen die komplett übersprungen werden
// z.B. {"vendor", "lib", "node_modules"}
QList<FunctionInfo> scanDirectory(const QString &rootPath,
const QStringList &extensions = {"*.php"}) const;
const QStringList &extensions = {"*.php"},
const QStringList &excludeDirs = {}) const;
// Scannt eine einzelne Datei
QList<FunctionInfo> scanFile(const QString &filePath) const;

View File

@@ -14,10 +14,15 @@ 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);
// Popup als reines Anzeige-Fenster — dieselbe Fensterart wie
// SignatureTooltip, die nachweislich nie den Fokus an sich reißt.
// Wichtig: Ein echtes Elternfenster (editor->window()) angeben, sonst
// behandelt der Fenstermanager das Popup als eigenständiges Fenster.
// Das führte dazu, dass bei mehreren offenen Dateien/Tabs verwaiste
// Popups entstehen konnten, die den Fokus übernehmen und die
// Texteingabe blockieren.
m_popup = new QListWidget(editor->window());
m_popup->setWindowFlags(Qt::ToolTip | Qt::FramelessWindowHint);
m_popup->setAttribute(Qt::WA_ShowWithoutActivating);
m_popup->setFocusPolicy(Qt::NoFocus);
m_popup->setMaximumHeight(200);
@@ -155,6 +160,14 @@ void VariableCompleter::hidePopup()
m_popup->clear();
}
// ---------------------------------------------------------------------------
// Editor hat den Fokus verloren (z. B. Tab-Wechsel) — Popup schließen
// ---------------------------------------------------------------------------
void VariableCompleter::notifyFocusLost()
{
hidePopup();
}
// ---------------------------------------------------------------------------
// Variablen im aktuellen Dokument sammeln
// ---------------------------------------------------------------------------

View File

@@ -24,6 +24,11 @@ public:
// Muss nach jedem Tastendruck aufgerufen werden
void handleKeyPress(QKeyEvent *event);
// Muss aufgerufen werden, wenn der Editor den Fokus verliert
// (z. B. beim Wechsel des Tabs) — schließt ein evtl. offenes Popup,
// damit es nicht als "Geisterfenster" stehen bleibt.
void notifyFocusLost();
private slots:
void onItemActivated(QListWidgetItem *item);