From b0855607435a69b036000d0efb39eaf4dc0a3d12 Mon Sep 17 00:00:00 2001 From: Roy Qu Date: Fri, 11 Nov 2022 21:20:57 +0800 Subject: [PATCH] - fix: Escape suquences like \uxxxx and \Uxxxxxxxx in strings are not correctly highlighted. - enhancement: Search / replace dialogs redesigned. - fix: inline functions are not correctly parsed; - fix: &operator= functions are not correctly parsed; - fix: Code Formatter's "add indent to continueous lines" option is not correctly saved. --- NEWS.md | 8 +- RedPandaIDE/RedPandaIDE.pro | 6 + RedPandaIDE/mainwindow.cpp | 53 +- RedPandaIDE/mainwindow.h | 8 + RedPandaIDE/parser/cppparser.cpp | 35 +- RedPandaIDE/parser/cpptokenizer.cpp | 1 + RedPandaIDE/settings.cpp | 2 +- .../settingsdialog/formattergeneralwidget.ui | 10 +- RedPandaIDE/translations/RedPandaIDE_pt_BR.ts | 172 ++- RedPandaIDE/translations/RedPandaIDE_zh_CN.ts | 1129 ++++++++++------- RedPandaIDE/translations/RedPandaIDE_zh_TW.ts | 131 +- RedPandaIDE/utils.cpp | 13 + RedPandaIDE/utils.h | 4 + RedPandaIDE/widgets/replacedialog.cpp | 212 ++++ RedPandaIDE/widgets/replacedialog.h | 42 + RedPandaIDE/widgets/replacedialog.ui | 386 ++++++ RedPandaIDE/widgets/searchdialog.cpp | 500 ++------ RedPandaIDE/widgets/searchdialog.h | 65 +- RedPandaIDE/widgets/searchdialog.ui | 486 +++---- RedPandaIDE/widgets/searchinfiledialog.cpp | 295 +++++ RedPandaIDE/widgets/searchinfiledialog.h | 68 + RedPandaIDE/widgets/searchinfiledialog.ui | 278 ++++ libs/qsynedit/qsynedit/SynEdit.cpp | 21 +- libs/qsynedit/qsynedit/SynEdit.h | 1 + libs/qsynedit/qsynedit/TextPainter.cpp | 108 +- 25 files changed, 2700 insertions(+), 1334 deletions(-) create mode 100644 RedPandaIDE/widgets/replacedialog.cpp create mode 100644 RedPandaIDE/widgets/replacedialog.h create mode 100644 RedPandaIDE/widgets/replacedialog.ui create mode 100644 RedPandaIDE/widgets/searchinfiledialog.cpp create mode 100644 RedPandaIDE/widgets/searchinfiledialog.h create mode 100644 RedPandaIDE/widgets/searchinfiledialog.ui diff --git a/NEWS.md b/NEWS.md index 794dfa38..2aec27e8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -20,6 +20,11 @@ Red Panda C++ Version 2.4 - fix: modifitions in the project options dialogs's dll host page is not correctly saved. - enhancement: In the project options dialog, autoset the default folder in the openning dialog when choosing file/directory paths. - fix: Escape suquences like \uxxxx and \Uxxxxxxxx in strings are not correctly highlighted. + - enhancement: Search / replace dialogs redesigned. + - fix: inline functions are not correctly parsed; + - fix: &operator= functions are not correctly parsed; + - fix: Code Formatter's "add indent to continueous lines" option is not correctly saved. + Red Panda C++ Version 2.3 @@ -46,7 +51,8 @@ Red Panda C++ Version 2.2 - fix: Set max undo memory usage to 0 don't really remove the limit for undo - fix: Set max undo times to 0 don't really remove the limit for undo - fix: Keep the newest undo info regardless of undo memory usage - + - fix: inline functions not correctly handled by parser + - fix: &operator= not correctly handled by parser Red Panda C++ Version 2.1 diff --git a/RedPandaIDE/RedPandaIDE.pro b/RedPandaIDE/RedPandaIDE.pro index b582166f..a8424d87 100644 --- a/RedPandaIDE/RedPandaIDE.pro +++ b/RedPandaIDE/RedPandaIDE.pro @@ -199,7 +199,9 @@ SOURCES += \ widgets/projectalreadyopendialog.cpp \ widgets/qconsole.cpp \ widgets/qpatchedcombobox.cpp \ + widgets/replacedialog.cpp \ widgets/searchdialog.cpp \ + widgets/searchinfiledialog.cpp \ widgets/searchresultview.cpp \ widgets/shortcutinputedit.cpp \ widgets/shrinkabletabwidget.cpp \ @@ -331,7 +333,9 @@ HEADERS += \ widgets/projectalreadyopendialog.h \ widgets/qconsole.h \ widgets/qpatchedcombobox.h \ + widgets/replacedialog.h \ widgets/searchdialog.h \ + widgets/searchinfiledialog.h \ widgets/searchresultview.h \ widgets/shortcutinputedit.h \ widgets/shrinkabletabwidget.h \ @@ -395,7 +399,9 @@ FORMS += \ widgets/newtemplatedialog.ui \ widgets/ojproblempropertywidget.ui \ widgets/projectalreadyopendialog.ui \ + widgets/replacedialog.ui \ widgets/searchdialog.ui \ + widgets/searchinfiledialog.ui \ widgets/signalmessagedialog.ui win32: { diff --git a/RedPandaIDE/mainwindow.cpp b/RedPandaIDE/mainwindow.cpp index 59a350a6..8e3d2b9b 100644 --- a/RedPandaIDE/mainwindow.cpp +++ b/RedPandaIDE/mainwindow.cpp @@ -50,6 +50,9 @@ #include "widgets/newtemplatedialog.h" #include "visithistorymanager.h" #include "widgets/projectalreadyopendialog.h" +#include "widgets/searchdialog.h" +#include "widgets/replacedialog.h" + #include #include @@ -85,7 +88,7 @@ #include "cpprefacter.h" #include "widgets/newprojectunitdialog.h" -#include "widgets/searchdialog.h" +#include "widgets/searchinfiledialog.h" #ifdef Q_OS_WIN #include @@ -107,7 +110,9 @@ MainWindow* pMainWindow; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), + mSearchInFilesDialog(nullptr), mSearchDialog(nullptr), + mReplaceDialog(nullptr), mQuitting(false), mClosingProject(false), mCheckSyntaxInBack(false), @@ -965,6 +970,18 @@ void MainWindow::onFileSaved(const QString &path, bool inProject) //updateForEncodingInfo(); } +void MainWindow::prepareSearchDialog() +{ + if (!mSearchDialog) + mSearchDialog = new SearchDialog(this); +} + +void MainWindow::prepareReplaceDialog() +{ + if (!mReplaceDialog) + mReplaceDialog = new ReplaceDialog(this); +} + void MainWindow::updateAppTitle() { Editor *e = mEditorList->getEditor(); @@ -4783,9 +4800,9 @@ const std::shared_ptr &MainWindow::completionPopup() const return mCompletionPopup; } -SearchDialog *MainWindow::searchDialog() const +SearchInFileDialog *MainWindow::searchInFilesDialog() const { - return mSearchDialog; + return mSearchInFilesDialog; } SearchResultModel *MainWindow::searchResultModel() @@ -5771,24 +5788,22 @@ void MainWindow::on_actionFind_triggered() Editor *e = mEditorList->getEditor(); if (!e) return; - if (mSearchDialog==nullptr) { - mSearchDialog = new SearchDialog(this); - } QString s = e->wordAtCursor(); + prepareSearchDialog(); mSearchDialog->find(s); } void MainWindow::on_actionFind_in_files_triggered() { - if (mSearchDialog==nullptr) { - mSearchDialog = new SearchDialog(this); + if (mSearchInFilesDialog==nullptr) { + mSearchInFilesDialog = new SearchInFileDialog(this); } Editor *e = mEditorList->getEditor(); if (e) { QString s = e->wordAtCursor(); - mSearchDialog->findInFiles(s); + mSearchInFilesDialog->findInFiles(s); } else { - mSearchDialog->findInFiles(""); + mSearchInFilesDialog->findInFiles(""); } } @@ -5797,11 +5812,10 @@ void MainWindow::on_actionReplace_triggered() Editor *e = mEditorList->getEditor(); if (!e) return; - if (mSearchDialog==nullptr) { - mSearchDialog = new SearchDialog(this); - } + QString s = e->wordAtCursor(); - mSearchDialog->replace(s,s); + prepareReplaceDialog(); + mReplaceDialog->replace(s); } void MainWindow::on_actionFind_Next_triggered() @@ -5841,14 +5855,14 @@ void MainWindow::on_cbSearchHistory_currentIndexChanged(int index) void MainWindow::on_btnSearchAgain_clicked() { - if (mSearchDialog==nullptr) { - mSearchDialog = new SearchDialog(this); + if (mSearchInFilesDialog==nullptr) { + mSearchInFilesDialog = new SearchInFileDialog(this); } PSearchResults results=mSearchResultModel.currentResults(); if (!results) return; if (results->searchType == SearchType::Search){ - mSearchDialog->findInFiles(results->keyword, + mSearchInFilesDialog->findInFiles(results->keyword, results->scope, results->options); } else if (results->searchType == SearchType::FindOccurences) { @@ -8785,3 +8799,8 @@ void MainWindow::on_actionSwitchHeaderSource_triggered() } } +SearchDialog *MainWindow::searchDialog() const +{ + return mSearchDialog; +} + diff --git a/RedPandaIDE/mainwindow.h b/RedPandaIDE/mainwindow.h index 382431d6..3beb648a 100644 --- a/RedPandaIDE/mainwindow.h +++ b/RedPandaIDE/mainwindow.h @@ -65,7 +65,9 @@ class Editor; class Debugger; class CPUDialog; class QPlainTextEdit; +class SearchInFileDialog; class SearchDialog; +class ReplaceDialog; class Project; struct ProjectModelNode; class ProjectUnit; @@ -173,6 +175,8 @@ public: EditorList *editorList() const; + SearchInFileDialog *searchInFilesDialog() const; + SearchDialog *searchDialog() const; SearchResultModel* searchResultModel(); @@ -252,6 +256,8 @@ public slots: void onFileSaved(const QString& path, bool inProject); private: + void prepareSearchDialog(); + void prepareReplaceDialog(); void prepareProjectForCompile(); void closeProject(bool refreshEditor); void updateProjectView(); @@ -756,7 +762,9 @@ private: std::shared_ptr mCompilerManager; std::shared_ptr mDebugger; CPUDialog *mCPUDialog; + SearchInFileDialog *mSearchInFilesDialog; SearchDialog *mSearchDialog; + ReplaceDialog *mReplaceDialog; bool mQuitting; bool mClosingProject; QElapsedTimer mParserTimer; diff --git a/RedPandaIDE/parser/cppparser.cpp b/RedPandaIDE/parser/cppparser.cpp index 18c60ef4..c0caf747 100644 --- a/RedPandaIDE/parser/cppparser.cpp +++ b/RedPandaIDE/parser/cppparser.cpp @@ -1456,8 +1456,10 @@ void CppParser::addSoloScopeLevel(PStatement& statement, int line, bool shouldRe else mClassScope = StatementClassScope::Public; // structs are public by default mCurrentClassScope.append(mClassScope); - //if (mCurrentClassScope.count()==2) +#ifdef QT_DEBUG + //if (mCurrentClassScope.count()==1) // qDebug()<<"++add scope"<matchIndex-1), - indexAfterParentheis,false,false); + currentText, + mIndex,false,false); return; } //check for constructor like Foo::Foo() @@ -1996,7 +2000,7 @@ void CppParser::checkAndHandleMethodOrVar(KeywordType keywordType) ||mTokenizer[mIndex + 1]->text == ';' ||mTokenizer[mIndex + 1]->text == ':' ||mTokenizer[mIndex + 1]->text == '{' - ||mTokenizer[mIndex + 1]->text == '=') { + || mTokenizer[mIndex + 1]->text == '=') { handleVar(sType,isExtern,isStatic); return; } else { @@ -3027,6 +3031,11 @@ void CppParser::handleScope(KeywordType keywordType) mIndex+=2; // the scope is followed by a ':' } + +#ifdef QT_DEBUG +static int lastIndex=-1; +#endif + bool CppParser::handleStatement() { QString funcType,funcName; @@ -3034,6 +3043,11 @@ bool CppParser::handleStatement() int idx2=getCurrentBlockBeginSkip(); int idx3=getCurrentInlineNamespaceEndSkip(); KeywordType keywordType; +#ifdef QT_DEBUG +// qDebug()<=lastIndex); + lastIndex=mIndex; +#endif if (mIndex >= idx2) { //skip (previous handled) block begin @@ -3080,7 +3094,7 @@ bool CppParser::handleStatement() // } else if (checkForLambda()) { // is lambda // handleLambda(); } else if (mTokenizer[mIndex]->text=='(') { - if (mTokenizer[mIndex]->text=="operator") { + if (mIndex+1text=="operator") { // things like (operator int) mIndex++; //just skip '(' } else @@ -3131,11 +3145,13 @@ bool CppParser::handleStatement() handleUsing(); } else if (checkForStructs(keywordType)) { handleStructs(false); - } else { + } else if (keywordType == KeywordType::Inline) { + mIndex++; + }else { // it should be method/constructor/var checkAndHandleMethodOrVar(keywordType); } - Q_ASSERT(mIndex<999999); + //Q_ASSERT(mIndex<999999); // while (mTokenizer.lambdasCount()>0 && mTokenizer.indexOfFirstLambda() - 0 + 1 @@ -337,7 +337,11 @@ - + + + 40 + + @@ -883,7 +887,7 @@ - + diff --git a/RedPandaIDE/translations/RedPandaIDE_pt_BR.ts b/RedPandaIDE/translations/RedPandaIDE_pt_BR.ts index 2316ae57..cdb1579c 100644 --- a/RedPandaIDE/translations/RedPandaIDE_pt_BR.ts +++ b/RedPandaIDE/translations/RedPandaIDE_pt_BR.ts @@ -6171,6 +6171,105 @@ Valor + + ReplaceDialog + + Replace + Substituir + + + Text to Find: + Texto a procurar: + + + Replace with: + Substituir por: + + + Scope: + Escopo: + + + Global + Global + + + Selection + Seleção + + + Origin: + Origem: + + + From cursor + A partir do cursor + + + Entire scope + Escopo inteiro + + + Options: + Opções: + + + Whole words only + Apenas palavras inteiras + + + Wrap Around + Wrap Around + + + Regular Expression + Expressão regular + + + Prompt on replace + Perguntar ao substituir + + + Case Sensitive + Sensibilidade a maiúsculas/minúsculas + + + Find Previous + Procurar anterior + + + Find Next + Procurar outro + + + Replace All + + + + Close + + + + Beginning of file has been reached. + + + + Do you want to continue from file's end? + + + + End of file has been reached. + Encontrado fim de arquivo. + + + Do you want to continue from file's beginning? + Quer continuar a partir do início do arquivo? + + + Continue Search + Continuar a procura + + SearchDialog @@ -6271,15 +6370,15 @@ Replace - Substituir + Substituir Find in files - Procurar em arquivos + Procurar em arquivos Replace in files - Substituir em arquivos + Substituir em arquivos Continue Search @@ -6295,11 +6394,74 @@ Replace this occurrence of ''%1''? - Substituir essa ocorrência de ''%1''? + Substituir essa ocorrência de ''%1''? Continue Replace - Continuar a substituir + Continuar a substituir + + + Close after search + + + + Find Previous + Procurar anterior + + + Find Next + Procurar outro + + + Close + + + + Beginning of file has been reached. + + + + Do you want to continue from file's end? + + + + + SearchInFileDialog + + Find + Procurar + + + Replace + Substituir + + + Find in files + Procurar em arquivos + + + Replace in files + Substituir em arquivos + + + Continue Search + Continuar a procura + + + End of file has been reached. + Encontrado fim de arquivo. + + + Do you want to continue from file's beginning? + Quer continuar a partir do início do arquivo? + + + Replace this occurrence of ''%1''? + Substituir essa ocorrência de ''%1''? + + + Continue Replace + Continuar a substituir diff --git a/RedPandaIDE/translations/RedPandaIDE_zh_CN.ts b/RedPandaIDE/translations/RedPandaIDE_zh_CN.ts index 1a37f86d..fd61e14b 100644 --- a/RedPandaIDE/translations/RedPandaIDE_zh_CN.ts +++ b/RedPandaIDE/translations/RedPandaIDE_zh_CN.ts @@ -1325,10 +1325,10 @@ Are you really want to continue? - - - - + + + + Error 错误 @@ -1347,34 +1347,34 @@ Are you really want to continue? 文件%1已经被打开! - + The text to be copied exceeds count limit! 要复制的内容超过了行数限制! - + The text to be copied exceeds character limit! 要复制的内容超过了字符数限制! - + The text to be cut exceeds count limit! 要剪切的内容超过了行数限制! - + The text to be cut exceeds character limit! 要剪切的内容超过了字符数限制! - + Print Document 打印文档 - - - + + + Ctrl+click for more info Ctrl+单击以获取更多信息 @@ -1383,27 +1383,27 @@ Are you really want to continue? 未找到符号'%1'! - + astyle not found 找不到astyle程序 - + Can't find astyle in "%1". 找不到astyle程序"%1". - + Break point condition 断点条件 - + Enter the condition of the breakpoint: 输入当前断点的生效条件: - + Readonly 只读 @@ -3918,18 +3918,18 @@ Are you really want to continue? MainWindow - + Red Panda C++ 小熊猫C++ - - - - - + + + + + Issues 编译器 @@ -4003,7 +4003,7 @@ Are you really want to continue? - + Debug Console 调试主控台 @@ -4079,7 +4079,7 @@ Are you really want to continue? 工具栏2 - + New 新建 @@ -4137,8 +4137,8 @@ Are you really want to continue? - - + + Compile 编译 @@ -4216,9 +4216,9 @@ Are you really want to continue? - - - + + + Copy 复制 @@ -4229,7 +4229,7 @@ Are you really want to continue? - + Paste 粘贴 @@ -4240,8 +4240,8 @@ Are you really want to continue? - - + + Select All 选择全部 @@ -4367,7 +4367,7 @@ Are you really want to continue? - + New Problem Set 新建试题集 @@ -4386,14 +4386,14 @@ Are you really want to continue? - + Save Problem Set 保存试题集 - + Load Problem Set 载入试题集 @@ -4454,7 +4454,7 @@ Are you really want to continue? - + Run All Cases Run Current Case 运行所有案例 @@ -4746,7 +4746,7 @@ Are you really want to continue? - + Clear all breakpoints 删除所有断点 @@ -4942,7 +4942,7 @@ Are you really want to continue? - + New File 新建文件 @@ -4983,7 +4983,7 @@ Are you really want to continue? - + Rename Symbol 重命名符号 @@ -5004,13 +5004,13 @@ Are you really want to continue? - + Export As RTF 导出为RTF - + Export As HTML 导出为HTML @@ -5279,42 +5279,42 @@ Are you really want to continue? 运行参数... - + File Encoding 文件编码 - + Recent Files 文件历史 - - - - - - + + + + + + Debugging 正在调试 - - - - - - + + + + + + Running 正在运行 - - - - - - + + + + + + Compiling 正在编译 @@ -5323,23 +5323,23 @@ Are you really want to continue? 行:%1 列:%2 已选择:%3 总行数:%4 总长度:%5 - + Line:%1 Col:%2 Selected:%3 Lines:%4 Length:%5 Line: %1 Col: %2 Selected: %3 Lines: %4 Length: %5 行: %1 列: %2 已选择 :%3 总行数: %4 总长度: %5 - + Read Only 只读 - + Insert 插入 - + Overwrite 覆写 @@ -5352,133 +5352,133 @@ Are you really want to continue? 你确定要关闭'%1'吗? - - + + Confirm 确认 - - - + + + Source file is not compiled. 源文件尚未编译。 - - + + Compile now? 现在编译? - - + + Source file is more recent than executable. 源文件比可执行程序新。 - + Recompile now? 重新编译? - - - - + + + + Wrong Compiler Settings 错误的编译器设置 - - - - + + + + Compiler is set not to generate executable. 编译器被设置为不生成可执行文件。 - - + + We need the executabe to run problem case. 我们需要可执行文件来运行试题案例。 - + No compiler set 无编译器设置 - + No compiler set is configured. 没有配置编译器设置。 - + Can't start debugging. 无法启动调试器 - - + + Enable debugging 启用调试参数 - - + + You have not enabled debugging info (-g3) and/or stripped it from the executable (-s) in Compiler Options.<BR /><BR />Do you want to correct this now? 当前编译设置中未启用调试选项(-g3),或启用了信息剥除选项(-s)<br /><br/>是否纠正这一问题? - + Project not built 项目尚未构建 - + Project hasn't been built. Build it now? 项目尚未构建。是否构建? - + Host applcation missing 宿主程序不存在 - + DLL project needs a host application to run. 动态链接库(DLL)需要一个宿主程序来运行。 - + But it's missing. 但它不存在。 - + Host application not exists 宿主程序不存在 - + Host application file '%1' doesn't exist. 宿主程序'%1'不存在。 - - + + Please correct this before start debugging 请在调试前改正设置。 - + Recompile? 重新编译? - - + + Save last open info error 保存上次打开信息失败 @@ -5487,60 +5487,60 @@ Are you really want to continue? 无法删除旧上次打开信息文件'%1' - + Can't save last open info file '%1' 无法保存上次打开信息文件'%1' - - + + Load last open info error 载入上次打开信息失败 - - + + Can't load last open info file '%1' 无法载入上次打开信息文件'%1' - + Open Source File 打开源代码文件 - - + + Batch Set Cases 批量设置案例 - + Show detail debug logs 显示详细调试器日志 - + Copy all 全部复制 - + Go to Line 跳转到行 - + Line - + Template Exists 模板已存在 - + Template %1 already exists. Do you want to overwrite? 模板%1已存在。是否覆盖? @@ -5548,25 +5548,25 @@ Are you really want to continue? - - - + + + Clear 清除 - + Export 导出 - + Insert Snippet 插入代码段 - - + + Problem Set %1 试题集%1 @@ -5587,68 +5587,68 @@ Are you really want to continue? 或者选择使用其他的网络端口。 - - + + Rebuild Project 重新构建项目 - - + + Project has been modified, do you want to rebuild it? 项目已经被修改过,是否需要重新构建? - + Auto Save Error 自动保存出错 - + Auto save "%1" to "%2" failed:%3 自动保存"%1"到"%2"失败:%3 - + Properties... 试题属性... - + Set Problem Set Name 设置试题集名称 - + Problem Set Name: 试题集名称: - + Remove 删除 - + Remove All Bookmarks 删除全部书签 - + Modify Description 修改描述 - - - + + + Bookmark Description 书签描述 - - - + + + Description: 描述: @@ -5657,194 +5657,194 @@ Are you really want to continue? 在调试主控台中显示调试器输出 - + Remove this search 清除这次搜索 - + Clear all searches 删除所有搜索 - + Breakpoint condition... 断点条件... - + Break point condition 断点条件 - + Enter the condition of the breakpoint: 输入当前断点的生效条件: - + Remove All Breakpoints Remove all breakpoints 删除所有断点 - + Remove Breakpoint 删除当前断点 - + Rename File 重命名文件 - - + + Add Folder 添加文件夹 - - + + New folder 新文件夹 - + Folder name: 文件夹: - + Rename Folder 重命名 - + Can't open last open information file '%1' for write! 无法写入配置文件'%1'。 - + Run Current Case 运行当前案例 - + Remove Folder 删除文件夹 - + Switch to normal view 切换为普通视图 - + Switch to custom view 切换为自定义视图 - + Sort By Type 按类型排序 - + Sort alphabetically 按名称排序 - + Show inherited members 显示继承的成员 - + Goto declaration 跳转到声明处 - + Goto definition 跳转到定义处 - + In current file 仅当前文件 - + In current project 整个项目 - - + + New Folder 新建文件夹 - + Rename 重命名 - - - - + + + + Delete 删除 - + Open in Editor 在编辑器中打开 - + Open in External Program 使用外部程序打开 - + Open in Terminal 在终端中打开 - + Open in Windows Explorer 在Windows浏览器中打开 - + Character sets 字符集 - + Convert to %1 转换为%1编码 - + %1 files autosaved 已自动保存%1个文件 - + Set answer to... 设置答案源代码... - + select other file... 选择其他文件... - + Select Answer Source File 选择答案源代码文件 @@ -5854,7 +5854,7 @@ Are you really want to continue? C/C++源代码文件 (*.c *.cpp *.cc *.cxx) - + New Folder %1 新建文件夹%1 @@ -5867,67 +5867,67 @@ Are you really want to continue? 无标题%1 - + Do you really want to delete %1? 你真的要删除%1吗? - + Do you really want to delete %1 files? 你真的要删除%1个文件吗? - + Save project 保存项目 - + The project '%1' has modifications. 项目'%1'有改动。 - - + + Do you want to save it? 需要保存吗? - - + + File Changed 文件已发生变化 - + New Project File? 新建项目文件? - + Do you want to add the new file to the project? 您是否要将新建的文件加入项目? - - - + + + Save Error 保存失败 - + Change Project Compiler Set 改变项目编译器配置集 - + Change the project's compiler set will lose all custom compiler set options. 改变项目的编译器配置集会导致所有的自定义编译器选项被重置。 - - + + Do you really want to do that? 你真的想要那么做吗? @@ -5936,12 +5936,12 @@ Are you really want to continue? 批量设置案例 - + Choose input files 选择输入数据文件 - + Input data files (*.in) 输入数据文件 (*.in) @@ -5950,104 +5950,104 @@ Are you really want to continue? 无标题%1 - + Modify Watch 修改监视表达式 - + Watch Expression 监视表达式 - + Do you really want to clear all breakpoints in this file? 您真的要清除该文件的所有断点吗? - + New project 新建项目 - + Close %1 and start new project? 关闭'%1'以打开新项目? - + Folder not exist 文件夹不存在 - + Folder '%1' doesn't exist. Create it now? 文件夹'%1'不存在。是否创建? - + Can't create folder 无法创建文件夹 - + Failed to create folder '%1'. 创建文件夹'%1'失败。 - + Save new project as - + Folder %1 is not empty. 文件夹%1不是空的。 - + Do you really want to delete it? 你真的要删除它吗? - + Change working folder 改变工作文件夹 - + File '%1' is not in the current working folder. File '%1' is not in the current working folder 文件'%1'不在当前工作文件夹中。 - + Do you want to change working folder to '%1'? 是否将工作文件夹改设为'%1'? - + Can't Commit 无法提交 - + Git needs user info to commit. Git需要用信息进行提交。 - + Choose Input Data File 选择输入数据文件 - - + + All files (*.*) 所有文件 (*.*) - + Choose Expected Output Data File Choose Expected Input Data File 选择期望输出文件 @@ -6059,59 +6059,59 @@ Are you really want to continue? - + Choose Working Folder 选择工作文件夹 - - + + Header Exists 头文件已存在 - - + + Header file "%1" already exists! 头文件"%1"已存在! - + Source Exists 源文件已存在! - + Source file "%1" already exists! 源文件"%1"已存在! - + Can't commit! 无法提交! - + The following files are in conflicting: 下列文件处于冲突状态,请解决后重新添加和提交: - + Commit Message 提交信息 - + Commit Message: 提交信息: - + Commit Failed 提交失败 - + Commit message shouldn't be empty! 提交信息不能为空! @@ -6120,22 +6120,22 @@ Are you really want to continue? 小熊猫Dev-C++项目文件 (*.dev) - + New project fail 新建项目失败 - + Can't assign project template 无法使用模板创建项目 - + Remove file 删除文件 - + Remove the file from disk? 同时从硬盘上删除文件? @@ -6144,228 +6144,228 @@ Are you really want to continue? 无标题 - + New Project File Name 新的项目文件名 - + File Name: 文件名: - + File Already Exists! 文件已存在! - + File '%1' already exists! 文件'%1'已经存在! - + Add to project 添加到项目 - + C/C++ Source Files (*.c *.cpp *.cc *.cxx) C/C++源代码文件 (*.c *.cpp *.cc *.cxx) - + This operation will remove all cases for the current problem. 本操作会删除此试题的所有案例。 - + Red Panda C++ project file (*.dev) 小熊猫C++项目文件(*.dev) - + Rename Error 重命名出错 - + Symbol '%1' is defined in system header. 符号'%1'在系统头文件中定义,无法修改。 - + New Name 新名称 - - + + Replace Error 替换出错 - + Can't open file '%1' for replace! 无法打开文件'%1'进行替换! - + Contents has changed since last search! 内容和上次查找时不一致。 - + Rich Text Format Files (*.rtf) RTF格式文件 (*.rtf) - + HTML Files (*.html) HTML文件 (*.html) - + The current problem set is not empty. 当前的试题集不是空的。 - + Problem %1 试题%1 - - + + Problem Set Files (*.pbs) 试题集文件 (*.pbs) - + Load Error 载入失败 - - + + Problem Case %1 试题案例%1 - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + Error 错误 - + Recent Projects 项目历史 - + Load Theme Error 载入主题失败 - - + + Clear History 清除历史 - - + + Version Control 版本控制 - + File '%1' was changed. 磁盘文件'%1'已被修改。 - + Reload its content from disk? 是否重新读取它的内容? - + File '%1' was removed. 磁盘文件'%1'已被删除。 - + Keep it open? 是否保持它在小熊猫C++中打开的编辑窗口? - + Open 打开 - + Compile Failed 编译失败 - + Run Failed 运行失败 - - - - + + + + Confirm Convertion 确认转换 - - - - + + + + The editing file will be saved using %1 encoding. <br />This operation can't be reverted. <br />Are you sure to continue? 当前编辑器中的文件将会使用%1编码保存。<br />这项操作无法被撤回。<br />你确定要继续吗? - + New Watch Expression 新监视表达式 - + Enter Watch Expression (it is recommended to use 'this->' for class members): 输入监视表达式 - + Parsing file %1 of %2: "%3" (%1/%2)正在解析文件"%3" - - + + Done parsing %1 files in %2 seconds 完成%1个文件的解析,用时%2秒 - + (%1 files per second) (每秒%1个文件) @@ -7406,7 +7406,7 @@ Are you really want to continue? precompiled header files (*.pch) - 预定义头文件 (*.pch) + 预定义头文件 (*.pch) header files (*.h) @@ -7930,22 +7930,22 @@ Are you really want to continue? 下标"%1"越界 - + bytes 字节 - + KB KB - + MB MB - + GB GB @@ -8291,176 +8291,375 @@ Are you really want to continue? - SearchDialog + ReplaceDialog - - Dialog - 对话框 - - - - Text to Find: - 要查找的关键字 - - - - Replace with: - 替换为: - - - - Options: - 选项: - - - - Case Sensitive - 区分大小写 - - - - Whole words only - 整个单词 - - - - Wrap Around - 循环查找 - - - - Regular Expression - 正则表达式 - - - - Prompt on replace - 替换时提示 - - - - Scope: - 范围: - - - - Global - 全局 - - - - Selection - 选中文字 - - - - Origin: - 起点: - - - - From cursor - 从光标处 - - - - Entire scope - 整个范围 - - - - Direction: - 方向 - - - - Forward - 向后 - - - - Backward - 向前 - - - - Where: - 在哪些文件中查找: - - - - Current File - 当前文件 - - - - Files In Project - 项目中的文件 - - - - Open Files - 已打开的文件 - - - - - - Find - 查找 - - - - Cancel - 取消 - - - - Find in files - 在文件中查找 - - - - - + + Replace 替换 - - Replace in files - 在文件中替换 + + Text to Find: + 查找字符串: - + + Replace with: + 替换为: + + + + Scope: + 范围: + + + + Global + 全局 + + + + Selection + 选中内容 + + + + Origin: + 起点: + + + + From cursor + 从光标处 + + + + Entire scope + 整个范围 + + + + Options: + 选项: + + + + Whole words only + 整个单词 + + + + Wrap Around + 循环查找 + + + + Regular Expression + 正则表达式 + + + + Prompt on replace + 替换时提示 + + + + Case Sensitive + 区分大小写 + + + + Find Previous + 查找前一个 + + + + Find Next + 查找下一个 + + + + Replace All + 全部替换 + + + + Close + 关闭 + + + + Beginning of file has been reached. + 已到达文件开头。 + + + + Do you want to continue from file's end? + 是否需要从文件末尾开始继续查找? + + + + + End of file has been reached. + 已到达文件结尾。 + + + + + Do you want to continue from file's beginning? + 是否从文件开头继续? + + + + + Continue Search + 继续查找 + + + + SearchDialog + + + Dialog + 对话框 + + + + + Text to Find: + 要查找的关键字 + + + + Replace with: + 替换为: + + + + + Options: + 选项: + + + + + Case Sensitive + 区分大小写 + + + + + Whole words only + 整个单词 + + + + + Wrap Around + 循环查找 + + + + + Regular Expression + 正则表达式 + + + + + Prompt on replace + 替换时提示 + + + + + Scope: + 范围: + + + + + Global + 全局 + + + + + Selection + 选中文字 + + + + + Origin: + 起点: + + + + + From cursor + 从光标处 + + + + + Entire scope + 整个范围 + + + + Close after search + 找到后关闭对话框 + + + + Find Previous + 查找前一个 + + + + Find Next + 查找下一个 + + + + Close + 关闭 + + + + Direction: + 方向 + + + + Forward + 向后 + + + + Backward + 向前 + + + + Where: + 在哪些文件中查找: + + + + Current File + 当前文件 + + + + Files In Project + 项目中的文件 + + + + Open Files + 已打开的文件 + + + + + Find + 查找 + + + + Cancel + 取消 + + + Find in files + 在文件中查找 + + + Replace + 替换 + + + Replace in files + 在文件中替换 + + + Continue Search Search Around 继续查找 - - + End of file has been reached. End of file has been reached. 已到达文件结尾。 - - + + Beginning of file has been reached. + 已到达文件开头 + + + + Do you want to continue from file's end? + 是否从文件结尾继续? + + + Do you want to continue from file's beginning? Do you want to start from beginning? 是否从文件开头继续? - Replace this occurrence of ''%1''? - 替换这里的"%1"? + 替换这里的"%1"? - Continue Replace Replace Around - 继续替换 + 继续替换 + + + + SearchInFileDialog + + Find + 查找 + + + Replace + 替换 + + + Find in files + 在文件中查找 + + + Replace in files + 在文件中替换 + + + Continue Search + 继续查找 + + + End of file has been reached. + 已到达文件结尾。 + + + Do you want to continue from file's beginning? + 是否从文件开头继续? + + + Replace this occurrence of ''%1''? + 替换这里的"%1"? + + + Continue Replace + 继续替换 @@ -8705,14 +8904,14 @@ Are you really want to continue? 性能 - + Compiler Set 编译器配置集 - + Compiler @@ -8724,7 +8923,7 @@ Are you really want to continue? 自动链接 - + @@ -8800,15 +8999,15 @@ Are you really want to continue? 杂项 - - + + Program Runner 程序运行 - + Problem Set 试题集 diff --git a/RedPandaIDE/translations/RedPandaIDE_zh_TW.ts b/RedPandaIDE/translations/RedPandaIDE_zh_TW.ts index 3def09cc..51e7009d 100644 --- a/RedPandaIDE/translations/RedPandaIDE_zh_TW.ts +++ b/RedPandaIDE/translations/RedPandaIDE_zh_TW.ts @@ -5908,6 +5908,105 @@ + + ReplaceDialog + + Replace + + + + Text to Find: + + + + Replace with: + + + + Scope: + + + + Global + + + + Selection + + + + Origin: + + + + From cursor + + + + Entire scope + + + + Options: + + + + Whole words only + + + + Wrap Around + + + + Regular Expression + + + + Prompt on replace + + + + Case Sensitive + + + + Find Previous + + + + Find Next + + + + Replace All + + + + Close + + + + Beginning of file has been reached. + + + + Do you want to continue from file's end? + + + + End of file has been reached. + + + + Do you want to continue from file's beginning? + + + + Continue Search + + + SearchDialog @@ -6006,18 +6105,6 @@ Cancel - - Replace - - - - Find in files - - - - Replace in files - - Continue Search @@ -6031,11 +6118,27 @@ - Replace this occurrence of ''%1''? + Close after search - Continue Replace + Find Previous + + + + Find Next + + + + Close + + + + Beginning of file has been reached. + + + + Do you want to continue from file's end? diff --git a/RedPandaIDE/utils.cpp b/RedPandaIDE/utils.cpp index 8b9be845..700c32ec 100644 --- a/RedPandaIDE/utils.cpp +++ b/RedPandaIDE/utils.cpp @@ -12,6 +12,7 @@ #include "compiler/executablerunner.h" #include #ifdef Q_OS_WIN +#include #include #endif @@ -526,3 +527,15 @@ QString getSizeString(int size) return QString("%1 ").arg(size / 1024.0 / 1024.0 / 1024.0)+QObject::tr("GB"); } } + +void saveComboHistory(QComboBox* cb,const QString& text) { + QString s = text.trimmed(); + if (s.isEmpty()) + return; + int i = cb->findText(s); + if (i>=0) { + cb->removeItem(i); + } + cb->insertItem(0,s); + cb->setCurrentText(s); +} diff --git a/RedPandaIDE/utils.h b/RedPandaIDE/utils.h index b18febde..678012b8 100644 --- a/RedPandaIDE/utils.h +++ b/RedPandaIDE/utils.h @@ -143,4 +143,8 @@ QByteArray getHTTPBody(const QByteArray& content); QString getSizeString(int size); +class QComboBox; +void saveComboHistory(QComboBox* cb,const QString& text); + + #endif // UTILS_H diff --git a/RedPandaIDE/widgets/replacedialog.cpp b/RedPandaIDE/widgets/replacedialog.cpp new file mode 100644 index 00000000..2e4b2fb4 --- /dev/null +++ b/RedPandaIDE/widgets/replacedialog.cpp @@ -0,0 +1,212 @@ +#include "replacedialog.h" +#include "ui_replacedialog.h" +#include +#include +#include +#include +#include "../utils.h" +#include "../editor.h" +#include "../editorlist.h" +#include "../mainwindow.h" + +ReplaceDialog::ReplaceDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::ReplaceDialog), + mSearchOptions() +{ + ui->setupUi(this); + mBasicSearchEngine= std::make_shared(); + mRegexSearchEngine= std::make_shared(); +} + +ReplaceDialog::~ReplaceDialog() +{ + delete ui; +} + +void ReplaceDialog::replace(const QString &text) +{ + ui->cbFind->setCurrentText(text); + ui->cbFind->setFocus(); + //ui->cbReplace->setCurrentText(""); + show(); +} + +void ReplaceDialog::doSearch(bool backward) +{ + saveComboHistory(ui->cbFind,ui->cbFind->currentText()); + prepareOptions(backward); + + Editor *editor = pMainWindow->editorList()->getEditor(); + if (editor) { + // Modify the caret when using 'from cursor' and when the selection is ignored + if (!mSearchOptions.testFlag(QSynedit::ssoEntireScope) && !mSearchOptions.testFlag(QSynedit::ssoSelectedOnly) + && editor->selAvail()) { + // start at end of selection + if (mSearchOptions.testFlag(QSynedit::ssoBackwards)) { + editor->setCaretXY(editor->blockBegin()); + } else { + editor->setCaretXY(editor->blockEnd()); + } + } + QSynedit::PSynSearchBase searchEngine; + if (mSearchOptions.testFlag(QSynedit::ssoRegExp)) { + searchEngine = mRegexSearchEngine; + } else { + searchEngine = mBasicSearchEngine; + } + editor->searchReplace( + ui->cbFind->currentText(), + "", + mSearchOptions, + searchEngine, nullptr, [this,backward](){ + QString msg; + if (backward) { + msg = tr("Beginning of file has been reached. ") + +tr("Do you want to continue from file's end?"); + } else { + msg = tr("End of file has been reached. ") + +tr("Do you want to continue from file's beginning?"); + } + QWidget *p; + if (isVisible()) { + p=this; + } else { + p=pMainWindow; + } + return QMessageBox::question(p, + tr("Continue Search"), + msg, + QMessageBox::Yes|QMessageBox::No, + QMessageBox::Yes) == QMessageBox::Yes; + }); + } +} + +void ReplaceDialog::doReplace(bool replaceAll) +{ + saveComboHistory(ui->cbFind,ui->cbFind->currentText()); + saveComboHistory(ui->cbReplace,ui->cbReplace->currentText()); + prepareOptions(false); + Editor *editor = pMainWindow->editorList()->getEditor(); + if (editor) { +// // Modify the caret when using 'from cursor' and when the selection is ignored +// if (!mSearchOptions.testFlag(QSynedit::ssoEntireScope) && !mSearchOptions.testFlag(QSynedit::ssoSelectedOnly) +// && editor->selAvail()) { +// // start at end of selection +// if (mSearchOptions.testFlag(QSynedit::ssoBackwards)) { +// editor->setCaretXY(editor->blockBegin()); +// } else { +// editor->setCaretXY(editor->blockEnd()); +// } +// } + QSynedit::PSynSearchBase searchEngine; + if (mSearchOptions.testFlag(QSynedit::ssoRegExp)) { + searchEngine = mRegexSearchEngine; + } else { + searchEngine = mBasicSearchEngine; + } + editor->searchReplace( + ui->cbFind->currentText(), + ui->cbReplace->currentText(), + mSearchOptions, + searchEngine, + [&replaceAll](const QString& /*sSearch*/, + const QString& /*sReplace*/, int /*Line*/, int /*ch*/, int /*wordLen*/){ + if (replaceAll) { + return QSynedit::SearchAction::ReplaceAll; + } else { + return QSynedit::SearchAction::ReplaceAndExit; + } + }, + [this](){ + QString msg = tr("End of file has been reached. ") + +tr("Do you want to continue from file's beginning?"); + QWidget *p; + if (isVisible()) { + p=this; + } else { + p=pMainWindow; + } + return QMessageBox::question(p, + tr("Continue Search"), + msg, + QMessageBox::Yes|QMessageBox::No, + QMessageBox::Yes) == QMessageBox::Yes; + }); + } +} + +void ReplaceDialog::prepareOptions(bool backward) +{ + mSearchOptions&=0; + + // Apply options + if (backward) { + mSearchOptions.setFlag(QSynedit::ssoBackwards); + } + + if (ui->chkRegExp->isChecked()) { + mSearchOptions.setFlag(QSynedit::ssoRegExp); + } + if (ui->chkCaseSensetive->isChecked()) { + mSearchOptions.setFlag(QSynedit::ssoMatchCase); + } + if (ui->chkWholeWord->isChecked()) { + mSearchOptions.setFlag(QSynedit::ssoWholeWord); + } + if (ui->chkWrapAround->isChecked()) { + mSearchOptions.setFlag(QSynedit::ssoWrapAround); + } + + // Apply scope, when enabled + if (ui->grpScope->isEnabled()) { + if (ui->rbSelection->isChecked()) { + mSearchOptions.setFlag(QSynedit::ssoSelectedOnly); + } + } + + // Apply origin, when enabled + if (ui->grpOrigin->isEnabled()) { + if (ui->rbEntireScope->isChecked()) { + mSearchOptions.setFlag(QSynedit::ssoEntireScope); + } + } + +} + +void ReplaceDialog::on_cbFind_currentTextChanged(const QString &value) +{ + ui->btnNext->setEnabled(!value.isEmpty()); + ui->btnPrevious->setEnabled(!value.isEmpty()); + ui->btnReplace->setEnabled(!value.isEmpty()); + ui->btnReplaceAll->setEnabled(!value.isEmpty()); +} + +void ReplaceDialog::on_btnClose_clicked() +{ + close(); +} + +void ReplaceDialog::on_btnNext_clicked() +{ + doSearch(false); +} + +void ReplaceDialog::on_btnPrevious_clicked() +{ + doSearch(true); +} + +void ReplaceDialog::on_btnReplace_clicked() +{ + doReplace(false); +} + + +void ReplaceDialog::on_btnReplaceAll_clicked() +{ + doReplace(true); + close(); +} + diff --git a/RedPandaIDE/widgets/replacedialog.h b/RedPandaIDE/widgets/replacedialog.h new file mode 100644 index 00000000..6ba68965 --- /dev/null +++ b/RedPandaIDE/widgets/replacedialog.h @@ -0,0 +1,42 @@ +#ifndef REPLACEDIALOG_H +#define REPLACEDIALOG_H + +#include +#include + +namespace Ui { +class ReplaceDialog; +} + +class ReplaceDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ReplaceDialog(QWidget *parent = nullptr); + ~ReplaceDialog(); + void replace(const QString& text); +private: + void doSearch(bool backward); + void doReplace(bool replaceAll); + void prepareOptions(bool backward); +private slots: + void on_cbFind_currentTextChanged(const QString &arg1); + + void on_btnClose_clicked(); + + void on_btnNext_clicked(); + + void on_btnPrevious_clicked(); + void on_btnReplace_clicked(); + + void on_btnReplaceAll_clicked(); + +private: + Ui::ReplaceDialog *ui; + QSynedit::SearchOptions mSearchOptions; + QSynedit::PSynSearchBase mBasicSearchEngine; + QSynedit::PSynSearchBase mRegexSearchEngine; +}; + +#endif // REPLACEDIALOG_H diff --git a/RedPandaIDE/widgets/replacedialog.ui b/RedPandaIDE/widgets/replacedialog.ui new file mode 100644 index 00000000..c7555a81 --- /dev/null +++ b/RedPandaIDE/widgets/replacedialog.ui @@ -0,0 +1,386 @@ + + + ReplaceDialog + + + + 0 + 0 + 512 + 242 + + + + Replace + + + + + + + 7 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Text to Find: + + + + + + + + 0 + 0 + + + + true + + + QComboBox::InsertAtTop + + + + + + + Replace with: + + + + + + + true + + + QComboBox::InsertAtTop + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Scope: + + + + 7 + + + 7 + + + 7 + + + 7 + + + + + Global + + + + + + + Selection + + + + + + + + + + Origin: + + + + 7 + + + 7 + + + 7 + + + 7 + + + + + From cursor + + + true + + + + + + + Entire scope + + + + + + + + + + Options: + + + + 7 + + + 7 + + + 7 + + + 7 + + + + + Whole words only + + + + + + + Wrap Around + + + true + + + + + + + + 0 + 0 + + + + <html><head/><body><p><a href="https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference"><span style=" text-decoration: underline; color:#0000ff;">(?)</span></a></p></body></html> + + + true + + + + + + + + 0 + 0 + + + + Regular Expression + + + + + + + Prompt on replace + + + true + + + + + + + Case Sensitive + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Find Previous + + + + + + + Find Next + + + true + + + + + + + Replace + + + + + + + Replace All + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 15 + + + + + + + + Close + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + diff --git a/RedPandaIDE/widgets/searchdialog.cpp b/RedPandaIDE/widgets/searchdialog.cpp index e9a70beb..b570607e 100644 --- a/RedPandaIDE/widgets/searchdialog.cpp +++ b/RedPandaIDE/widgets/searchdialog.cpp @@ -1,51 +1,23 @@ -/* - * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ #include "searchdialog.h" #include "ui_searchdialog.h" -#include -#include "../editor.h" -#include "../mainwindow.h" -#include "../editorlist.h" + +#include +#include #include #include -#include "../project.h" -#include "../settings.h" -#include -#include - +#include "../utils.h" +#include "../editor.h" +#include "../editorlist.h" +#include "../mainwindow.h" SearchDialog::SearchDialog(QWidget *parent) : QDialog(parent), ui(new Ui::SearchDialog), - mSearchEngine() + mSearchOptions() { - setWindowFlag(Qt::WindowContextHelpButtonHint,false); ui->setupUi(this); - mTabBar = new QTabBar(); - mTabBar->addTab(tr("Find")); - mTabBar->addTab(tr("Replace")); - mTabBar->addTab(tr("Find in files")); - mTabBar->addTab(tr("Replace in files")); - mTabBar->setExpanding(false); - ui->dialogLayout->insertWidget(0,mTabBar); - connect(mTabBar,&QTabBar::currentChanged,this, &SearchDialog::onTabChanged); - mSearchOptions&=0; - mBasicSearchEngine= QSynedit::PSynSearchBase(new QSynedit::BasicSearcher()); - mRegexSearchEngine= QSynedit::PSynSearchBase(new QSynedit::RegexSearcher()); + mBasicSearchEngine= std::make_shared(); + mRegexSearchEngine= std::make_shared(); } SearchDialog::~SearchDialog() @@ -55,11 +27,6 @@ SearchDialog::~SearchDialog() void SearchDialog::find(const QString &text) { - if (mTabBar->currentIndex()==0) { - this->onTabChanged(); - } else { - mTabBar->setCurrentIndex(0); - } ui->cbFind->setCurrentText(text); ui->cbFind->setFocus(); show(); @@ -67,153 +34,29 @@ void SearchDialog::find(const QString &text) void SearchDialog::findNext() { - if (mTabBar->currentIndex()==0) { // it's a find action - - // Disable entire scope searching - ui->rbEntireScope->setChecked(false); - - // Always search forwards - ui->rbForward->setChecked(true); - - ui->btnExecute->click(); - } + doSearch(false); + if (ui->chkCloseAfterSearch) + close(); } -void SearchDialog::findInFiles(const QString &text) +void SearchDialog::findPrevious() { - mTabBar->setCurrentIndex(2); - ui->cbFind->setCurrentText(text); - ui->cbFind->setFocus(); - show(); + doSearch(true); + if (ui->chkCloseAfterSearch) + close(); } -void SearchDialog::findInFiles(const QString &keyword, SearchFileScope scope, QSynedit::SearchOptions options) +void SearchDialog::doSearch(bool backward) { - mTabBar->setCurrentIndex(2); - - ui->cbFind->setCurrentText(keyword); - ui->cbFind->setFocus(); - - switch(scope) { - case SearchFileScope::currentFile: - ui->rbCurrentFile->setChecked(true); - break; - case SearchFileScope::openedFiles: - ui->rbOpenFiles->setChecked(true); - break; - case SearchFileScope::wholeProject: - ui->rbProject->setChecked(true); - break; - } - // Apply options - ui->chkRegExp->setChecked(options.testFlag(QSynedit::ssoRegExp)); - ui->chkCaseSensetive->setChecked(options.testFlag(QSynedit::ssoMatchCase)); - ui->chkWholeWord->setChecked(options.testFlag(QSynedit::ssoWholeWord)); - ui->chkWrapAround->setChecked(options.testFlag(QSynedit::ssoWholeWord)); - - show(); -} - -void SearchDialog::replace(const QString &sFind, const QString &sReplace) -{ - mTabBar->setCurrentIndex(1); - ui->cbFind->setCurrentText(sFind); - ui->cbReplace->setCurrentText(sReplace); - ui->cbFind->setFocus(); - show(); -} - -void SearchDialog::onTabChanged() -{ - bool isfind = (mTabBar->currentIndex() == 0); - bool isfindfiles = (mTabBar->currentIndex() == 2 || mTabBar->currentIndex() == 3 ); - bool isreplace = (mTabBar->currentIndex() == 1); - - ui->lblReplace->setVisible(isreplace); - ui->cbReplace->setVisible(isreplace); - - ui->grpOrigin->setVisible(isfind || isreplace); - ui->grpOrigin->setEnabled(isfind || isreplace); - - ui->grpScope->setVisible(isfind || isreplace); - ui->grpScope->setEnabled(isreplace); - ui->grpWhere->setVisible(isfindfiles); - ui->grpWhere->setEnabled(isfindfiles); - ui->grpDirection->setVisible(isfind || isreplace); - ui->grpDirection->setEnabled(isfind || isreplace); - - // grpOption is always visible - - // Disable project search option when none is open -// rbProjectFiles.Enabled := Assigned(MainForm.Project); - ui->rbProject->setEnabled(pMainWindow->project()!=nullptr); - ui->rbOpenFiles->setEnabled(pMainWindow->editorList()->pageCount()>0); -// if not Assigned(MainForm.Project) then -// rbOpenFiles.Checked := true; - - // Disable prompt when doing finds - ui->chkPrompt->setEnabled(isreplace); - ui->chkPrompt->setVisible(isreplace); - ui->chkWrapAround->setEnabled(!isfindfiles); - ui->chkWrapAround->setVisible(!isfindfiles); - - if (isfind || isfindfiles) { - ui->btnExecute->setText(tr("Find")); - } else { - ui->btnExecute->setText(tr("Replace")); - } - setWindowTitle(mTabBar->tabText(mTabBar->currentIndex())); -} - -void SearchDialog::on_cbFind_currentTextChanged(const QString &) -{ - ui->btnExecute->setEnabled(!ui->cbFind->currentText().isEmpty()); -} - -void SearchDialog::on_btnCancel_clicked() -{ - this->close(); -} - -static void saveComboHistory(QComboBox* cb,const QString& text) { - QString s = text.trimmed(); - if (s.isEmpty()) - return; - int i = cb->findText(s); - if (i>=0) { - cb->removeItem(i); - } - cb->insertItem(0,s); - cb->setCurrentText(s); -} - -void SearchDialog::on_btnExecute_clicked() -{ - int findCount = 0; saveComboHistory(ui->cbFind,ui->cbFind->currentText()); - saveComboHistory(ui->cbReplace,ui->cbReplace->currentText()); - - SearchAction actionType; - switch (mTabBar->currentIndex()) { - case 0: - actionType = SearchAction::Find; - break; - case 1: - actionType = SearchAction::Replace; - break; - case 2: - actionType = SearchAction::FindFiles; - break; - case 3: - actionType = SearchAction::ReplaceFiles; - break; - default: - return; - } mSearchOptions&=0; // Apply options + if (backward) { + mSearchOptions.setFlag(QSynedit::ssoBackwards); + } + if (ui->chkRegExp->isChecked()) { mSearchOptions.setFlag(QSynedit::ssoRegExp); } @@ -234,13 +77,6 @@ void SearchDialog::on_btnExecute_clicked() } } - // Apply direction, when enabled - if (ui->grpDirection->isEnabled()) { - if (ui->rbBackward->isChecked()) { - mSearchOptions.setFlag(QSynedit::ssoBackwards); - } - } - // Apply origin, when enabled if (ui->grpOrigin->isEnabled()) { if (ui->rbEntireScope->isChecked()) { @@ -248,259 +84,69 @@ void SearchDialog::on_btnExecute_clicked() } } - // Use entire scope for file finding/replacing - if (actionType == SearchAction::FindFiles || actionType == SearchAction::ReplaceFiles) { - mSearchOptions.setFlag(QSynedit::ssoEntireScope); - } - - this->close(); - - // Find the first one, then quit - if (actionType == SearchAction::Find) { - Editor *e = pMainWindow->editorList()->getEditor(); - if (e!=nullptr) { - findCount+=execute(e,ui->cbFind->currentText(),"",nullptr, - [](){ - return QMessageBox::question(pMainWindow, - tr("Continue Search"), - tr("End of file has been reached. ") - +tr("Do you want to continue from file's beginning?"), - QMessageBox::Yes|QMessageBox::No, - QMessageBox::Yes) == QMessageBox::Yes; - }); - } - } 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(), - [&doPrompt](const QString& sSearch, - const QString& /*sReplace*/, int /*Line*/, int /*ch*/, int /*wordLen*/){ - if (doPrompt) { - switch(QMessageBox::question(pMainWindow, - tr("Replace"), - tr("Replace this occurrence of ''%1''?").arg(sSearch), - QMessageBox::Yes|QMessageBox::YesAll|QMessageBox::No|QMessageBox::Cancel, - QMessageBox::Yes)) { - case QMessageBox::Yes: - return QSynedit::SearchAction::Replace; - case QMessageBox::YesAll: - return QSynedit::SearchAction::ReplaceAll; - case QMessageBox::No: - return QSynedit::SearchAction::Skip; - case QMessageBox::Cancel: - return QSynedit::SearchAction::Exit; - default: - return QSynedit::SearchAction::Exit; - } - } else { - return QSynedit::SearchAction::ReplaceAll; - } - }, - [](){ - return QMessageBox::question(pMainWindow, - tr("Continue Replace"), - tr("End of file has been reached. ") - +tr("Do you want to continue from file's beginning?"), - QMessageBox::Yes|QMessageBox::No, - QMessageBox::Yes) == QMessageBox::Yes; - }); - } - - } else if (actionType == SearchAction::FindFiles || actionType == SearchAction::ReplaceFiles) { - 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, - e->filename(), - keyword); - int t = parentItem->results.size(); - findCount+=t; - if (t>0) { - fileHitted++; - results->results.append(parentItem); - } - } + Editor *editor = pMainWindow->editorList()->getEditor(); + if (editor) { + // Modify the caret when using 'from cursor' and when the selection is ignored + if (!mSearchOptions.testFlag(QSynedit::ssoEntireScope) && !mSearchOptions.testFlag(QSynedit::ssoSelectedOnly) + && editor->selAvail()) { + // start at end of selection + if (mSearchOptions.testFlag(QSynedit::ssoBackwards)) { + editor->setCaretXY(editor->blockBegin()); + } else { + editor->setCaretXY(editor->blockEnd()); } - pMainWindow->searchResultModel()->notifySearchResultsUpdated(); - } else if (ui->rbCurrentFile->isChecked()) { - PSearchResults results = pMainWindow->searchResultModel()->addSearchResults( - keyword, - mSearchOptions, - SearchFileScope::currentFile - ); - Editor * e= pMainWindow->editorList()->getEditor(); - if (e!=nullptr) { - fileSearched++; - PSearchResultTreeItem parentItem = batchFindInEditor( - e, - e->filename(), - keyword); - int t = parentItem->results.size(); - findCount+=t; - if (t>0) { - fileHitted++; - results->results.append(parentItem); - } - } - pMainWindow->searchResultModel()->notifySearchResultsUpdated(); - } else if (ui->rbProject->isChecked()) { - PSearchResults results = pMainWindow->searchResultModel()->addSearchResults( - keyword, - mSearchOptions, - SearchFileScope::wholeProject - ); - foreach (PProjectUnit unit, pMainWindow->project()->unitList()) { - Editor * e = pMainWindow->project()->unitEditor(unit); - QString curFilename = unit->fileName(); - if (e) { - fileSearched++; - PSearchResultTreeItem parentItem = batchFindInEditor( - e, - e->filename(), - keyword); - int t = parentItem->results.size(); - findCount+=t; - if (t>0) { - fileHitted++; - results->results.append(parentItem); - } - } else if (fileExists(curFilename)) { - QSynedit::SynEdit editor; - QByteArray realEncoding; - editor.document()->loadFromFile(curFilename,ENCODING_AUTO_DETECT, realEncoding); - fileSearched++; - PSearchResultTreeItem parentItem = batchFindInEditor( - &editor, - curFilename, - keyword); - int t = parentItem->results.size(); - findCount+=t; - if (t>0) { - fileHitted++; - results->results.append(parentItem); - } - - } - } - pMainWindow->searchResultModel()->notifySearchResultsUpdated(); } - pMainWindow->showSearchPanel(actionType == SearchAction::ReplaceFiles); - } -} - -int SearchDialog::execute(QSynedit::SynEdit *editor, const QString &sSearch, const QString &sReplace, - QSynedit::SearchMathedProc matchCallback, - QSynedit::SearchConfirmAroundProc confirmAroundCallback) -{ - if (editor==nullptr) - return 0; - // Modify the caret when using 'from cursor' and when the selection is ignored - if (!mSearchOptions.testFlag(QSynedit::ssoEntireScope) && !mSearchOptions.testFlag(QSynedit::ssoSelectedOnly) - && editor->selAvail()) { - // start at end of selection - if (mSearchOptions.testFlag(QSynedit::ssoBackwards)) { - editor->setCaretXY(editor->blockBegin()); + QSynedit::PSynSearchBase searchEngine; + if (mSearchOptions.testFlag(QSynedit::ssoRegExp)) { + searchEngine = mRegexSearchEngine; } else { - editor->setCaretXY(editor->blockEnd()); + searchEngine = mBasicSearchEngine; } - } - - if (mSearchOptions.testFlag(QSynedit::ssoRegExp)) { - mSearchEngine = mRegexSearchEngine; - } else { - mSearchEngine = mBasicSearchEngine; - } - - return editor->searchReplace(sSearch, sReplace, mSearchOptions, - mSearchEngine, matchCallback, confirmAroundCallback); -} - -std::shared_ptr SearchDialog::batchFindInEditor(QSynedit::SynEdit *e, const QString& filename,const QString &keyword) -{ - //backup - QSynedit::BufferCoord caretBackup = e->caretXY(); - QSynedit::BufferCoord blockBeginBackup = e->blockBegin(); - QSynedit::BufferCoord blockEndBackup = e->blockEnd(); - int toplineBackup = e->topLine(); - int leftCharBackup = e->leftChar(); - - PSearchResultTreeItem parentItem = std::make_shared(); - parentItem->filename = filename; - parentItem->parent = nullptr; - execute(e,keyword,"", - [e,&parentItem, filename](const QString&, - const QString&, int Line, int ch, int wordLen){ - PSearchResultTreeItem item = std::make_shared(); - item->filename = filename; - item->line = Line; - item->start = ch; - item->len = wordLen; - item->parent = parentItem.get(); - item->text = e->document()->getString(Line-1); - item->text.replace('\t',' '); - parentItem->results.append(item); - return QSynedit::SearchAction::Skip; - }); - - // restore - e->setCaretXY(caretBackup); - e->setTopLine(toplineBackup); - e->setLeftChar(leftCharBackup); - e->setCaretAndSelection( - caretBackup, - blockBeginBackup, - blockEndBackup - ); - return parentItem; -} - -void SearchDialog::showEvent(QShowEvent *event) -{ - QDialog::showEvent(event); - if (pSettings->environment().language()=="zh_CN") { - ui->txtRegExpHelp->setText( - QString("

(?)

") - .arg("https://www.runoob.com/regexp/regexp-tutorial.html")); - } else { - ui->txtRegExpHelp->setText( - QString("

(?)

") - .arg("https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference")); + editor->searchReplace( + ui->cbFind->currentText(), + "", + mSearchOptions, + searchEngine, nullptr, [this,backward](){ + QString msg; + if (backward) { + msg = tr("Beginning of file has been reached. ") + +tr("Do you want to continue from file's end?"); + } else { + msg = tr("End of file has been reached. ") + +tr("Do you want to continue from file's beginning?"); + } + QWidget *p; + if (isVisible()) { + p=this; + } else { + p=pMainWindow; + } + return QMessageBox::question(p, + tr("Continue Search"), + msg, + QMessageBox::Yes|QMessageBox::No, + QMessageBox::Yes) == QMessageBox::Yes; + }); } } -QTabBar *SearchDialog::tabBar() const +void SearchDialog::on_cbFind_currentTextChanged(const QString &value) { - return mTabBar; + ui->btnNext->setEnabled(!value.isEmpty()); + ui->btnPrevious->setEnabled(!value.isEmpty()); } -QSynedit::PSynSearchBase SearchDialog::searchEngine() const +void SearchDialog::on_btnClose_clicked() { - return mSearchEngine; + close(); } -void SearchDialog::findPrevious() +void SearchDialog::on_btnNext_clicked() { - if (mTabBar->currentIndex()==0) { // it's a find action - - // Disable entire scope searching - ui->rbEntireScope->setChecked(false); - - // Always search backward - ui->rbBackward->setChecked(true); - - ui->btnExecute->click(); - } + doSearch(false); +} + +void SearchDialog::on_btnPrevious_clicked() +{ + doSearch(true); } diff --git a/RedPandaIDE/widgets/searchdialog.h b/RedPandaIDE/widgets/searchdialog.h index c1ddd094..5e89e9ab 100644 --- a/RedPandaIDE/widgets/searchdialog.h +++ b/RedPandaIDE/widgets/searchdialog.h @@ -1,81 +1,40 @@ -/* - * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ #ifndef SEARCHDIALOG_H #define SEARCHDIALOG_H #include -#include -#include "../utils.h" +#include namespace Ui { class SearchDialog; } -struct SearchResultTreeItem; -class QTabBar; -class Editor; class SearchDialog : public QDialog { Q_OBJECT - enum class SearchAction { - Find, - FindFiles, - Replace, - ReplaceFiles - }; - public: explicit SearchDialog(QWidget *parent = nullptr); ~SearchDialog(); void find(const QString& text); void findNext(); void findPrevious(); - void findInFiles(const QString& text); - void findInFiles(const QString& keyword, SearchFileScope scope, QSynedit::SearchOptions options); - void replace(const QString& sFind, const QString& sReplace); - QSynedit::PSynSearchBase searchEngine() const; - - QTabBar *tabBar() const; - -private slots: - void onTabChanged(); - void on_cbFind_currentTextChanged(const QString &arg1); - - void on_btnCancel_clicked(); - - void on_btnExecute_clicked(); private: - int execute(QSynedit::SynEdit* editor, const QString& sSearch, - const QString& sReplace, - QSynedit::SearchMathedProc matchCallback = nullptr, - QSynedit::SearchConfirmAroundProc confirmAroundCallback = nullptr); - std::shared_ptr batchFindInEditor(QSynedit::SynEdit * editor,const QString& filename, const QString& keyword); + void doSearch(bool backward); +private slots: + void on_cbFind_currentTextChanged(const QString &arg1); + + void on_btnClose_clicked(); + + void on_btnNext_clicked(); + + + void on_btnPrevious_clicked(); + private: Ui::SearchDialog *ui; - QTabBar *mTabBar; QSynedit::SearchOptions mSearchOptions; - QSynedit::PSynSearchBase mSearchEngine; QSynedit::PSynSearchBase mBasicSearchEngine; QSynedit::PSynSearchBase mRegexSearchEngine; - - // QWidget interface -protected: - void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; }; #endif // SEARCHDIALOG_H diff --git a/RedPandaIDE/widgets/searchdialog.ui b/RedPandaIDE/widgets/searchdialog.ui index 17b5be13..13ad7bbf 100644 --- a/RedPandaIDE/widgets/searchdialog.ui +++ b/RedPandaIDE/widgets/searchdialog.ui @@ -6,41 +6,35 @@ 0 0 - 684 - 548 + 609 + 242 - Dialog + Find - - - 0 - - - 2 - - - 2 - - - 2 - - - 2 - + - - - QFrame::StyledPanel - - - QFrame::Raised - + + + 7 + + + 0 + + + 0 + + + 0 + + + 0 + - - + + 0 @@ -53,14 +47,14 @@ 0 - + Text to Find: - + @@ -76,131 +70,42 @@ - - - - - 0 - 0 - - - - true - - - QComboBox::InsertAtTop - - - - - - - Replace with: - - - - - - - - - Options: - - - - - - Case Sensitive - - - - - - - Whole words only - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Regular Expression - - - - - - - - 0 - 0 - - - - <html><head/><body><p><a href="https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference"><span style=" text-decoration: underline; color:#0000ff;">(?)</span></a></p></body></html> - - - true - - - - - - - - - - Wrap Around - - - true - - - - - - - Prompt on replace - - - true - - - - - - - + + + + 0 + + + 0 + + + 0 + + + 0 + + Scope: + + 7 + + + 7 + + + 7 + + + 7 + @@ -218,12 +123,24 @@ - + Origin: + + 7 + + + 7 + + + 7 + + + 7 + @@ -244,107 +161,92 @@ - - - - + + + + Options: + + + 7 - - 0 - - 0 + 7 - 0 + 7 - 0 + 7 - - - - Direction: + + + + Case Sensitive - - - - - Forward - - - true - - - - - - - Backward - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - - Where: + + + + Wrap Around + + + true + + + + + + + Prompt on replace + + + true + + + + + + + + 0 + 0 + + + + Regular Expression + + + + + + + Whole words only + + + + + + + + 0 + 0 + + + + <html><head/><body><p><a href="https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference"><span style=" text-decoration: underline; color:#0000ff;">(?)</span></a></p></body></html> + + + true + + + + + + + Close after search - - - - - Current File - - - true - - - - - - - Files In Project - - - - - - - Open Files - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - @@ -353,6 +255,77 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Find Previous + + + + + + + Find Next + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 15 + + + + + + + + Close + + + @@ -361,7 +334,7 @@ 20 - 134 + 40 @@ -369,45 +342,6 @@ - - - - - 0 - 0 - - - - - - - Qt::Horizontal - - - - 455 - 20 - - - - - - - - Find - - - - - - - Cancel - - - - - - diff --git a/RedPandaIDE/widgets/searchinfiledialog.cpp b/RedPandaIDE/widgets/searchinfiledialog.cpp new file mode 100644 index 00000000..16c36e0e --- /dev/null +++ b/RedPandaIDE/widgets/searchinfiledialog.cpp @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "searchinfiledialog.h" +#include "ui_searchinfiledialog.h" +#include +#include "../editor.h" +#include "../mainwindow.h" +#include "../editorlist.h" +#include +#include +#include "../project.h" +#include "../settings.h" +#include +#include + + +SearchInFileDialog::SearchInFileDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::SearchInFileDialog) +{ + setWindowFlag(Qt::WindowContextHelpButtonHint,false); + ui->setupUi(this); + mSearchOptions&=0; + mBasicSearchEngine= QSynedit::PSynSearchBase(new QSynedit::BasicSearcher()); + mRegexSearchEngine= QSynedit::PSynSearchBase(new QSynedit::RegexSearcher()); +} + +SearchInFileDialog::~SearchInFileDialog() +{ + delete ui; +} + + +void SearchInFileDialog::findInFiles(const QString &text) +{ + ui->cbFind->setCurrentText(text); + ui->cbFind->setFocus(); + show(); +} + +void SearchInFileDialog::findInFiles(const QString &keyword, SearchFileScope scope, QSynedit::SearchOptions options) +{ + ui->cbFind->setCurrentText(keyword); + ui->cbFind->setFocus(); + + switch(scope) { + case SearchFileScope::currentFile: + ui->rbCurrentFile->setChecked(true); + break; + case SearchFileScope::openedFiles: + ui->rbOpenFiles->setChecked(true); + break; + case SearchFileScope::wholeProject: + ui->rbProject->setChecked(true); + break; + } + // Apply options + ui->chkRegExp->setChecked(options.testFlag(QSynedit::ssoRegExp)); + ui->chkCaseSensetive->setChecked(options.testFlag(QSynedit::ssoMatchCase)); + ui->chkWholeWord->setChecked(options.testFlag(QSynedit::ssoWholeWord)); + show(); +} + +void SearchInFileDialog::on_cbFind_currentTextChanged(const QString &value) +{ + ui->btnExecute->setEnabled(!value.isEmpty()); + ui->btnReplace->setEnabled(!value.isEmpty()); +} + +void SearchInFileDialog::on_btnCancel_clicked() +{ + this->close(); +} + +void SearchInFileDialog::on_btnExecute_clicked() +{ + doSearch(false); +} + +void SearchInFileDialog::doSearch(bool replace) +{ + int findCount = 0; + saveComboHistory(ui->cbFind,ui->cbFind->currentText()); + + mSearchOptions&=0; + + // Apply options + if (ui->chkRegExp->isChecked()) { + mSearchOptions.setFlag(QSynedit::ssoRegExp); + } + if (ui->chkCaseSensetive->isChecked()) { + mSearchOptions.setFlag(QSynedit::ssoMatchCase); + } + if (ui->chkWholeWord->isChecked()) { + mSearchOptions.setFlag(QSynedit::ssoWholeWord); + } + + mSearchOptions.setFlag(QSynedit::ssoEntireScope); + + close(); + + // Find the first one, then quit + 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, + e->filename(), + 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::currentFile + ); + Editor * e= pMainWindow->editorList()->getEditor(); + if (e!=nullptr) { + fileSearched++; + PSearchResultTreeItem parentItem = batchFindInEditor( + e, + e->filename(), + keyword); + int t = parentItem->results.size(); + findCount+=t; + if (t>0) { + fileHitted++; + results->results.append(parentItem); + } + } + pMainWindow->searchResultModel()->notifySearchResultsUpdated(); + } else if (ui->rbProject->isChecked()) { + PSearchResults results = pMainWindow->searchResultModel()->addSearchResults( + keyword, + mSearchOptions, + SearchFileScope::wholeProject + ); + foreach (PProjectUnit unit, pMainWindow->project()->unitList()) { + Editor * e = pMainWindow->project()->unitEditor(unit); + QString curFilename = unit->fileName(); + if (e) { + fileSearched++; + PSearchResultTreeItem parentItem = batchFindInEditor( + e, + e->filename(), + keyword); + int t = parentItem->results.size(); + findCount+=t; + if (t>0) { + fileHitted++; + results->results.append(parentItem); + } + } else if (fileExists(curFilename)) { + QSynedit::SynEdit editor; + QByteArray realEncoding; + editor.document()->loadFromFile(curFilename,ENCODING_AUTO_DETECT, realEncoding); + fileSearched++; + PSearchResultTreeItem parentItem = batchFindInEditor( + &editor, + curFilename, + keyword); + int t = parentItem->results.size(); + findCount+=t; + if (t>0) { + fileHitted++; + results->results.append(parentItem); + } + + } + } + pMainWindow->searchResultModel()->notifySearchResultsUpdated(); + } + pMainWindow->showSearchPanel(replace); + +} + +int SearchInFileDialog::execute(QSynedit::SynEdit *editor, const QString &sSearch, const QString &sReplace, + QSynedit::SearchMathedProc matchCallback, + QSynedit::SearchConfirmAroundProc confirmAroundCallback) +{ + if (editor==nullptr) + return 0; + // Modify the caret when using 'from cursor' and when the selection is ignored + if (!mSearchOptions.testFlag(QSynedit::ssoEntireScope) && !mSearchOptions.testFlag(QSynedit::ssoSelectedOnly) + && editor->selAvail()) { + // start at end of selection + if (mSearchOptions.testFlag(QSynedit::ssoBackwards)) { + editor->setCaretXY(editor->blockBegin()); + } else { + editor->setCaretXY(editor->blockEnd()); + } + } + + QSynedit::PSynSearchBase searchEngine; + if (mSearchOptions.testFlag(QSynedit::ssoRegExp)) { + searchEngine = mRegexSearchEngine; + } else { + searchEngine = mBasicSearchEngine; + } + + return editor->searchReplace(sSearch, sReplace, mSearchOptions, + searchEngine, matchCallback, confirmAroundCallback); +} + +std::shared_ptr SearchInFileDialog::batchFindInEditor(QSynedit::SynEdit *e, const QString& filename,const QString &keyword) +{ + //backup + QSynedit::BufferCoord caretBackup = e->caretXY(); + QSynedit::BufferCoord blockBeginBackup = e->blockBegin(); + QSynedit::BufferCoord blockEndBackup = e->blockEnd(); + int toplineBackup = e->topLine(); + int leftCharBackup = e->leftChar(); + + PSearchResultTreeItem parentItem = std::make_shared(); + parentItem->filename = filename; + parentItem->parent = nullptr; + execute(e,keyword,"", + [e,&parentItem, filename](const QString&, + const QString&, int Line, int ch, int wordLen){ + PSearchResultTreeItem item = std::make_shared(); + item->filename = filename; + item->line = Line; + item->start = ch; + item->len = wordLen; + item->parent = parentItem.get(); + item->text = e->document()->getString(Line-1); + item->text.replace('\t',' '); + parentItem->results.append(item); + return QSynedit::SearchAction::Skip; + }); + + // restore + e->setCaretXY(caretBackup); + e->setTopLine(toplineBackup); + e->setLeftChar(leftCharBackup); + e->setCaretAndSelection( + caretBackup, + blockBeginBackup, + blockEndBackup + ); + return parentItem; +} + +void SearchInFileDialog::showEvent(QShowEvent *event) +{ + QDialog::showEvent(event); + if (pSettings->environment().language()=="zh_CN") { + ui->txtRegExpHelp->setText( + QString("

(?)

") + .arg("https://www.runoob.com/regexp/regexp-tutorial.html")); + } else { + ui->txtRegExpHelp->setText( + QString("

(?)

") + .arg("https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference")); + } +} + +void SearchInFileDialog::on_btnReplace_clicked() +{ + doSearch(true); +} + diff --git a/RedPandaIDE/widgets/searchinfiledialog.h b/RedPandaIDE/widgets/searchinfiledialog.h new file mode 100644 index 00000000..23f61256 --- /dev/null +++ b/RedPandaIDE/widgets/searchinfiledialog.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef SEARCHINFILEDIALOG_H +#define SEARCHINFILEDIALOG_H + +#include +#include +#include "../utils.h" + +namespace Ui { +class SearchInFileDialog; +} + +struct SearchResultTreeItem; +class QTabBar; +class Editor; +class SearchInFileDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SearchInFileDialog(QWidget *parent = nullptr); + ~SearchInFileDialog(); + void findInFiles(const QString& text); + void findInFiles(const QString& keyword, SearchFileScope scope, QSynedit::SearchOptions options); + QSynedit::PSynSearchBase searchEngine() const; + +private slots: + void on_cbFind_currentTextChanged(const QString &arg1); + + void on_btnCancel_clicked(); + + void on_btnExecute_clicked(); + void on_btnReplace_clicked(); + +private: + void doSearch(bool replace); + int execute(QSynedit::SynEdit* editor, const QString& sSearch, + const QString& sReplace, + QSynedit::SearchMathedProc matchCallback = nullptr, + QSynedit::SearchConfirmAroundProc confirmAroundCallback = nullptr); + std::shared_ptr batchFindInEditor(QSynedit::SynEdit * editor,const QString& filename, const QString& keyword); +private: + Ui::SearchInFileDialog *ui; + QSynedit::SearchOptions mSearchOptions; + QSynedit::PSynSearchBase mBasicSearchEngine; + QSynedit::PSynSearchBase mRegexSearchEngine; + + // QWidget interface +protected: + void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; +}; + +#endif // SEARCHINFILEDIALOG_H diff --git a/RedPandaIDE/widgets/searchinfiledialog.ui b/RedPandaIDE/widgets/searchinfiledialog.ui new file mode 100644 index 00000000..626b439b --- /dev/null +++ b/RedPandaIDE/widgets/searchinfiledialog.ui @@ -0,0 +1,278 @@ + + + SearchInFileDialog + + + + 0 + 0 + 684 + 548 + + + + Dialog + + + + 0 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Text to Find: + + + + + + + + 0 + 0 + + + + true + + + QComboBox::InsertAtTop + + + + + + + + + + + + + + 7 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Where: + + + + + + Current File + + + true + + + + + + + Files In Project + + + + + + + Open Files + + + + + + + + + + + + + Options: + + + + + + Whole words only + + + + + + + Case Sensitive + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + 0 + + + + Regular Expression + + + + + + + + 0 + 0 + + + + <html><head/><body><p><a href="https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference"><span style=" text-decoration: underline; color:#0000ff;">(?)</span></a></p></body></html> + + + true + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 134 + + + + + + + + + + + + 0 + 0 + + + + + + + Qt::Horizontal + + + + 455 + 20 + + + + + + + + Find + + + + + + + Replace + + + + + + + Cancel + + + + + + + + + + + diff --git a/libs/qsynedit/qsynedit/SynEdit.cpp b/libs/qsynedit/qsynedit/SynEdit.cpp index d945803f..4a9db21b 100644 --- a/libs/qsynedit/qsynedit/SynEdit.cpp +++ b/libs/qsynedit/qsynedit/SynEdit.cpp @@ -5241,6 +5241,7 @@ int SynEdit::searchReplace(const QString &sSearch, const QString &sReplace, Sear else i = 0; // Operate on all results in this line. + bool needRefresh = (nInLine>0); while (nInLine > 0) { // An occurrence may have been replaced with a text of different length int nFound = searchEngine->result(i) + 1 + iResultOffset; @@ -5274,26 +5275,25 @@ int SynEdit::searchReplace(const QString &sSearch, const QString &sReplace, Sear setBlockBegin(ptCurrent); //Be sure to use the Ex version of CursorPos so that it appears in the middle if necessary - setCaretXYEx(false, BufferCoord{1, ptCurrent.line}); + setCaretXYEx(false, BufferCoord{ptCurrent.ch, ptCurrent.line}); ensureCursorPosVisibleEx(true); ptCurrent.ch += nSearchLen; setBlockEnd(ptCurrent); - //internalSetCaretXY(ptCurrent); - if (bBackward) - internalSetCaretXY(blockBegin()); - else - internalSetCaretXY(ptCurrent); QString replaceText = searchEngine->replace(selText(), sReplace); - if (matchedCallback && !dobatchReplace) { + if (searchAction==SearchAction::ReplaceAndExit) { + searchAction=SearchAction::Exit; + } else if (matchedCallback && !dobatchReplace) { searchAction = matchedCallback(sSearch,replaceText,ptCurrent.line, nFound,nSearchLen); } if (searchAction==SearchAction::Exit) { + invalidateLine(ptCurrent.line); return result; } else if (searchAction == SearchAction::Skip) { continue; } else if (searchAction == SearchAction::Replace + || searchAction == SearchAction::ReplaceAndExit || searchAction == SearchAction::ReplaceAll) { if (!dobatchReplace && (searchAction == SearchAction::ReplaceAll) ){ @@ -5319,14 +5319,17 @@ int SynEdit::searchReplace(const QString &sSearch, const QString &sReplace, Sear mOptions.setFlag(EditorOption::eoAutoIndent,oldAutoIndent); } } + if (needRefresh) + invalidateLine(ptCurrent.line); + // search next / previous line if (bBackward) ptCurrent.line--; else ptCurrent.line++; if (((ptCurrent.line < ptStart.line) || (ptCurrent.line > ptEnd.line)) - && bFromCursor && sOptions.testFlag(ssoWrapAround)){ - if (confirmAroundCallback && !confirmAroundCallback()) + && bFromCursor ){ + if (!sOptions.testFlag(ssoWrapAround) && confirmAroundCallback && !confirmAroundCallback()) break; //search start from cursor, search has finished but no result founds bFromCursor = false; diff --git a/libs/qsynedit/qsynedit/SynEdit.h b/libs/qsynedit/qsynedit/SynEdit.h index ecbcde53..026d583e 100644 --- a/libs/qsynedit/qsynedit/SynEdit.h +++ b/libs/qsynedit/qsynedit/SynEdit.h @@ -113,6 +113,7 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(EditorOptions) enum class SearchAction { Replace, ReplaceAll, + ReplaceAndExit, Skip, Exit }; diff --git a/libs/qsynedit/qsynedit/TextPainter.cpp b/libs/qsynedit/qsynedit/TextPainter.cpp index a8351b5b..b58a1b86 100644 --- a/libs/qsynedit/qsynedit/TextPainter.cpp +++ b/libs/qsynedit/qsynedit/TextPainter.cpp @@ -252,64 +252,62 @@ void SynEditTextPainter::computeSelectionInfo() BufferCoord vEnd{0,0}; bAnySelection = false; // Only if selection is visible anyway. - if (edit->hasFocus()) { - bAnySelection = true; - // Get the *real* start of the selected area. - if (edit->mBlockBegin.line < edit->mBlockEnd.line) { - vStart = edit->mBlockBegin; - vEnd = edit->mBlockEnd; - } else if (edit->mBlockBegin.line > edit->mBlockEnd.line) { - vEnd = edit->mBlockBegin; - vStart = edit->mBlockEnd; - } else if (edit->mBlockBegin.ch != edit->mBlockEnd.ch) { - // it is only on this line. - vStart.line = edit->mBlockBegin.line; - vEnd.line = vStart.line; - if (edit->mBlockBegin.ch < edit->mBlockEnd.ch) { - vStart.ch = edit->mBlockBegin.ch; - vEnd.ch = edit->mBlockEnd.ch; - } else { - vStart.ch = edit->mBlockEnd.ch; - vEnd.ch = edit->mBlockBegin.ch; - } - } else - bAnySelection = false; - if (edit->mInputPreeditString.length()>0) { - if (vStart.line == edit->mCaretY && vStart.ch >=edit->mCaretX) { - vStart.ch+=edit->mInputPreeditString.length(); - } - if (vEnd.line == edit->mCaretY && vEnd.ch >edit->mCaretX) { - vEnd.ch+=edit->mInputPreeditString.length(); - } + bAnySelection = true; + // Get the *real* start of the selected area. + if (edit->mBlockBegin.line < edit->mBlockEnd.line) { + vStart = edit->mBlockBegin; + vEnd = edit->mBlockEnd; + } else if (edit->mBlockBegin.line > edit->mBlockEnd.line) { + vEnd = edit->mBlockBegin; + vStart = edit->mBlockEnd; + } else if (edit->mBlockBegin.ch != edit->mBlockEnd.ch) { + // it is only on this line. + vStart.line = edit->mBlockBegin.line; + vEnd.line = vStart.line; + if (edit->mBlockBegin.ch < edit->mBlockEnd.ch) { + vStart.ch = edit->mBlockBegin.ch; + vEnd.ch = edit->mBlockEnd.ch; + } else { + vStart.ch = edit->mBlockEnd.ch; + vEnd.ch = edit->mBlockBegin.ch; } - // If there is any visible selection so far, then test if there is an - // intersection with the area to be painted. + } else + bAnySelection = false; + if (edit->mInputPreeditString.length()>0) { + if (vStart.line == edit->mCaretY && vStart.ch >=edit->mCaretX) { + vStart.ch+=edit->mInputPreeditString.length(); + } + if (vEnd.line == edit->mCaretY && vEnd.ch >edit->mCaretX) { + vEnd.ch+=edit->mInputPreeditString.length(); + } + } + // If there is any visible selection so far, then test if there is an + // intersection with the area to be painted. + if (bAnySelection) { + // Don't care if the selection is not visible. + bAnySelection = (vEnd.line >= vFirstLine) && (vStart.line <= vLastLine); if (bAnySelection) { - // Don't care if the selection is not visible. - bAnySelection = (vEnd.line >= vFirstLine) && (vStart.line <= vLastLine); - if (bAnySelection) { - // Transform the selection from text space into screen space - vSelStart = edit->bufferToDisplayPos(vStart); - vSelEnd = edit->bufferToDisplayPos(vEnd); - if (edit->mInputPreeditString.length() - && vStart.line == edit->mCaretY) { - QString sLine = edit->lineText().left(edit->mCaretX-1) - + edit->mInputPreeditString - + edit->lineText().mid(edit->mCaretX-1); - vSelStart.Column = edit->charToColumn(sLine,vStart.ch); - } - if (edit->mInputPreeditString.length() - && vEnd.line == edit->mCaretY) { - QString sLine = edit->lineText().left(edit->mCaretX-1) - + edit->mInputPreeditString - + edit->lineText().mid(edit->mCaretX-1); - vSelEnd.Column = edit->charToColumn(sLine,vEnd.ch); - } - // In the column selection mode sort the begin and end of the selection, - // this makes the painting code simpler. - if (edit->mActiveSelectionMode == SelectionMode::Column && vSelStart.Column > vSelEnd.Column) - std::swap(vSelStart.Column, vSelEnd.Column); + // Transform the selection from text space into screen space + vSelStart = edit->bufferToDisplayPos(vStart); + vSelEnd = edit->bufferToDisplayPos(vEnd); + if (edit->mInputPreeditString.length() + && vStart.line == edit->mCaretY) { + QString sLine = edit->lineText().left(edit->mCaretX-1) + + edit->mInputPreeditString + + edit->lineText().mid(edit->mCaretX-1); + vSelStart.Column = edit->charToColumn(sLine,vStart.ch); } + if (edit->mInputPreeditString.length() + && vEnd.line == edit->mCaretY) { + QString sLine = edit->lineText().left(edit->mCaretX-1) + + edit->mInputPreeditString + + edit->lineText().mid(edit->mCaretX-1); + vSelEnd.Column = edit->charToColumn(sLine,vEnd.ch); + } + // In the column selection mode sort the begin and end of the selection, + // this makes the painting code simpler. + if (edit->mActiveSelectionMode == SelectionMode::Column && vSelStart.Column > vSelEnd.Column) + std::swap(vSelStart.Column, vSelEnd.Column); } } }