Suchen in Dateien hinzugefügt
@@ -29,6 +29,7 @@ find_package(Qt6 REQUIRED COMPONENTS
|
||||
Core
|
||||
Gui
|
||||
Widgets
|
||||
Concurrent
|
||||
)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
|
Before Width: | Height: | Size: 707 B After Width: | Height: | Size: 846 B |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 685 B After Width: | Height: | Size: 824 B |
BIN
resources/icon_24.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 269 KiB After Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 7.6 KiB |
@@ -35,6 +35,12 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
{
|
||||
m_projectManager->openProject(lastPath);
|
||||
}
|
||||
|
||||
// Letzte Session wiederherstellen (geöffnete Dateien + aktiver Tab)
|
||||
m_editor->restoreSession(
|
||||
m_settings->lastOpenFiles(),
|
||||
m_settings->lastActiveFile()
|
||||
);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() = default;
|
||||
@@ -110,6 +116,9 @@ void MainWindow::setupMenuBar()
|
||||
m_actSearch = mBearbeiten->addAction(tr("&Suchen / Ersetzen…"), this, &MainWindow::onShowSearch);
|
||||
m_actSearch->setShortcut(QKeySequence::Find);
|
||||
|
||||
m_actFileSearch = mBearbeiten->addAction(tr("In &Dateien suchen…"), this, &MainWindow::onShowFileSearch);
|
||||
m_actFileSearch->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_F);
|
||||
|
||||
// ---- Ansicht ----
|
||||
QMenu *mAnsicht = menuBar()->addMenu(tr("&Ansicht"));
|
||||
|
||||
@@ -272,10 +281,16 @@ void MainWindow::onAbout()
|
||||
// ---------------------------------------------------------------------------
|
||||
// Slots – Projekt
|
||||
// ---------------------------------------------------------------------------
|
||||
void MainWindow::onShowFileSearch()
|
||||
{
|
||||
m_editor->showFileSearchPanel();
|
||||
}
|
||||
|
||||
void MainWindow::onProjectOpened(const QString &path)
|
||||
{
|
||||
setWindowTitle(QString("BareCode – %1").arg(path));
|
||||
m_fileTree->setRootPath(path);
|
||||
m_editor->setSearchRoot(path);
|
||||
statusBar()->showMessage(tr("Projekt geöffnet: %1").arg(path), 4000);
|
||||
}
|
||||
|
||||
@@ -283,6 +298,7 @@ void MainWindow::onProjectClosed()
|
||||
{
|
||||
setWindowTitle("BareCode");
|
||||
m_fileTree->clearRoot();
|
||||
m_editor->setSearchRoot(QString());
|
||||
statusBar()->showMessage(tr("Projekt geschlossen"), 3000);
|
||||
}
|
||||
|
||||
@@ -294,6 +310,10 @@ void MainWindow::saveWindowState()
|
||||
QSettings s(QSettings::IniFormat, QSettings::UserScope, "BareCode", "BareCode");
|
||||
s.setValue("window/geometry", saveGeometry());
|
||||
s.setValue("window/state", saveState());
|
||||
|
||||
// Session speichern
|
||||
m_settings->setLastOpenFiles(m_editor->openFilePaths());
|
||||
m_settings->setLastActiveFile(m_editor->activeFilePath());
|
||||
}
|
||||
|
||||
void MainWindow::restoreWindowState()
|
||||
|
||||
@@ -41,6 +41,7 @@ private slots:
|
||||
void onUndo();
|
||||
void onRedo();
|
||||
void onShowSearch();
|
||||
void onShowFileSearch();
|
||||
// Ansicht
|
||||
void onToggleDarkMode(bool checked);
|
||||
// Hilfe
|
||||
@@ -80,6 +81,7 @@ private:
|
||||
QAction *m_actUndo = nullptr;
|
||||
QAction *m_actRedo = nullptr;
|
||||
QAction *m_actSearch = nullptr;
|
||||
QAction *m_actFileSearch = nullptr;
|
||||
QAction *m_actDarkMode = nullptr;
|
||||
QAction *m_actAbout = nullptr;
|
||||
};
|
||||
|
||||
@@ -88,3 +88,26 @@ void Settings::setLastProjectPath(const QString &path)
|
||||
{
|
||||
m_settings.setValue("project/lastPath", path);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Session – geöffnete Dateien
|
||||
// ---------------------------------------------------------------------------
|
||||
QStringList Settings::lastOpenFiles() const
|
||||
{
|
||||
return m_settings.value("session/openFiles", QStringList()).toStringList();
|
||||
}
|
||||
|
||||
void Settings::setLastOpenFiles(const QStringList &files)
|
||||
{
|
||||
m_settings.setValue("session/openFiles", files);
|
||||
}
|
||||
|
||||
QString Settings::lastActiveFile() const
|
||||
{
|
||||
return m_settings.value("session/activeFile", QString()).toString();
|
||||
}
|
||||
|
||||
void Settings::setLastActiveFile(const QString &file)
|
||||
{
|
||||
m_settings.setValue("session/activeFile", file);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,13 @@ public:
|
||||
int fileTreeWidth() const;
|
||||
void setFileTreeWidth(int width);
|
||||
|
||||
// Zuletzt geöffnete Dateien (Session-Wiederherstellung)
|
||||
QStringList lastOpenFiles() const;
|
||||
void setLastOpenFiles(const QStringList &files);
|
||||
|
||||
QString lastActiveFile() const;
|
||||
void setLastActiveFile(const QString &file);
|
||||
|
||||
// Recent
|
||||
QString lastProjectPath() const;
|
||||
void setLastProjectPath(const QString &path);
|
||||
|
||||
@@ -9,6 +9,8 @@ set(EDITOR_SOURCES
|
||||
EditorTab.h
|
||||
SearchPanel.cpp
|
||||
SearchPanel.h
|
||||
FileSearchPanel.cpp
|
||||
FileSearchPanel.h
|
||||
)
|
||||
|
||||
add_library(BareCode_Editor STATIC ${EDITOR_SOURCES})
|
||||
@@ -17,6 +19,7 @@ target_link_libraries(BareCode_Editor PUBLIC
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Widgets
|
||||
Qt6::Concurrent
|
||||
BareCode_Highlighter
|
||||
)
|
||||
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
#include "EditorTab.h"
|
||||
#include "CodeEditor.h"
|
||||
#include "SearchPanel.h"
|
||||
#include "FileSearchPanel.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QFile>
|
||||
#include <QTextDocument>
|
||||
#include <QTextBlock>
|
||||
|
||||
EditorPanel::EditorPanel(Settings *settings, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
@@ -28,15 +31,20 @@ void EditorPanel::setupUi()
|
||||
m_tabWidget->setDocumentMode(true);
|
||||
|
||||
m_searchPanel = new SearchPanel(this);
|
||||
m_fileSearch = new FileSearchPanel(this);
|
||||
|
||||
m_layout->addWidget(m_tabWidget, 1);
|
||||
m_layout->addWidget(m_searchPanel, 0);
|
||||
m_layout->addWidget(m_fileSearch, 0);
|
||||
|
||||
connect(m_tabWidget, &QTabWidget::tabCloseRequested,
|
||||
this, &EditorPanel::onTabCloseRequested);
|
||||
|
||||
connect(m_tabWidget, &QTabWidget::currentChanged,
|
||||
this, &EditorPanel::onCurrentTabChanged);
|
||||
|
||||
connect(m_fileSearch, &FileSearchPanel::fileLineRequested,
|
||||
this, &EditorPanel::goToLine);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -127,9 +135,88 @@ void EditorPanel::saveAllFiles()
|
||||
|
||||
void EditorPanel::showSearchPanel()
|
||||
{
|
||||
m_fileSearch->hide();
|
||||
m_searchPanel->activate();
|
||||
}
|
||||
|
||||
void EditorPanel::showFileSearchPanel()
|
||||
{
|
||||
m_searchPanel->hide();
|
||||
m_fileSearch->activate();
|
||||
}
|
||||
|
||||
void EditorPanel::setSearchRoot(const QString &path)
|
||||
{
|
||||
m_fileSearch->setSearchRoot(path);
|
||||
}
|
||||
|
||||
void EditorPanel::goToLine(const QString &filePath, int line)
|
||||
{
|
||||
// Datei öffnen falls noch nicht geöffnet
|
||||
openFile(filePath);
|
||||
|
||||
EditorTab *tab = m_openTabs.value(filePath, nullptr);
|
||||
if (!tab)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_tabWidget->setCurrentWidget(tab);
|
||||
|
||||
// Zur gewünschten Zeile springen
|
||||
CodeEditor *editor = tab->editor();
|
||||
QTextBlock block = editor->document()->findBlockByLineNumber(line - 1);
|
||||
if (block.isValid())
|
||||
{
|
||||
QTextCursor cursor(block);
|
||||
cursor.movePosition(QTextCursor::StartOfBlock);
|
||||
editor->setTextCursor(cursor);
|
||||
editor->centerCursor();
|
||||
editor->setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList EditorPanel::openFilePaths() const
|
||||
{
|
||||
QStringList paths;
|
||||
for (int i = 0; i < m_tabWidget->count(); ++i)
|
||||
{
|
||||
EditorTab *tab = qobject_cast<EditorTab *>(m_tabWidget->widget(i));
|
||||
if (tab)
|
||||
{
|
||||
paths.append(tab->filePath());
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
QString EditorPanel::activeFilePath() const
|
||||
{
|
||||
EditorTab *tab = currentTab();
|
||||
return tab ? tab->filePath() : QString();
|
||||
}
|
||||
|
||||
void EditorPanel::restoreSession(const QStringList &files, const QString &activeFile)
|
||||
{
|
||||
for (const QString &path : files)
|
||||
{
|
||||
if (QFile::exists(path))
|
||||
{
|
||||
openFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
// Aktiven Tab wiederherstellen
|
||||
if (!activeFile.isEmpty())
|
||||
{
|
||||
const int idx = findTabForFile(activeFile);
|
||||
if (idx != -1)
|
||||
{
|
||||
m_tabWidget->setCurrentIndex(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPanel::undo()
|
||||
{
|
||||
if (EditorTab *tab = currentTab())
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
class EditorTab;
|
||||
class Settings;
|
||||
class SearchPanel;
|
||||
class FileSearchPanel;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// EditorPanel – Rechtes Panel: Tab-Leiste + Editoren + Such/Ersetzen-Panel.
|
||||
@@ -25,7 +26,15 @@ public slots:
|
||||
void saveCurrentFile();
|
||||
void saveCurrentFileAs();
|
||||
void saveAllFiles();
|
||||
void setSearchRoot(const QString &path);
|
||||
void showSearchPanel();
|
||||
void showFileSearchPanel();
|
||||
void goToLine(const QString &filePath, int line);
|
||||
|
||||
// Session
|
||||
QStringList openFilePaths() const;
|
||||
QString activeFilePath() const;
|
||||
void restoreSession(const QStringList &files, const QString &activeFile);
|
||||
void undo();
|
||||
void redo();
|
||||
|
||||
@@ -41,10 +50,11 @@ private:
|
||||
int findTabForFile(const QString &filePath) const;
|
||||
EditorTab *currentTab() const;
|
||||
|
||||
Settings *m_settings = nullptr;
|
||||
QVBoxLayout *m_layout = nullptr;
|
||||
QTabWidget *m_tabWidget = nullptr;
|
||||
SearchPanel *m_searchPanel = nullptr;
|
||||
Settings *m_settings = nullptr;
|
||||
QVBoxLayout *m_layout = nullptr;
|
||||
QTabWidget *m_tabWidget = nullptr;
|
||||
SearchPanel *m_searchPanel = nullptr;
|
||||
FileSearchPanel *m_fileSearch = nullptr;
|
||||
|
||||
QHash<QString, EditorTab *> m_openTabs;
|
||||
};
|
||||
|
||||
330
src/editor/FileSearchPanel.cpp
Normal file
@@ -0,0 +1,330 @@
|
||||
#include "FileSearchPanel.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QRegularExpression>
|
||||
#include <QKeyEvent>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QFuture>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Konstruktor
|
||||
// ---------------------------------------------------------------------------
|
||||
FileSearchPanel::FileSearchPanel(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setupUi();
|
||||
hide();
|
||||
|
||||
m_watcher = new QFutureWatcher<QList<Match>>(this);
|
||||
connect(m_watcher, &QFutureWatcher<QList<Match>>::finished,
|
||||
this, &FileSearchPanel::onSearchFinished);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// UI
|
||||
// ---------------------------------------------------------------------------
|
||||
void FileSearchPanel::setupUi()
|
||||
{
|
||||
QVBoxLayout *root = new QVBoxLayout(this);
|
||||
root->setContentsMargins(6, 4, 6, 4);
|
||||
root->setSpacing(4);
|
||||
|
||||
// ---- Zeile 1: Suchbegriff ----
|
||||
QHBoxLayout *row1 = new QHBoxLayout();
|
||||
row1->addWidget(new QLabel(tr("Suchen in Dateien:"), this));
|
||||
|
||||
m_searchEdit = new QLineEdit(this);
|
||||
m_searchEdit->setPlaceholderText(tr("Suchbegriff…"));
|
||||
m_searchEdit->setClearButtonEnabled(true);
|
||||
row1->addWidget(m_searchEdit, 1);
|
||||
|
||||
m_btnSearch = new QPushButton(tr("Suchen"), this);
|
||||
m_btnSearch->setDefault(true);
|
||||
row1->addWidget(m_btnSearch);
|
||||
|
||||
m_btnClose = new QPushButton(tr("✕"), this);
|
||||
m_btnClose->setFixedWidth(24);
|
||||
m_btnClose->setFlat(true);
|
||||
m_btnClose->setToolTip(tr("Schließen"));
|
||||
row1->addWidget(m_btnClose);
|
||||
|
||||
root->addLayout(row1);
|
||||
|
||||
// ---- Zeile 2: Optionen + Filter ----
|
||||
QHBoxLayout *row2 = new QHBoxLayout();
|
||||
m_chkCase = new QCheckBox(tr("Groß-/Kleinschreibung"), this);
|
||||
m_chkWord = new QCheckBox(tr("Ganzes Wort"), this);
|
||||
m_chkRegex = new QCheckBox(tr("Regex"), this);
|
||||
|
||||
row2->addWidget(m_chkCase);
|
||||
row2->addWidget(m_chkWord);
|
||||
row2->addWidget(m_chkRegex);
|
||||
row2->addSpacing(12);
|
||||
|
||||
row2->addWidget(new QLabel(tr("Dateitypen:"), this));
|
||||
m_filterEdit = new QLineEdit(this);
|
||||
m_filterEdit->setText("*.html *.php *.css *.js *.c *.cpp *.h");
|
||||
m_filterEdit->setFixedWidth(220);
|
||||
m_filterEdit->setToolTip(tr("Leerzeichen-getrennte Muster, z.B.: *.php *.html"));
|
||||
row2->addWidget(m_filterEdit);
|
||||
row2->addStretch();
|
||||
|
||||
root->addLayout(row2);
|
||||
|
||||
// ---- Fortschritt + Status ----
|
||||
m_progress = new QProgressBar(this);
|
||||
m_progress->setRange(0, 0); // Unbestimmter Modus
|
||||
m_progress->setFixedHeight(4);
|
||||
m_progress->hide();
|
||||
root->addWidget(m_progress);
|
||||
|
||||
m_statusLabel = new QLabel(this);
|
||||
m_statusLabel->setStyleSheet("color: palette(mid);");
|
||||
root->addWidget(m_statusLabel);
|
||||
|
||||
// ---- Ergebnisliste ----
|
||||
m_results = new QTreeWidget(this);
|
||||
m_results->setHeaderHidden(true);
|
||||
m_results->setRootIsDecorated(true);
|
||||
m_results->setIndentation(16);
|
||||
m_results->setUniformRowHeights(true);
|
||||
m_results->setAlternatingRowColors(true);
|
||||
root->addWidget(m_results, 1);
|
||||
|
||||
// ---- Verbindungen ----
|
||||
connect(m_btnSearch, &QPushButton::clicked, this, &FileSearchPanel::onSearch);
|
||||
connect(m_searchEdit, &QLineEdit::returnPressed, this, &FileSearchPanel::onSearch);
|
||||
connect(m_btnClose, &QPushButton::clicked, this, [this]()
|
||||
{
|
||||
hide();
|
||||
});
|
||||
connect(m_results, &QTreeWidget::itemActivated,
|
||||
this, &FileSearchPanel::onResultActivated);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Öffentliche Schnittstelle
|
||||
// ---------------------------------------------------------------------------
|
||||
void FileSearchPanel::setSearchRoot(const QString &path)
|
||||
{
|
||||
m_searchRoot = path;
|
||||
}
|
||||
|
||||
void FileSearchPanel::activate()
|
||||
{
|
||||
show();
|
||||
m_searchEdit->setFocus();
|
||||
m_searchEdit->selectAll();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Suche starten
|
||||
// ---------------------------------------------------------------------------
|
||||
void FileSearchPanel::onSearch()
|
||||
{
|
||||
const QString needle = m_searchEdit->text().trimmed();
|
||||
if (needle.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_searchRoot.isEmpty())
|
||||
{
|
||||
m_statusLabel->setText(tr("Kein Projektverzeichnis geöffnet."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Laufende Suche abbrechen
|
||||
if (m_watcher->isRunning())
|
||||
{
|
||||
m_watcher->cancel();
|
||||
m_watcher->waitForFinished();
|
||||
}
|
||||
|
||||
m_results->clear();
|
||||
m_statusLabel->setText(tr("Suche läuft…"));
|
||||
m_progress->show();
|
||||
m_btnSearch->setEnabled(false);
|
||||
|
||||
const QString root = m_searchRoot;
|
||||
const bool cs = m_chkCase->isChecked();
|
||||
const bool word = m_chkWord->isChecked();
|
||||
const bool regex = m_chkRegex->isChecked();
|
||||
const QStringList extensions = m_filterEdit->text().simplified().split(' ',
|
||||
Qt::SkipEmptyParts);
|
||||
|
||||
QFuture<QList<Match>> future = QtConcurrent::run(
|
||||
[this, root, needle, cs, word, regex, extensions]()
|
||||
{
|
||||
return searchInFiles(root, needle, cs, word, regex, extensions);
|
||||
}
|
||||
);
|
||||
|
||||
m_watcher->setFuture(future);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Suchergebnisse anzeigen
|
||||
// ---------------------------------------------------------------------------
|
||||
void FileSearchPanel::onSearchFinished()
|
||||
{
|
||||
m_progress->hide();
|
||||
m_btnSearch->setEnabled(true);
|
||||
|
||||
if (m_watcher->isCanceled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QList<Match> matches = m_watcher->result();
|
||||
|
||||
// Ergebnisse gruppiert nach Datei aufbauen
|
||||
QString currentFile;
|
||||
QTreeWidgetItem *fileItem = nullptr;
|
||||
int fileCount = 0;
|
||||
int matchCount = 0;
|
||||
|
||||
for (const Match &m : matches)
|
||||
{
|
||||
if (m.filePath != currentFile)
|
||||
{
|
||||
currentFile = m.filePath;
|
||||
++fileCount;
|
||||
|
||||
fileItem = new QTreeWidgetItem(m_results);
|
||||
fileItem->setText(0, QFileInfo(m.filePath).fileName());
|
||||
fileItem->setToolTip(0, m.filePath);
|
||||
fileItem->setData(0, Qt::UserRole, m.filePath);
|
||||
fileItem->setData(0, Qt::UserRole + 1, -1);
|
||||
|
||||
QFont boldFont = fileItem->font(0);
|
||||
boldFont.setBold(true);
|
||||
fileItem->setFont(0, boldFont);
|
||||
fileItem->setExpanded(true);
|
||||
}
|
||||
|
||||
QTreeWidgetItem *lineItem = new QTreeWidgetItem(fileItem);
|
||||
lineItem->setText(0, QString(" Zeile %1: %2")
|
||||
.arg(m.line)
|
||||
.arg(m.content.trimmed().left(120)));
|
||||
lineItem->setToolTip(0, m.content.trimmed());
|
||||
lineItem->setData(0, Qt::UserRole, m.filePath);
|
||||
lineItem->setData(0, Qt::UserRole + 1, m.line);
|
||||
|
||||
++matchCount;
|
||||
}
|
||||
|
||||
// Datei-Titelzeilen um Trefferanzahl ergänzen
|
||||
for (int i = 0; i < m_results->topLevelItemCount(); ++i)
|
||||
{
|
||||
QTreeWidgetItem *item = m_results->topLevelItem(i);
|
||||
const int count = item->childCount();
|
||||
item->setText(0, QString("%1 (%2 Treffer)")
|
||||
.arg(QFileInfo(item->data(0, Qt::UserRole).toString()).fileName())
|
||||
.arg(count));
|
||||
}
|
||||
|
||||
if (matchCount == 0)
|
||||
{
|
||||
m_statusLabel->setText(tr("Keine Treffer gefunden."));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_statusLabel->setText(tr("%1 Treffer in %2 Datei(en).")
|
||||
.arg(matchCount)
|
||||
.arg(fileCount));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Klick auf Treffer → Datei + Zeile öffnen
|
||||
// ---------------------------------------------------------------------------
|
||||
void FileSearchPanel::onResultActivated(QTreeWidgetItem *item, int /*column*/)
|
||||
{
|
||||
const QString path = item->data(0, Qt::UserRole).toString();
|
||||
const int line = item->data(0, Qt::UserRole + 1).toInt();
|
||||
|
||||
if (path.isEmpty() || line < 0)
|
||||
{
|
||||
// Datei-Titelzeile: nur auf-/zuklappen
|
||||
item->setExpanded(!item->isExpanded());
|
||||
return;
|
||||
}
|
||||
|
||||
emit fileLineRequested(path, line);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Eigentliche Suchroutine (läuft in Thread-Pool)
|
||||
// ---------------------------------------------------------------------------
|
||||
QList<FileSearchPanel::Match> FileSearchPanel::searchInFiles(
|
||||
const QString &root,
|
||||
const QString &needle,
|
||||
bool caseSensitive,
|
||||
bool wholeWord,
|
||||
bool useRegex,
|
||||
const QStringList &extensions) const
|
||||
{
|
||||
QList<Match> results;
|
||||
|
||||
// Regulären Ausdruck vorbereiten
|
||||
QString pattern = useRegex ? needle : QRegularExpression::escape(needle);
|
||||
if (wholeWord)
|
||||
{
|
||||
pattern = "\\b" + pattern + "\\b";
|
||||
}
|
||||
|
||||
QRegularExpression re(pattern,
|
||||
caseSensitive
|
||||
? QRegularExpression::NoPatternOption
|
||||
: QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
if (!re.isValid())
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
// Verzeichnis rekursiv durchsuchen
|
||||
QDirIterator it(root,
|
||||
extensions.isEmpty()
|
||||
? QStringList("*")
|
||||
: extensions,
|
||||
QDir::Files,
|
||||
QDirIterator::Subdirectories);
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
if (m_watcher->isCanceled())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const QString filePath = it.next();
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
stream.setEncoding(QStringConverter::Utf8);
|
||||
|
||||
int lineNumber = 0;
|
||||
while (!stream.atEnd())
|
||||
{
|
||||
++lineNumber;
|
||||
const QString line = stream.readLine();
|
||||
|
||||
if (re.match(line).hasMatch())
|
||||
{
|
||||
results.append({ filePath, lineNumber, line });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
77
src/editor/FileSearchPanel.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QCheckBox>
|
||||
#include <QTreeWidget>
|
||||
#include <QTreeWidgetItem>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QProgressBar>
|
||||
#include <QFutureWatcher>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FileSearchPanel – Suche in allen Dateien eines Verzeichnisses.
|
||||
//
|
||||
// Ergebnisse werden als aufklappbare Liste angezeigt:
|
||||
// Dateiname (N Treffer)
|
||||
// └ Zeile 12: <Zeileninhalt>
|
||||
// └ Zeile 34: <Zeileninhalt>
|
||||
//
|
||||
// Klick auf einen Treffer öffnet die Datei im Editor und springt zur Zeile.
|
||||
// ---------------------------------------------------------------------------
|
||||
class FileSearchPanel : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FileSearchPanel(QWidget *parent = nullptr);
|
||||
|
||||
void setSearchRoot(const QString &path);
|
||||
|
||||
public slots:
|
||||
void activate();
|
||||
|
||||
signals:
|
||||
void fileLineRequested(const QString &filePath, int line);
|
||||
|
||||
private slots:
|
||||
void onSearch();
|
||||
void onResultActivated(QTreeWidgetItem *item, int column);
|
||||
void onSearchFinished();
|
||||
|
||||
private:
|
||||
struct Match
|
||||
{
|
||||
QString filePath;
|
||||
int line;
|
||||
QString content;
|
||||
};
|
||||
|
||||
void setupUi();
|
||||
QList<Match> searchInFiles(const QString &root,
|
||||
const QString &needle,
|
||||
bool caseSensitive,
|
||||
bool wholeWord,
|
||||
bool useRegex,
|
||||
const QStringList &extensions) const;
|
||||
|
||||
QString m_searchRoot;
|
||||
|
||||
QLineEdit *m_searchEdit = nullptr;
|
||||
QCheckBox *m_chkCase = nullptr;
|
||||
QCheckBox *m_chkWord = nullptr;
|
||||
QCheckBox *m_chkRegex = nullptr;
|
||||
QLineEdit *m_filterEdit = nullptr; // Dateiendungen-Filter
|
||||
QPushButton *m_btnSearch = nullptr;
|
||||
QPushButton *m_btnClose = nullptr;
|
||||
QLabel *m_statusLabel = nullptr;
|
||||
QTreeWidget *m_results = nullptr;
|
||||
QProgressBar *m_progress = nullptr;
|
||||
|
||||
QFutureWatcher<QList<Match>> *m_watcher = nullptr;
|
||||
};
|
||||