Move find/replace logic out of shader viewer so it can be reused

This commit is contained in:
baldurk
2026-08-13 17:46:48 +01:00
parent 91cf34e4bd
commit b60f42d8b1
9 changed files with 736 additions and 514 deletions
+554 -8
View File
@@ -25,10 +25,16 @@
#include "FindReplace.h"
#include <QKeyEvent>
#include <QLineEdit>
#include "Code/QRDUtils.h"
#include "Code/ScintillaSyntax.h"
#include "Widgets/Extended/RDLineEdit.h"
#include "scintilla/include/qt/ScintillaEdit.h"
#include "toolwindowmanager/ToolWindowManager.h"
#include "toolwindowmanager/ToolWindowManagerArea.h"
#include "ui_FindReplace.h"
FindReplace::FindReplace(QWidget *parent) : QFrame(parent), ui(new Ui::FindReplace)
FindReplace::FindReplace(QList<ScintillaEdit *> &scintillas, QWidget *parent)
: QFrame(parent), ui(new Ui::FindReplace), m_Scintillas(scintillas)
{
ui->setupUi(this);
@@ -55,7 +61,7 @@ FindReplace::FindReplace(QWidget *parent) : QFrame(parent), ui(new Ui::FindRepla
m_direction = SearchDirection::Down;
addHistory(ui->findText);
emit performFind();
performFind();
m_direction = dir;
}
@@ -99,6 +105,52 @@ bool FindReplace::regexp()
return ui->regexp->isChecked();
}
void FindReplace::configureFindIndicator(ScintillaEdit *editor, sptr_t indic)
{
editor->indicSetFore(indic, SCINTILLA_COLOUR(200, 200, 64));
editor->indicSetStyle(indic, INDIC_FULLBOX);
editor->indicSetAlpha(indic, 50);
editor->indicSetOutlineAlpha(indic, 80);
}
void FindReplace::raiseOrShow()
{
if(m_DockManager)
{
if(isVisible())
{
ToolWindowManager::raiseToolWindow(this);
}
else
{
m_DockManager->moveToolWindow(
this, ToolWindowManager::AreaReference(ToolWindowManager::NewFloatingArea));
m_DockManager->setToolWindowProperties(this, ToolWindowManager::HideOnClose);
}
m_DockManager->areaOf(this)->parentWidget()->activateWindow();
}
takeFocus();
}
void FindReplace::setFindAllResultsDisplay(ScintillaEdit *findResults)
{
m_FindResultsDisplay = findResults;
configureFindIndicator(m_FindResultsDisplay, INDICATOR_FINDRESULTHIGHLIGHT);
QColor highlightColor = palette().color(QPalette::Highlight).toRgb();
m_FindResultsDisplay->indicSetFore(
INDICATOR_FINDALLCURRENTRESULT,
SCINTILLA_COLOUR(highlightColor.red(), highlightColor.green(), highlightColor.blue()));
m_FindResultsDisplay->indicSetStyle(INDICATOR_FINDALLCURRENTRESULT, INDIC_FULLBOX);
m_FindResultsDisplay->indicSetAlpha(INDICATOR_FINDALLCURRENTRESULT, 120);
m_FindResultsDisplay->indicSetOutlineAlpha(INDICATOR_FINDALLCURRENTRESULT, 180);
QObject::connect(m_FindResultsDisplay, &ScintillaEdit::doubleClick, this,
&FindReplace::resultsDoubleClick);
}
void FindReplace::setFindText(QString text)
{
ui->findText->setCurrentText(text);
@@ -124,6 +176,14 @@ void FindReplace::allowUserModeChange(bool allow)
ui->modeChangeFrame->setVisible(allow);
}
void FindReplace::allowFindAll(bool allow)
{
ui->searchContextLabel->setVisible(allow);
ui->searchContext->setVisible(allow);
if(!allow)
ui->searchContext->setCurrentIndex(FindReplace::File);
}
void FindReplace::setReplaceMode(bool replacing)
{
ui->replaceLabel->setVisible(replacing);
@@ -159,12 +219,23 @@ void FindReplace::keyPressEvent(QKeyEvent *event)
else
m_direction = SearchDirection::Down;
emit performFind();
performFind();
m_direction = dir;
}
else
{
if(event->key() == Qt::Key_Escape)
{
// if we are in a floating area, hide on escape
ToolWindowManagerArea *area = m_DockManager->areaOf(this);
if(area && m_DockManager->isFloating(this))
{
m_DockManager->hideToolWindow(this);
}
}
emit keyPress(event);
}
}
@@ -191,34 +262,34 @@ void FindReplace::on_findPrev_clicked()
{
setDirection(FindReplace::Up);
addHistory(ui->findText);
emit performFind();
performFind();
}
void FindReplace::on_find_clicked()
{
setDirection(FindReplace::Down);
addHistory(ui->findText);
emit performFind();
performFind();
}
void FindReplace::on_findAll_clicked()
{
addHistory(ui->findText);
emit performFindAll();
performFindAll();
}
void FindReplace::on_replace_clicked()
{
addHistory(ui->findText);
addHistory(ui->replaceText);
emit performReplace();
performReplace();
}
void FindReplace::on_replaceAll_clicked()
{
addHistory(ui->findText);
addHistory(ui->replaceText);
emit performReplaceAll();
performReplaceAll();
}
void FindReplace::on_findMode_clicked()
@@ -230,3 +301,478 @@ void FindReplace::on_replaceMode_clicked()
{
setReplaceMode(true);
}
ScintillaEdit *FindReplace::currentScintilla()
{
ScintillaEdit *cur = qobject_cast<ScintillaEdit *>(QApplication::focusWidget());
if(cur == NULL)
{
for(ScintillaEdit *s : m_Scintillas)
{
if(s->isVisible())
{
cur = s;
break;
}
}
}
return cur;
}
ScintillaEdit *FindReplace::nextScintilla(ScintillaEdit *cur)
{
for(int i = 0; i < m_Scintillas.count(); i++)
{
if(m_Scintillas[i] == cur)
{
if(i + 1 < m_Scintillas.count())
return m_Scintillas[i + 1];
return m_Scintillas[0];
}
}
if(!m_Scintillas.isEmpty())
return m_Scintillas[0];
return NULL;
}
void FindReplace::clearFindState()
{
m_FindState = FindState();
}
void FindReplace::performFind(bool down)
{
ScintillaEdit *cur = currentScintilla();
if(!cur)
return;
QString find = findText();
sptr_t flags = 0;
if(matchCase())
flags |= SCFIND_MATCHCASE;
if(matchWord())
flags |= SCFIND_WHOLEWORD;
if(regexp())
flags |= SCFIND_REGEXP | SCFIND_POSIX;
FindReplace::SearchContext ctx = context();
QString findHash = QFormatStr("%1%2%3%4").arg(find).arg(flags).arg((int)ctx).arg(down);
if(findHash != m_FindState.hash)
{
m_FindState.hash = findHash;
m_FindState.start = 0;
m_FindState.end = cur->length();
m_FindState.offset = cur->currentPos();
if(down && cur->selectionStart() == m_FindState.offset &&
cur->selectionEnd() - m_FindState.offset == find.length())
m_FindState.offset += find.length();
}
int start = m_FindState.start + m_FindState.offset;
int end = m_FindState.end;
if(!down)
end = m_FindState.start;
QPair<int, int> result = cur->findText(flags, find.toUtf8().data(), start, end);
m_FindState.prevResult = result;
if(result.first == -1)
{
sptr_t maxOffset = down ? 0 : m_FindState.end;
// if we're at offset 0 searching down, there are no results. Same for offset max and
// searching up
if(m_FindState.offset == maxOffset)
return;
// otherwise, we can wrap the search around
if(ctx == FindReplace::AllFiles)
{
cur = nextScintilla(cur);
ToolWindowManager::raiseToolWindow(cur);
cur->activateWindow();
cur->QWidget::setFocus();
}
m_FindState.offset = maxOffset;
start = m_FindState.start + m_FindState.offset;
end = m_FindState.end;
if(!down)
end = m_FindState.start;
result = cur->findText(flags, find.toUtf8().data(), start, end);
m_FindState.prevResult = result;
if(result.first == -1)
return;
}
cur->setSelection(result.first, result.second);
EnsureLineScrolled(cur, cur->lineFromPosition(result.first));
if(down)
m_FindState.offset = result.second - m_FindState.start;
else
m_FindState.offset = result.first - m_FindState.start;
}
void FindReplace::handleEditorKeypress(ScintillaEdit *editor, QKeyEvent *ev)
{
bool readOnly = editor->readOnly();
if((ev->modifiers() & Qt::ControlModifier) && (ev->key() == Qt::Key_F || ev->key() == Qt::Key_F3))
{
// if there's a selection, fill the find prompt with that
if(!editor->getSelText().isEmpty())
{
setFindText(QString::fromUtf8(editor->getSelText()));
}
else
{
// otherwise pick the word under the cursor, if there is one
sptr_t scintillaPos = editor->currentPos();
sptr_t start = editor->wordStartPosition(scintillaPos, true);
sptr_t end = editor->wordEndPosition(scintillaPos, true);
QByteArray text = editor->textRange(start, end);
if(!text.isEmpty())
setFindText(QString::fromUtf8(text));
}
}
if(ev->key() == Qt::Key_F && (ev->modifiers() & Qt::ControlModifier))
{
setReplaceMode(false);
raiseOrShow();
}
if(ev->key() == Qt::Key_F3)
{
setReplaceMode(false);
performFind((ev->modifiers() & Qt::ShiftModifier) == 0);
}
if(!readOnly && ev->key() == Qt::Key_H && (ev->modifiers() & Qt::ControlModifier))
{
setReplaceMode(true);
raiseOrShow();
}
}
void FindReplace::performFind()
{
performFind(direction() == FindReplace::Down);
}
void FindReplace::performFindAll()
{
ScintillaEdit *cur = currentScintilla();
if(!cur)
return;
clearFindState();
m_FindAllResults.clear();
QString find = findText();
sptr_t flags = 0;
m_FindAllResultString = tr("Find all \"%1\"").arg(find);
if(matchCase())
{
flags |= SCFIND_MATCHCASE;
m_FindAllResultString += tr(", Match case");
}
if(matchWord())
{
flags |= SCFIND_WHOLEWORD;
m_FindAllResultString += tr(", Match whole word");
}
if(regexp())
{
flags |= SCFIND_REGEXP | SCFIND_POSIX;
m_FindAllResultString += tr(", with Regular Expressions");
}
FindReplace::SearchContext ctx = context();
if(ctx == FindReplace::File)
m_FindAllResultString += tr(", in current file\n");
else
m_FindAllResultString += tr(", in all files\n");
QList<ScintillaEdit *> scintillas = m_Scintillas;
if(ctx == FindReplace::File)
scintillas = {cur};
QList<QPair<int, int>> resultList;
QByteArray findUtf8 = find.toUtf8();
if(findUtf8.isEmpty())
return;
for(ScintillaEdit *s : scintillas)
{
sptr_t start = 0;
sptr_t end = s->length();
if(m_FindIndicator >= 0)
{
s->setIndicatorCurrent(m_FindIndicator);
s->indicatorClearRange(start, end);
}
QPair<int, int> result;
do
{
result = s->findText(flags, findUtf8.data(), start, end);
if(result.first >= 0)
{
int line = s->lineFromPosition(result.first);
sptr_t lineStart = s->positionFromLine(line);
sptr_t lineEnd = s->lineEndPosition(line);
if(m_FindIndicator >= 0)
s->indicatorFillRange(result.first, result.second - result.first);
QString lineText = QString::fromUtf8(s->textRange(lineStart, lineEnd));
m_FindAllResultString += QFormatStr(" %1(%2): ").arg(s->windowTitle()).arg(line + 1, 4);
int startPos = m_FindAllResultString.length();
m_FindAllResultString += lineText;
m_FindAllResultString += lit("\n");
resultList.push_back(
qMakePair(result.first - lineStart + startPos, result.second - lineStart + startPos));
m_FindAllResults.push_back({s, result.first});
}
start = result.second;
} while(result.first >= 0);
}
m_FindAllResultString += tr("Matching lines: %1").arg(resultList.count());
if(m_FindResultsDisplay)
{
m_FindResultsDisplay->setReadOnly(false);
m_FindResultsDisplay->setText(m_FindAllResultString.toUtf8().data());
m_FindResultsDisplay->setIndicatorCurrent(INDICATOR_FINDALLCURRENTRESULT);
m_FindResultsDisplay->indicatorClearRange(0, m_FindResultsDisplay->length());
m_FindResultsDisplay->setIndicatorCurrent(INDICATOR_FINDRESULTHIGHLIGHT);
for(QPair<int, int> r : resultList)
m_FindResultsDisplay->indicatorFillRange(r.first, r.second - r.first);
m_FindResultsDisplay->setReadOnly(true);
if(m_FindResultsDisplay->isVisible())
{
if(m_DockManager)
ToolWindowManager::raiseToolWindow(m_FindResultsDisplay);
}
else
{
if(m_DockManager)
{
m_DockManager->moveToolWindow(m_FindResultsDisplay, ToolWindowManager::AreaReference(
ToolWindowManager::BottomOf,
m_DockManager->areaOf(cur), 0.2f));
m_DockManager->setToolWindowProperties(
m_FindResultsDisplay,
ToolWindowManager::HideOnClose | ToolWindowManager::DisallowFloatWindow);
}
}
}
}
void FindReplace::performReplace()
{
ScintillaEdit *cur = currentScintilla();
if(!cur)
return;
QString find = findText();
if(find.isEmpty())
return;
sptr_t flags = 0;
if(matchCase())
flags |= SCFIND_MATCHCASE;
if(matchWord())
flags |= SCFIND_WHOLEWORD;
if(regexp())
flags |= SCFIND_REGEXP | SCFIND_POSIX;
FindReplace::SearchContext ctx = context();
bool down = direction() == FindReplace::Down;
QString findHash = QFormatStr("%1%2%3%4").arg(find).arg(flags).arg((int)ctx).arg(down);
// if we didn't have a valid previous find, just do a find and bail
if(findHash != m_FindState.hash)
{
performFind();
return;
}
if(m_FindState.prevResult.first == -1)
return;
cur->setTargetRange(m_FindState.prevResult.first, m_FindState.prevResult.second);
FindState save = m_FindState;
QString dest = replaceText();
// otherwise we have a valid previous find. Do the replace now
// note this will invalidate the find state (as most user operations would), so we save/restore
// the state
if(regexp())
cur->replaceTargetRE(-1, dest.toUtf8().data());
else
cur->replaceTarget(-1, dest.toUtf8().data());
m_FindState = save;
// adjust the offset if we replaced text and it went up or down in size
m_FindState.offset += (dest.count() - find.count());
// move to the next result
performFind();
}
void FindReplace::performReplaceAll()
{
ScintillaEdit *cur = currentScintilla();
if(!cur)
return;
QString find = findText();
QString replace = replaceText();
if(find.isEmpty())
return;
sptr_t flags = 0;
if(matchCase())
flags |= SCFIND_MATCHCASE;
if(matchWord())
flags |= SCFIND_WHOLEWORD;
if(regexp())
flags |= SCFIND_REGEXP | SCFIND_POSIX;
FindReplace::SearchContext ctx = context();
// trash the find state for any incremental finds
clearFindState();
QList<ScintillaEdit *> scintillas = m_Scintillas;
if(ctx == FindReplace::File)
scintillas = {cur};
int numReplacements = 0;
for(ScintillaEdit *s : scintillas)
{
sptr_t start = 0;
sptr_t end = s->length();
QPair<int, int> result;
QByteArray findUtf8 = find.toUtf8();
QByteArray replaceUtf8 = replace.toUtf8();
do
{
result = s->findText(flags, findUtf8.data(), start, end);
if(result.first >= 0)
{
s->setTargetRange(result.first, result.second);
if(regexp())
s->replaceTargetRE(-1, replaceUtf8.data());
else
s->replaceTarget(-1, replaceUtf8.data());
numReplacements++;
}
start = result.second + (replaceUtf8.count() - findUtf8.count());
} while(result.first >= 0);
}
QString message;
if(scintillas.count() > 1)
message = tr("%1 replacements made in %2 files.").arg(numReplacements).arg(scintillas.count());
else
message = tr("%1 replacements made.").arg(numReplacements);
RDDialog::information(this, tr("Replace all"), message);
}
void FindReplace::resultsDoubleClick(int position, int line)
{
if(line >= 1 && line - 1 < m_FindAllResults.count())
{
m_FindResultsDisplay->setIndicatorCurrent(INDICATOR_FINDALLCURRENTRESULT);
m_FindResultsDisplay->indicatorClearRange(0, m_FindResultsDisplay->length());
sptr_t start = m_FindResultsDisplay->positionFromLine(line);
sptr_t length = m_FindResultsDisplay->lineLength(line);
m_FindResultsDisplay->indicatorFillRange(start, length);
m_FindResultsDisplay->setSelection(position, position);
ScintillaEdit *s = m_FindAllResults[line - 1].first;
int resultPos = m_FindAllResults[line - 1].second;
ToolWindowManager::raiseToolWindow(s);
s->activateWindow();
s->QWidget::setFocus();
s->clearSelections();
s->setSelection(resultPos, resultPos);
s->scrollCaret();
}
}