forked from qt-creator/qt-creator
Core: Add searchable terminal
Change-Id: Id058fb1a97c967fc253b08edf3910f019e708b50 Reviewed-by: Cristian Adam <cristian.adam@qt.io>
This commit is contained in:
@@ -12,7 +12,6 @@ add_qtc_plugin(Terminal
|
||||
terminalpane.cpp terminalpane.h
|
||||
terminalplugin.cpp
|
||||
terminalprocessimpl.cpp terminalprocessimpl.h
|
||||
terminalsearch.cpp terminalsearch.h
|
||||
terminalsettings.cpp terminalsettings.h
|
||||
terminaltr.h
|
||||
terminalwidget.cpp terminalwidget.h
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "terminalsearch.h"
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QLoggingCategory>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
Q_LOGGING_CATEGORY(terminalSearchLog, "qtc.terminal.search", QtWarningMsg)
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace Terminal {
|
||||
|
||||
constexpr std::chrono::milliseconds debounceInterval = 100ms;
|
||||
|
||||
TerminalSearch::TerminalSearch(TerminalSolution::TerminalSurface *surface)
|
||||
: m_surface(surface)
|
||||
{
|
||||
m_debounceTimer.setInterval(debounceInterval);
|
||||
m_debounceTimer.setSingleShot(true);
|
||||
|
||||
connect(surface,
|
||||
&TerminalSolution::TerminalSurface::invalidated,
|
||||
this,
|
||||
&TerminalSearch::updateHits);
|
||||
connect(&m_debounceTimer, &QTimer::timeout, this, &TerminalSearch::debouncedUpdateHits);
|
||||
}
|
||||
|
||||
void TerminalSearch::setCurrentSelection(std::optional<SearchHitWithText> selection)
|
||||
{
|
||||
m_currentSelection = selection;
|
||||
}
|
||||
|
||||
void TerminalSearch::setSearchString(const QString &searchString, FindFlags findFlags)
|
||||
{
|
||||
if (m_currentSearchString != searchString || m_findFlags != findFlags) {
|
||||
m_currentSearchString = searchString;
|
||||
m_findFlags = findFlags;
|
||||
updateHits();
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalSearch::nextHit()
|
||||
{
|
||||
if (m_hits.isEmpty())
|
||||
return;
|
||||
|
||||
m_currentHit = (m_currentHit + 1) % m_hits.size();
|
||||
emit currentHitChanged();
|
||||
}
|
||||
|
||||
void TerminalSearch::previousHit()
|
||||
{
|
||||
if (m_hits.isEmpty())
|
||||
return;
|
||||
|
||||
m_currentHit = (m_currentHit - 1 + m_hits.size()) % m_hits.size();
|
||||
emit currentHitChanged();
|
||||
}
|
||||
|
||||
void TerminalSearch::updateHits()
|
||||
{
|
||||
if (!m_hits.isEmpty()) {
|
||||
m_hits.clear();
|
||||
m_currentHit = -1;
|
||||
emit hitsChanged();
|
||||
emit currentHitChanged();
|
||||
}
|
||||
|
||||
m_debounceTimer.start();
|
||||
}
|
||||
|
||||
bool isSpace(char32_t a, char32_t b)
|
||||
{
|
||||
if (a == std::numeric_limits<char32_t>::max())
|
||||
return std::isspace(b);
|
||||
else if (b == std::numeric_limits<char32_t>::max())
|
||||
return std::isspace(a);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QList<TerminalSolution::SearchHit> TerminalSearch::search()
|
||||
{
|
||||
QList<TerminalSolution::SearchHit> hits;
|
||||
|
||||
std::function<bool(char32_t, char32_t)> compare;
|
||||
|
||||
if (m_findFlags.testFlag(FindFlag::FindCaseSensitively)) {
|
||||
compare = [](char32_t a, char32_t b) { return a == b || isSpace(a, b); };
|
||||
} else {
|
||||
compare = [](char32_t a, char32_t b) {
|
||||
return std::tolower(a) == std::tolower(b) || isSpace(a, b);
|
||||
};
|
||||
}
|
||||
|
||||
if (!m_currentSearchString.isEmpty()) {
|
||||
const QList<uint> asUcs4 = m_currentSearchString.toUcs4();
|
||||
std::u32string searchString(asUcs4.begin(), asUcs4.end());
|
||||
|
||||
if (m_findFlags.testFlag(FindFlag::FindWholeWords)) {
|
||||
searchString.push_back(std::numeric_limits<char32_t>::max());
|
||||
searchString.insert(searchString.begin(), std::numeric_limits<char32_t>::max());
|
||||
}
|
||||
|
||||
TerminalSolution::CellIterator it = m_surface->begin();
|
||||
while (it != m_surface->end()) {
|
||||
it = std::search(it, m_surface->end(), searchString.begin(), searchString.end(), compare);
|
||||
|
||||
if (it != m_surface->end()) {
|
||||
auto hit = TerminalSolution::SearchHit{it.position(),
|
||||
static_cast<int>(it.position() + searchString.size())};
|
||||
if (m_findFlags.testFlag(FindFlag::FindWholeWords)) {
|
||||
hit.start++;
|
||||
hit.end--;
|
||||
}
|
||||
hits << hit;
|
||||
it += m_currentSearchString.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
return hits;
|
||||
}
|
||||
|
||||
QList<TerminalSolution::SearchHit> TerminalSearch::searchRegex()
|
||||
{
|
||||
QList<TerminalSolution::SearchHit> hits;
|
||||
|
||||
QString allText;
|
||||
allText.reserve(1000);
|
||||
|
||||
// Contains offsets at which there are characters > 2 bytes
|
||||
QList<int> adjustTable;
|
||||
|
||||
for (auto it = m_surface->begin(); it != m_surface->end(); ++it) {
|
||||
auto chs = QChar::fromUcs4(*it);
|
||||
if (chs.size() > 1)
|
||||
adjustTable << (allText.size());
|
||||
allText += chs;
|
||||
}
|
||||
|
||||
QRegularExpression re(m_currentSearchString,
|
||||
m_findFlags.testFlag(FindFlag::FindCaseSensitively)
|
||||
? QRegularExpression::NoPatternOption
|
||||
: QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
QRegularExpressionMatchIterator it = re.globalMatch(allText);
|
||||
int adjust = 0;
|
||||
auto itAdjust = adjustTable.begin();
|
||||
while (it.hasNext()) {
|
||||
QRegularExpressionMatch match = it.next();
|
||||
int s = match.capturedStart();
|
||||
int e = match.capturedEnd();
|
||||
|
||||
// Update 'adjust' to account for characters > 2 bytes
|
||||
if (itAdjust != adjustTable.end()) {
|
||||
while (s > *itAdjust && itAdjust != adjustTable.end()) {
|
||||
adjust++;
|
||||
itAdjust++;
|
||||
}
|
||||
s -= adjust;
|
||||
while (e > *itAdjust && itAdjust != adjustTable.end()) {
|
||||
adjust++;
|
||||
itAdjust++;
|
||||
}
|
||||
e -= adjust;
|
||||
}
|
||||
hits << TerminalSolution::SearchHit{s, e};
|
||||
}
|
||||
|
||||
return hits;
|
||||
}
|
||||
|
||||
void TerminalSearch::debouncedUpdateHits()
|
||||
{
|
||||
QElapsedTimer t;
|
||||
t.start();
|
||||
|
||||
m_currentHit = -1;
|
||||
|
||||
const bool regex = m_findFlags.testFlag(FindFlag::FindRegularExpression);
|
||||
|
||||
QList<TerminalSolution::SearchHit> hits = regex ? searchRegex() : search();
|
||||
|
||||
if (hits != m_hits) {
|
||||
m_currentHit = -1;
|
||||
if (m_currentSelection)
|
||||
m_currentHit = hits.indexOf(*m_currentSelection);
|
||||
|
||||
if (m_currentHit == -1 && !hits.isEmpty())
|
||||
m_currentHit = 0;
|
||||
|
||||
m_hits = hits;
|
||||
emit hitsChanged();
|
||||
emit currentHitChanged();
|
||||
emit changed();
|
||||
}
|
||||
if (!m_currentSearchString.isEmpty())
|
||||
qCDebug(terminalSearchLog) << "Search took" << t.elapsed() << "ms";
|
||||
}
|
||||
|
||||
FindFlags TerminalSearch::supportedFindFlags() const
|
||||
{
|
||||
return FindFlag::FindCaseSensitively | FindFlag::FindBackward
|
||||
| FindFlag::FindRegularExpression | FindFlag::FindWholeWords;
|
||||
}
|
||||
|
||||
void TerminalSearch::resetIncrementalSearch()
|
||||
{
|
||||
m_currentSelection.reset();
|
||||
}
|
||||
|
||||
void TerminalSearch::clearHighlights()
|
||||
{
|
||||
setSearchString("", {});
|
||||
}
|
||||
|
||||
QString TerminalSearch::currentFindString() const
|
||||
{
|
||||
if (m_currentSelection)
|
||||
return m_currentSelection->text;
|
||||
else
|
||||
return m_currentSearchString;
|
||||
}
|
||||
|
||||
QString TerminalSearch::completedFindString() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
Core::IFindSupport::Result TerminalSearch::findIncremental(const QString &txt, FindFlags findFlags)
|
||||
{
|
||||
if (txt == m_currentSearchString) {
|
||||
if (m_debounceTimer.isActive())
|
||||
return Result::NotYetFound;
|
||||
else if (m_hits.isEmpty())
|
||||
return Result::NotFound;
|
||||
else
|
||||
return Result::Found;
|
||||
}
|
||||
|
||||
setSearchString(txt, findFlags);
|
||||
return Result::NotYetFound;
|
||||
}
|
||||
|
||||
Core::IFindSupport::Result TerminalSearch::findStep(const QString &txt, FindFlags findFlags)
|
||||
{
|
||||
if (txt == m_currentSearchString) {
|
||||
if (m_debounceTimer.isActive())
|
||||
return Result::NotYetFound;
|
||||
else if (m_hits.isEmpty())
|
||||
return Result::NotFound;
|
||||
|
||||
if (findFlags.testFlag(FindFlag::FindBackward))
|
||||
previousHit();
|
||||
else
|
||||
nextHit();
|
||||
|
||||
return Result::Found;
|
||||
}
|
||||
|
||||
return findIncremental(txt, findFlags);
|
||||
}
|
||||
|
||||
void TerminalSearch::highlightAll(const QString &txt, FindFlags findFlags)
|
||||
{
|
||||
setSearchString(txt, findFlags);
|
||||
}
|
||||
|
||||
} // namespace Terminal
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <terminal/terminalsurface.h>
|
||||
|
||||
#include <solutions/terminal/terminalview.h>
|
||||
|
||||
#include <coreplugin/find/ifindsupport.h>
|
||||
#include <coreplugin/find/textfindconstants.h>
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
namespace Terminal {
|
||||
|
||||
struct SearchHitWithText : TerminalSolution::SearchHit
|
||||
{
|
||||
QString text;
|
||||
};
|
||||
|
||||
class TerminalSearch : public Core::IFindSupport
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TerminalSearch(TerminalSolution::TerminalSurface *surface);
|
||||
|
||||
void setCurrentSelection(std::optional<SearchHitWithText> selection);
|
||||
void setSearchString(const QString &searchString, Utils::FindFlags findFlags);
|
||||
void nextHit();
|
||||
void previousHit();
|
||||
|
||||
const QList<TerminalSolution::SearchHit> &hits() const { return m_hits; }
|
||||
TerminalSolution::SearchHit currentHit() const
|
||||
{
|
||||
return m_currentHit >= 0 ? m_hits.at(m_currentHit) : TerminalSolution::SearchHit{};
|
||||
}
|
||||
|
||||
public:
|
||||
bool supportsReplace() const override { return false; }
|
||||
Utils::FindFlags supportedFindFlags() const override;
|
||||
void resetIncrementalSearch() override;
|
||||
void clearHighlights() override;
|
||||
QString currentFindString() const override;
|
||||
QString completedFindString() const override;
|
||||
Result findIncremental(const QString &txt, Utils::FindFlags findFlags) override;
|
||||
Result findStep(const QString &txt, Utils::FindFlags findFlags) override;
|
||||
|
||||
void highlightAll(const QString &, Utils::FindFlags) override;
|
||||
|
||||
signals:
|
||||
void hitsChanged();
|
||||
void currentHitChanged();
|
||||
|
||||
protected:
|
||||
void updateHits();
|
||||
void debouncedUpdateHits();
|
||||
QList<TerminalSolution::SearchHit> search();
|
||||
QList<TerminalSolution::SearchHit> searchRegex();
|
||||
|
||||
private:
|
||||
std::optional<SearchHitWithText> m_currentSelection;
|
||||
QString m_currentSearchString;
|
||||
Utils::FindFlags m_findFlags;
|
||||
TerminalSolution::TerminalSurface *m_surface;
|
||||
|
||||
int m_currentHit{-1};
|
||||
QList<TerminalSolution::SearchHit> m_hits;
|
||||
QTimer m_debounceTimer;
|
||||
};
|
||||
|
||||
} // namespace Terminal
|
||||
@@ -49,7 +49,7 @@ using namespace Core;
|
||||
|
||||
namespace Terminal {
|
||||
TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &openParameters)
|
||||
: TerminalSolution::TerminalView(parent)
|
||||
: Core::SearchableTerminal(parent)
|
||||
, m_context(Utils::Id("TerminalWidget_").withSuffix(QString::number((uintptr_t) this)))
|
||||
, m_openParameters(openParameters)
|
||||
{
|
||||
@@ -73,10 +73,6 @@ TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &op
|
||||
configBlinkTimer();
|
||||
setAllowBlinkingCursor(settings().allowBlinkingCursor());
|
||||
});
|
||||
|
||||
m_aggregate = new Aggregation::Aggregate(this);
|
||||
m_aggregate->add(this);
|
||||
m_aggregate->add(m_search.get());
|
||||
}
|
||||
|
||||
void TerminalWidget::setupPty()
|
||||
@@ -278,23 +274,11 @@ void TerminalWidget::resizePty(QSize newSize)
|
||||
|
||||
void TerminalWidget::surfaceChanged()
|
||||
{
|
||||
Core::SearchableTerminal::surfaceChanged();
|
||||
|
||||
m_shellIntegration.reset(new ShellIntegration());
|
||||
setSurfaceIntegration(m_shellIntegration.get());
|
||||
|
||||
m_search = TerminalSearchPtr(new TerminalSearch(surface()), [this](TerminalSearch *p) {
|
||||
m_aggregate->remove(p);
|
||||
delete p;
|
||||
});
|
||||
|
||||
connect(m_search.get(), &TerminalSearch::hitsChanged, this, &TerminalWidget::updateViewport);
|
||||
connect(m_search.get(), &TerminalSearch::currentHitChanged, this, [this] {
|
||||
TerminalSolution::SearchHit hit = m_search->currentHit();
|
||||
if (hit.start >= 0) {
|
||||
setSelection(Selection{hit.start, hit.end, true}, hit != m_lastSelectedHit);
|
||||
m_lastSelectedHit = hit;
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_shellIntegration.get(),
|
||||
&ShellIntegration::titleChanged,
|
||||
this,
|
||||
@@ -373,11 +357,6 @@ std::optional<TerminalSolution::TerminalView::Link> TerminalWidget::toLink(const
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QList<TerminalSolution::SearchHit> &TerminalWidget::searchHits() const
|
||||
{
|
||||
return m_search->hits();
|
||||
}
|
||||
|
||||
void TerminalWidget::onReadyRead(bool forceFlush)
|
||||
{
|
||||
QByteArray data = m_process->readAllRawStandardOutput();
|
||||
@@ -429,6 +408,8 @@ void TerminalWidget::restart(const OpenTerminalParameters &openParameters)
|
||||
|
||||
void TerminalWidget::selectionChanged(const std::optional<Selection> &newSelection)
|
||||
{
|
||||
Q_UNUSED(newSelection);
|
||||
|
||||
updateCopyState();
|
||||
|
||||
if (selection() && selection()->final) {
|
||||
@@ -437,9 +418,6 @@ void TerminalWidget::selectionChanged(const std::optional<Selection> &newSelecti
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
if (clipboard->supportsSelection())
|
||||
clipboard->setText(text, QClipboard::Selection);
|
||||
|
||||
m_search->setCurrentSelection(
|
||||
SearchHitWithText{{newSelection->start, newSelection->end}, text});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,12 @@
|
||||
|
||||
#include "shellintegration.h"
|
||||
#include "shortcutmap.h"
|
||||
#include "terminalsearch.h"
|
||||
|
||||
#include <solutions/terminal/terminalview.h>
|
||||
|
||||
#include <aggregation/aggregate.h>
|
||||
|
||||
#include <coreplugin/actionmanager/command.h>
|
||||
#include <coreplugin/icontext.h>
|
||||
#include <coreplugin/terminal/searchableterminal.h>
|
||||
|
||||
#include <utils/link.h>
|
||||
#include <utils/process.h>
|
||||
@@ -22,7 +20,7 @@ namespace Terminal {
|
||||
|
||||
using RegisteredAction = std::unique_ptr<QAction, std::function<void(QAction *)>>;
|
||||
|
||||
class TerminalWidget : public TerminalSolution::TerminalView
|
||||
class TerminalWidget : public Core::SearchableTerminal
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -31,8 +29,6 @@ public:
|
||||
|
||||
void closeTerminal();
|
||||
|
||||
TerminalSearch *search() { return m_search.get(); }
|
||||
|
||||
void setShellName(const QString &shellName);
|
||||
QString shellName() const;
|
||||
QString title() const;
|
||||
@@ -80,8 +76,6 @@ protected:
|
||||
void setClipboard(const QString &text) override;
|
||||
std::optional<TerminalView::Link> toLink(const QString &text) override;
|
||||
|
||||
const QList<TerminalSolution::SearchHit> &searchHits() const override;
|
||||
|
||||
RegisteredAction registerAction(Utils::Id commandId, const Core::Context &context);
|
||||
void registerShortcut(Core::Command *command);
|
||||
|
||||
@@ -95,8 +89,6 @@ private:
|
||||
QString m_shellName;
|
||||
QString m_title;
|
||||
|
||||
TerminalSolution::SearchHit m_lastSelectedHit{};
|
||||
|
||||
Utils::Id m_identifier;
|
||||
|
||||
Utils::Terminal::OpenTerminalParameters m_openParameters;
|
||||
@@ -104,11 +96,6 @@ private:
|
||||
Utils::FilePath m_cwd;
|
||||
Utils::CommandLine m_currentCommand;
|
||||
|
||||
using TerminalSearchPtr = std::unique_ptr<TerminalSearch, std::function<void(TerminalSearch *)>>;
|
||||
TerminalSearchPtr m_search;
|
||||
|
||||
Aggregation::Aggregate *m_aggregate{nullptr};
|
||||
|
||||
RegisteredAction m_copy;
|
||||
RegisteredAction m_paste;
|
||||
RegisteredAction m_clearSelection;
|
||||
|
||||
Reference in New Issue
Block a user