diff --git a/RedPandaIDE/mainwindow.cpp b/RedPandaIDE/mainwindow.cpp index d6d4a682..aa1568d8 100644 --- a/RedPandaIDE/mainwindow.cpp +++ b/RedPandaIDE/mainwindow.cpp @@ -107,6 +107,13 @@ MainWindow::MainWindow(QWidget *parent) connect(ui->debugConsole,&QConsole::commandInput,this,&MainWindow::onDebugCommandInput); connect(ui->cbEvaluate->lineEdit(), &QLineEdit::returnPressed, this, &MainWindow::onDebugEvaluateInput); + + mSearchResultTreeModel = std::make_shared(&mSearchResultModel); + mSearchResultListModel = std::make_shared(&mSearchResultModel); + mSearchViewDelegate = std::make_shared(mSearchResultTreeModel); + ui->cbSearchHistory->view()->setModel(mSearchResultListModel.get()); + ui->searchView->setModel(mSearchResultTreeModel.get()); + ui->searchView->setItemDelegate(mSearchViewDelegate.get()); } MainWindow::~MainWindow() @@ -882,6 +889,11 @@ SearchDialog *MainWindow::searchDialog() const return mSearchDialog; } +SearchResultModel *MainWindow::searchResultModel() +{ + return &mSearchResultModel; +} + EditorList *MainWindow::editorList() const { return mEditorList; diff --git a/RedPandaIDE/mainwindow.h b/RedPandaIDE/mainwindow.h index 2ec161c1..361f13da 100644 --- a/RedPandaIDE/mainwindow.h +++ b/RedPandaIDE/mainwindow.h @@ -3,6 +3,7 @@ #include #include "common.h" +#include "widgets/searchresultview.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } @@ -81,6 +82,8 @@ public: SearchDialog *searchDialog() const; + SearchResultModel* searchResultModel(); + protected: void openFiles(const QStringList& files); void openFile(const QString& filename); @@ -213,6 +216,11 @@ private: SearchDialog *mSearchDialog; QList mRecentFileActions; + SearchResultModel mSearchResultModel; + PSearchResultListModel mSearchResultListModel; + PSearchResultTreeModel mSearchResultTreeModel; + PSearchResultTreeViewDelegate mSearchViewDelegate; + bool mMessageControlChanged; bool mTabMessagesTogglingState; bool mCheckSyntaxInBack; diff --git a/RedPandaIDE/mainwindow.ui b/RedPandaIDE/mainwindow.ui index c239963f..7bd02c9f 100644 --- a/RedPandaIDE/mainwindow.ui +++ b/RedPandaIDE/mainwindow.ui @@ -574,7 +574,7 @@ - + 0 @@ -641,7 +641,7 @@ - + 150 @@ -680,7 +680,11 @@ - + + + false + + @@ -1304,7 +1308,7 @@ IssuesTable QTableView -
widgets/issuestable.h
+
widgets/issuestable.h
QConsole diff --git a/RedPandaIDE/qsynedit/SynEdit.h b/RedPandaIDE/qsynedit/SynEdit.h index f50fbce2..8f2131ee 100644 --- a/RedPandaIDE/qsynedit/SynEdit.h +++ b/RedPandaIDE/qsynedit/SynEdit.h @@ -214,6 +214,10 @@ public: void setCaretXY(const BufferCoord& value); void setCaretXYEx(bool CallEnsureCursorPos, BufferCoord value); void setCaretXYCentered(bool ForceToMiddle, const BufferCoord& value); + void setCaretAndSelection(const BufferCoord& ptCaret, + const BufferCoord& ptBefore, + const BufferCoord& ptAfter); + void uncollapseAroundLine(int line); PSynEditFoldRange foldHidesLine(int line); void setSelText(const QString& Value); @@ -451,9 +455,7 @@ private: void DeleteFromTo(const BufferCoord& start, const BufferCoord& end); void SetSelWord(); void SetWordBlock(BufferCoord Value); - void setCaretAndSelection(const BufferCoord& ptCaret, - const BufferCoord& ptBefore, - const BufferCoord& ptAfter); + void processGutterClick(QMouseEvent* event); diff --git a/RedPandaIDE/widgets/searchdialog.cpp b/RedPandaIDE/widgets/searchdialog.cpp index e1f1ca97..669e2900 100644 --- a/RedPandaIDE/widgets/searchdialog.cpp +++ b/RedPandaIDE/widgets/searchdialog.cpp @@ -7,6 +7,7 @@ #include "../qsynedit/Search.h" #include "../qsynedit/SearchRegex.h" #include +#include SearchDialog::SearchDialog(QWidget *parent) : @@ -122,7 +123,7 @@ void SearchDialog::on_btnCancel_clicked() void SearchDialog::on_btnExecute_clicked() { - int findcount = 0; + int findCount = 0; SearchAction actionType; switch (mTabBar->currentIndex()) { @@ -187,13 +188,13 @@ void SearchDialog::on_btnExecute_clicked() if (actionType == SearchAction::Find) { Editor *e = pMainWindow->editorList()->getEditor(); if (e!=nullptr) { - findcount+=execute(e,ui->cbFind->currentText(),""); + findCount+=execute(e,ui->cbFind->currentText(),""); } - } else { + } else if (actionType == SearchAction::Replace) { Editor *e = pMainWindow->editorList()->getEditor(); if (e!=nullptr) { bool doPrompt = ui->chkPrompt->isChecked(); - findcount+=execute(e,ui->cbFind->currentText(),ui->cbReplace->currentText(), + findCount+=execute(e,ui->cbFind->currentText(),ui->cbReplace->currentText(), [&doPrompt](const QString& sSearch, const QString& sReplace, int Line, int ch, int wordLen){ if (doPrompt) { @@ -219,165 +220,67 @@ void SearchDialog::on_btnExecute_clicked() }); } + } else if (actionType == SearchAction::FindFiles) { + int fileSearched = 0; + int fileHitted = 0; + QString keyword = ui->cbFind->currentText(); + if (ui->rbOpenFiles->isChecked()) { + PSearchResults results = pMainWindow->searchResultModel()->addSearchResults( + keyword, + mSearchOptions, + SearchFileScope::openedFiles + ); + // loop through editors, add results to message control + for (int i=0;ieditorList()->pageCount();i++) { + Editor * e=pMainWindow->editorList()->operator[](i); + if (e!=nullptr) { + fileSearched++; + PSearchResultTreeItem parentItem = batchFindInEditor(e, + keyword); + int t = parentItem->results.size(); + findCount+=t; + if (t>0) { + fileHitted++; + results->results.append(parentItem); + } + } + } + pMainWindow->searchResultModel()->notifySearchResultsUpdated(); + } else if (ui->rbCurrentFile->isChecked()) { + PSearchResults results = pMainWindow->searchResultModel()->addSearchResults( + keyword, + mSearchOptions, + SearchFileScope::openedFiles + ); + Editor * e= pMainWindow->editorList()->getEditor(); + if (e!=nullptr) { + fileSearched++; + PSearchResultTreeItem parentItem = batchFindInEditor(e, + keyword); + int t = parentItem->results.size(); + findCount+=t; + if (t>0) { + fileHitted++; + results->results.append(parentItem); + } + } + pMainWindow->searchResultModel()->notifySearchResultsUpdated(); + } else if (ui->rbProject->isChecked()) { + // end else if rbProjectFiles.Checked then begin + // for I := 0 to MainForm.Project.Units.Count - 1 do begin + // e := MainForm.Project.Units[i].Editor; + // fCurFile := MainForm.Project.Units[i].FileName; + + // // file is already open, use memory + // if Assigned(e) then begin begin + // inc(fileSearched); + // t:=Execute(e.Text, actiontype); + // Inc(findcount, t); + // if t>0 then + // inc(filehitted); + // end; + } } - -// // Replace first, find to next -//end else if actiontype = faReplace then begin -// e := MainForm.EditorList.GetEditor; - -// if Assigned(e) then begin -// Inc(findcount, Execute(e.Text, faReplace)); -// if findcount > 0 then begin -// Exclude(fSearchOptions, ssoReplace); -// Inc(findcount, Execute(e.Text, faFind)); -// end; -// end; -// // Or find everything -//end else if actiontype = faFindFiles then begin -// fileSearched:=0; -// fileHitted:=0; -// MainForm.FindOutput.BeginFind(cboFindText.Text); -// try - -// // loop through pagecontrol -// if rbOpenFiles.Checked then begin - -// // loop through editors, add results to message control -// for I := 0 to MainForm.EditorList.PageCount - 1 do begin -// e := MainForm.EditorList[i]; -// if Assigned(e) then begin -// inc(fileSearched); -// fCurFile := e.FileName; -// t:=Execute(e.Text, actiontype); -// Inc(findcount, t); -// if t>0 then -// inc(filehitted); -// end; -// end; - -// // loop through project -// end else if rbProjectFiles.Checked then begin -// for I := 0 to MainForm.Project.Units.Count - 1 do begin -// e := MainForm.Project.Units[i].Editor; -// fCurFile := MainForm.Project.Units[i].FileName; - -// // file is already open, use memory -// if Assigned(e) then begin begin -// inc(fileSearched); -// t:=Execute(e.Text, actiontype); -// Inc(findcount, t); -// if t>0 then -// inc(filehitted); -// end; - -// // not open? load from disk -// end else if FileExists(fCurFile) then begin -// // Only finding... -// fTempSynEdit.Lines.LoadFromFile(fCurFile); -// inc(fileSearched); -// t:=Execute(fTempSynEdit, actiontype); -// Inc(findcount, t); -// if t>0 then -// inc(filehitted); -// end; -// end; - -// // Don't loop, only pass single file -// end else if rbCurFile.Checked then begin -// e := MainForm.EditorList.GetEditor; - -// if Assigned(e) then begin - -// fCurFile := e.FileName; - -// inc(fileSearched); -// t:=Execute(e.Text, actiontype); -// Inc(findcount, t); -// if t>0 then -// inc(filehitted); -// end; -// end; -// finally -// MainForm.FindOutput.EndFind(cboFindText.Text,findCount, -// filehitted,filesearched); -// end; -//end else if actiontype = faReplaceFiles then begin -// // loop through pagecontrol -// if rbOpenFiles.Checked then begin - -// // loop through editors, add results to message control -// for I := 0 to MainForm.EditorList.PageCount - 1 do begin -// e := MainForm.EditorList[i]; -// if Assigned(e) then begin -// fCurFile := e.FileName; -// if (ssoPrompt in fSearchOptions) then -// e.Activate; -// Inc(findcount, Execute(e.Text, actiontype)); -// end; -// end; - -// // loop through project -// end else if rbProjectFiles.Checked then begin -// for I := 0 to MainForm.Project.Units.Count - 1 do begin -// e := MainForm.Project.Units[i].Editor; -// fCurFile := MainForm.Project.Units[i].FileName; - -// // file is already open, use memory -// if Assigned(e) then begin -// if (ssoPrompt in fSearchOptions) then -// e.Activate; -// Inc(findcount, Execute(e.Text, actiontype)); - -// // not open? load from disk -// end else if FileExists(fCurFile) then begin -// // we have to open an editor... -// if ssoPrompt in fSearchOptions then begin -// e := MainForm.EditorList.GetEditorFromFileName(fCurFile); -// if Assigned(e) then begin -// e.Activate; - -// Inc(findcount, Execute(e.Text, actiontype)); - -// // Save and close -// e.Save; -// MainForm.Project.CloseUnit(MainForm.Project.Units.Indexof(e)); -// end; -// end else begin -// // Stealth replace -// fTempSynEdit.Lines.LoadFromFile(fCurFile); -// Inc(findcount, Execute(fTempSynEdit, actiontype)); -// fTempSynEdit.Lines.SaveToFile(fCurFile); -// end; -// end; -// end; -// // Don't loop, only pass single file -// end else if rbCurFile.Checked then begin -// e := MainForm.EditorList.GetEditor; - -// if Assigned(e) then begin -// fCurFile := e.FileName; -// Inc(findcount, Execute(e.Text, actiontype)); -// end; -// end; -//end; - -//if actiontype = faFindFiles then begin -// MainForm.MessageControl.ActivePageIndex := 4; // Find Tab -// if findcount > 0 then -// MainForm.FindSheet.Caption := Lang[ID_SHEET_FIND]; -// MainForm.OpenCloseMessageSheet(TRUE); -// self.Close; -//end else if findcount = 0 then begin -// MessageBox( -// Self.Handle, -// PAnsiChar(Format(Lang[ID_MSG_TEXTNOTFOUND], [cboFindText.Text])), -// PAnsiChar(Lang[ID_INFO]), -// MB_ICONINFORMATION or MB_TOPMOST); -// cboFindText.SetFocus; -//end; -//if actiontype = faFind then begin -// self.Close; - //end; } int SearchDialog::execute(Editor *editor, const QString &sSearch, const QString &sReplace, SynSearchMathedProc matchCallback) @@ -403,30 +306,44 @@ int SearchDialog::execute(Editor *editor, const QString &sSearch, const QString return editor->searchReplace(sSearch, sReplace, mSearchOptions, mSearchEngine, matchCallback); +} +std::shared_ptr SearchDialog::batchFindInEditor(Editor *e, const QString &keyword) +{ + //backup + BufferCoord caretBackup = e->caretXY(); + BufferCoord blockBeginBackup = e->blockBegin(); + BufferCoord blockEndBackup = e->blockEnd(); + int toplineBackup = e->topLine(); + int leftCharBackup = e->leftChar(); -// // When using find in files, report each find using OnReplaceText -// if action = faFindFiles then -// editor.OnReplaceText := FindAllAction; + PSearchResultTreeItem parentItem = std::make_shared(); + parentItem->filename = e->filename(); + parentItem->parent = nullptr; + execute(e,keyword,"", + [e,&parentItem](const QString&, + const QString&, int Line, int ch, int wordLen){ + PSearchResultTreeItem item = std::make_shared(); + item->filename = e->filename(); + item->line = Line; + item->start = ch; + item->len = wordLen; + item->parent = parentItem.get(); + item->text = e->lines()->getString(Line-1); + parentItem->results.append(item); + return SynSearchAction::Skip; + }); -// // Swap search engire for ours -// if (ssoRegExp in fSearchOptions) then -// editor.SearchEngine := fRegExpSearchEngine -// else -// editor.SearchEngine := fSearchEngine; -// result := editor.SearchReplace(cboFindText.Text, cboReplaceText.Text, fSearchOptions); - -// // Don't touch editors which we are only scanning -// if action in [faFindFiles] then begin -// // Put backup back into place -// editor.CaretXY := caretbackup; -// editor.BlockBegin := blockbeginbackup; -// editor.BlockEnd := blockendbackup; -// editor.TopLine := toplinebackup; -// end; - -// editor.OnReplaceText := onreplacebackup; -// editor.SearchEngine := enginebackup; + // restore + e->setCaretXY(caretBackup); + e->setTopLine(toplineBackup); + e->setLeftChar(leftCharBackup); + e->setCaretAndSelection( + caretBackup, + blockBeginBackup, + blockEndBackup + ); + return parentItem; } QTabBar *SearchDialog::tabBar() const diff --git a/RedPandaIDE/widgets/searchdialog.h b/RedPandaIDE/widgets/searchdialog.h index 75523308..e03cdb71 100644 --- a/RedPandaIDE/widgets/searchdialog.h +++ b/RedPandaIDE/widgets/searchdialog.h @@ -8,6 +8,7 @@ namespace Ui { class SearchDialog; } +struct SearchResultTreeItem; class QTabBar; class Editor; class SearchDialog : public QDialog @@ -43,6 +44,7 @@ private slots: private: int execute(Editor* editor, const QString& sSearch, const QString& sReplace, SynSearchMathedProc matchCallback = nullptr); + std::shared_ptr batchFindInEditor(Editor* editor,const QString& keyword); private: Ui::SearchDialog *ui; QTabBar *mTabBar; diff --git a/RedPandaIDE/widgets/searchresultview.cpp b/RedPandaIDE/widgets/searchresultview.cpp index 4f76aaad..0b4b0594 100644 --- a/RedPandaIDE/widgets/searchresultview.cpp +++ b/RedPandaIDE/widgets/searchresultview.cpp @@ -1,4 +1,8 @@ #include "searchresultview.h" +#include +#include +#include +#include "mainwindow.h" PSearchResults SearchResultModel::addSearchResults(const QString &keyword, SynSearchOptions options, SearchFileScope scope) { @@ -10,7 +14,7 @@ PSearchResults SearchResultModel::addSearchResults(const QString &keyword, SynSe break; } } - if (index>0) { + if (index>=0) { mSearchResults.removeAt(index); } if (mSearchResults.size()>=MAX_SEARCH_RESULTS) { @@ -21,7 +25,7 @@ PSearchResults SearchResultModel::addSearchResults(const QString &keyword, SynSe results->options = options; results->scope = scope; mSearchResults.push_front(results); - emit modelChanged(); + mCurrentIndex = 0; return results; } @@ -33,6 +37,11 @@ PSearchResults SearchResultModel::results(int index) return mSearchResults[index]; } +void SearchResultModel::notifySearchResultsUpdated() +{ + emit modelChanged(); +} + SearchResultModel::SearchResultModel(QObject* parent): QObject(parent), mCurrentIndex(-1) @@ -75,7 +84,111 @@ SearchResultTreeModel::SearchResultTreeModel(SearchResultModel *model, QObject * QAbstractItemModel(parent), mSearchResultModel(model) { + connect(mSearchResultModel,&SearchResultModel::currentChanged, + this,&SearchResultTreeModel::onResultModelChanged); + connect(mSearchResultModel,&SearchResultModel::modelChanged, + this,&SearchResultTreeModel::onResultModelChanged); +} +QModelIndex SearchResultTreeModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row,column,parent)) + return QModelIndex(); + + PSearchResults results = mSearchResultModel->currentResults(); + if (!results) + return QModelIndex(); + SearchResultTreeItem *parentItem=nullptr; + PSearchResultTreeItem childItem; + if (!parent.isValid()) { + parentItem = nullptr; + childItem = results->results[row]; + } else { + parentItem = static_cast(parent.internalPointer()); + childItem = parentItem->results[row]; + } + if (childItem) + return createIndex(row,column,childItem.get()); + return QModelIndex(); +} + +QModelIndex SearchResultTreeModel::parent(const QModelIndex &child) const +{ + if (!child.isValid()) + return QModelIndex(); + SearchResultTreeItem* item = static_cast(child.internalPointer()); + if (!item) { + return QModelIndex(); + } else { + if (item->parent==nullptr) + return QModelIndex(); + SearchResultTreeItem* parent = item->parent; + int row = -1; + for (int i=0;iresults.count();i++) { + if (parent->results[i].get()==item) { + row = i; + break; + } + } + return createIndex(row,0,parent); + } +} + +int SearchResultTreeModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()){ //root + PSearchResults searchResults = mSearchResultModel->currentResults(); + if (!searchResults) + return 0; + return searchResults->results.count(); + } + SearchResultTreeItem* item = static_cast(parent.internalPointer()); if (!item) + return 0; + return item->results.count(); +} + +int SearchResultTreeModel::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +QVariant SearchResultTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()){ + return QVariant(); + } + SearchResultTreeItem *item = static_cast(index.internalPointer()); + if (!item) + return QVariant(); + if (role == Qt::DisplayRole) { + + PSearchResults results = mSearchResultModel->currentResults(); + + if (!results || !index.isValid() ) { + // This is nothing this function is supposed to handle + return QVariant(); + } + + if (item->parent==nullptr) { //is filename + return item->filename; + } else { + return QString("%1 %2: %3").arg(tr("Line")).arg(item->line) + .arg(item->text); + } + } + return QVariant(); + +} + +SearchResultModel *SearchResultTreeModel::searchResultModel() const +{ + return mSearchResultModel; +} + +void SearchResultTreeModel::onResultModelChanged() +{ + beginResetModel(); + endResetModel(); } SearchResultListModel::SearchResultListModel(SearchResultModel *model, QObject *parent): @@ -118,3 +231,53 @@ void SearchResultListModel::onResultModelChanged() beginResetModel(); endResetModel(); } + +/** + * + * see https://stackoverflow.com/questions/1956542/how-to-make-item-view-render-rich-html-text-in-qt/66412883#66412883 + */ +SearchResultTreeViewDelegate::SearchResultTreeViewDelegate(PSearchResultTreeModel model, QObject *parent): + QStyledItemDelegate(parent), + mModel(model) +{ + +} + +void SearchResultTreeViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optIn, const QModelIndex &index) const +{ + QStyleOptionViewItem option = optIn; + initStyleOption(&option,index); + PSearchResults results = mModel->searchResultModel()->currentResults(); + + if (!results || !index.isValid() ) { + // This is nothing this function is supposed to handle + return; + } + + QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + + // Painting item without text (this takes care of painting e.g. the highlighted for selected + // or hovered over items in an ItemView) + option.text = QString(); + style->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget); + SearchResultTreeItem* item = static_cast(index.internalPointer()); + + QString fullText; + if (item->parent==nullptr) { //is filename + fullText = item->filename; + } else { + fullText = QString("%1 %2: %3").arg(tr("Line")).arg(item->line) + .arg(item->text); + } + + // Figure out where to render the text in order to follow the requested alignment + option.text = fullText; + QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &option); + + QFontMetrics metrics = option.fontMetrics; + int x=textRect.left(); + int y=textRect.top() + metrics.ascent(); + //painter->setClipRect(textRect); + painter->drawText(x,y,fullText); + +} diff --git a/RedPandaIDE/widgets/searchresultview.h b/RedPandaIDE/widgets/searchresultview.h index 02d1cc03..648b2e48 100644 --- a/RedPandaIDE/widgets/searchresultview.h +++ b/RedPandaIDE/widgets/searchresultview.h @@ -3,38 +3,45 @@ #include #include +#include #include "../qsynedit/SearchBase.h" #include "utils.h" #define MAX_SEARCH_RESULTS 20 +struct SearchResultTreeItem; +using PSearchResultTreeItem = std::shared_ptr; +using SearchResultTreeItemList = QList; +using PSearchResultTreeItemList = std::shared_ptr; -struct SearchResult { +struct SearchResultTreeItem { QString filename; int line; int start; int len; + QString text; + SearchResultTreeItem* parent; + SearchResultTreeItemList results; }; -using PSearchResult = std::shared_ptr; -using SearchResultList = QList; -using PSearchResultList = std::shared_ptr; + struct SearchResults{ SynSearchOptions options; QString keyword; SearchFileScope scope; - QMap results; + QList results; }; using PSearchResults = std::shared_ptr; -class SearchResultModel : QObject { +class SearchResultModel : public QObject { Q_OBJECT public: explicit SearchResultModel(QObject* parent=nullptr); PSearchResults addSearchResults(const QString& keyword,SynSearchOptions options, SearchFileScope scope); PSearchResults results(int index); + void notifySearchResultsUpdated(); int currentIndex() const; int resultsCount() const; PSearchResults currentResults(); @@ -62,6 +69,8 @@ private: SearchResultModel *mSearchResultModel; }; +using PSearchResultListModel = std::shared_ptr; + class SearchResultTreeModel : public QAbstractItemModel { Q_OBJECT // QAbstractItemModel interface @@ -72,15 +81,26 @@ public: int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; + SearchResultModel *searchResultModel() const; +public slots: + void onResultModelChanged(); private: SearchResultModel *mSearchResultModel; }; -class SearchResultView : public QTreeView -{ - Q_OBJECT +using PSearchResultTreeModel = std::shared_ptr; + +class SearchResultTreeViewDelegate: public QStyledItemDelegate{ +Q_OBJECT + // QAbstractItemDelegate interface public: - explicit SearchResultView(QWidget* parent=nullptr); + explicit SearchResultTreeViewDelegate(PSearchResultTreeModel model, + QObject* parent=nullptr); + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +private: + PSearchResultTreeModel mModel; }; +using PSearchResultTreeViewDelegate = std::shared_ptr; + #endif // SEARCHRESULTVIEW_H