Terminal: Add search

* Adds new search widget to terminal
* Adds new theme color "TerminalFindMatch"
* Fixes ESC key handling in terminal

Fixes: QTCREATORBUG-28946
Change-Id: I7b6057d13902a94a6bcd41dde6cc8ba8418cd585
Reviewed-by: Cristian Adam <cristian.adam@qt.io>
This commit is contained in:
Marcus Tillmanns
2023-03-24 12:53:21 +01:00
parent 884a1d6f94
commit 9417f8b99e
25 changed files with 686 additions and 143 deletions

View File

@@ -7,6 +7,10 @@
#include "terminalsettings.h"
#include "terminalsurface.h"
#include <aggregation/aggregate.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/fileutils.h>
#include <coreplugin/icore.h>
@@ -48,6 +52,15 @@ using namespace Utils::Terminal;
namespace Terminal {
namespace ColorIndex {
enum Indices {
Foreground = Internal::ColorIndex::Foreground,
Background = Internal::ColorIndex::Background,
Selection,
FindMatch,
};
}
using namespace std::chrono_literals;
// Minimum time between two refreshes. (30fps)
@@ -72,7 +85,7 @@ TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &op
m_cursorBlinkState = !m_cursorBlinkState;
else
m_cursorBlinkState = true;
updateViewport(gridToViewport(QRect{m_cursor.position, m_cursor.position}));
updateViewportRect(gridToViewport(QRect{m_cursor.position, m_cursor.position}));
});
setAttribute(Qt::WA_InputMethodEnabled);
@@ -107,6 +120,18 @@ TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &op
setupFont();
configBlinkTimer();
});
m_aggregate = new Aggregation::Aggregate(this);
m_aggregate->add(this);
m_aggregate->add(m_search.get());
}
TerminalWidget::~TerminalWidget()
{
// The Aggregate stuff tries to do clever deletion of the children, but we
// we don't want that.
m_aggregate->remove(this);
m_aggregate->remove(m_search.get());
}
void TerminalWidget::setupPty()
@@ -203,12 +228,14 @@ void TerminalWidget::setupFont()
void TerminalWidget::setupColors()
{
// Check if the colors have changed.
std::array<QColor, 18> newColors;
std::array<QColor, 20> newColors;
for (int i = 0; i < 16; ++i) {
newColors[i] = TerminalSettings::instance().colors[i].value();
}
newColors[16] = TerminalSettings::instance().foregroundColor.value();
newColors[17] = TerminalSettings::instance().backgroundColor.value();
newColors[ColorIndex::Background] = TerminalSettings::instance().backgroundColor.value();
newColors[ColorIndex::Foreground] = TerminalSettings::instance().foregroundColor.value();
newColors[ColorIndex::Selection] = TerminalSettings::instance().selectionColor.value();
newColors[ColorIndex::FindMatch] = TerminalSettings::instance().findMatchColor.value();
if (m_currentColors == newColors)
return;
@@ -250,6 +277,16 @@ void TerminalWidget::setupSurface()
{
m_shellIntegration.reset(new ShellIntegration());
m_surface = std::make_unique<Internal::TerminalSurface>(QSize{80, 60}, m_shellIntegration.get());
m_search = std::make_unique<TerminalSearch>(m_surface.get());
connect(m_search.get(), &TerminalSearch::hitsChanged, this, &TerminalWidget::updateViewport);
connect(m_search.get(), &TerminalSearch::currentHitChanged, this, [this] {
SearchHit hit = m_search->currentHit();
if (hit.start >= 0) {
setSelection(Selection{hit.start, hit.end, true}, hit != m_lastSelectedHit);
m_lastSelectedHit = hit;
}
});
connect(m_surface.get(),
&Internal::TerminalSurface::writeToPty,
@@ -263,7 +300,8 @@ void TerminalWidget::setupSurface()
this,
[this](const QRect &rect) {
setSelection(std::nullopt);
updateViewport(gridToViewport(rect));
updateViewportRect(gridToViewport(rect));
verticalScrollBar()->setValue(m_surface->fullSize().height());
});
connect(m_surface.get(),
&Internal::TerminalSurface::cursorChanged,
@@ -282,7 +320,8 @@ void TerminalWidget::setupSurface()
m_cursor = newCursor;
updateViewport(gridToViewport(QRect{QPoint{startX, startY}, QPoint{endX, endY}}));
updateViewportRect(
gridToViewport(QRect{QPoint{startX, startY}, QPoint{endX, endY}}));
configBlinkTimer();
});
connect(m_surface.get(), &Internal::TerminalSurface::altscreenChanged, this, [this] {
@@ -330,7 +369,7 @@ QColor TerminalWidget::toQColor(std::variant<int, QColor> color) const
if (idx >= 0 && idx < 18)
return m_currentColors[idx];
return m_currentColors[Internal::ColorIndex::Background];
return m_currentColors[ColorIndex::Background];
}
return std::get<QColor>(color);
}
@@ -460,24 +499,34 @@ QString TerminalWidget::textFromSelection() const
Internal::CellIterator end = m_surface->iteratorAt(m_selection->end);
std::u32string s;
bool previousWasZero = false;
for (; it != end; ++it) {
if (it.gridPos().x() == 0 && !s.empty())
if (it.gridPos().x() == 0 && !s.empty() && previousWasZero)
s += U'\n';
if (*it != 0)
if (*it != 0) {
previousWasZero = false;
s += *it;
} else {
previousWasZero = true;
}
}
return QString::fromUcs4(s.data(), static_cast<int>(s.size()));
}
bool TerminalWidget::setSelection(const std::optional<Selection> &selection)
bool TerminalWidget::setSelection(const std::optional<Selection> &selection, bool scroll)
{
qCDebug(selectionLog) << "setSelection" << selection.has_value();
if (selection.has_value())
qCDebug(selectionLog) << "start:" << selection->start << "end:" << selection->end
<< "final:" << selection->final;
if (selectionLog().isDebugEnabled())
updateViewport();
if (selection == m_selection) {
if (selection == m_selection)
return false;
}
m_selection = selection;
@@ -485,13 +534,25 @@ bool TerminalWidget::setSelection(const std::optional<Selection> &selection)
if (m_selection && m_selection->final) {
qCDebug(selectionLog) << "Copy enabled:" << selection.has_value();
QString text = textFromSelection();
QClipboard *clipboard = QApplication::clipboard();
if (clipboard->supportsSelection()) {
QString text = textFromSelection();
qCDebug(selectionLog) << "Selection set to clipboard: " << text;
clipboard->setText(text, QClipboard::Selection);
}
if (scroll) {
QPoint start = m_surface->posToGrid(m_selection->start);
QPoint end = m_surface->posToGrid(m_selection->end);
QRect viewRect = gridToViewport(QRect{start, end});
if (viewRect.y() >= viewport()->height() || viewRect.y() < 0) {
// Selection is outside of the viewport, scroll to it.
verticalScrollBar()->setValue(start.y());
}
}
m_search->setCurrentSelection(SearchHitWithText{{selection->start, selection->end}, text});
}
if (!selectionLog().isDebugEnabled())
@@ -727,32 +788,64 @@ static void drawTextItemDecoration(QPainter &painter,
painter.setBrush(oldBrush);
}
void TerminalWidget::paintSelectionOrBackground(QPainter &p,
const Internal::TerminalCell &cell,
const QRectF &cellRect,
const QPoint gridPos) const
bool TerminalWidget::paintFindMatches(QPainter &p,
QList<SearchHit>::const_iterator &it,
const QRectF &cellRect,
const QPoint gridPos) const
{
if (it == m_search->hits().constEnd())
return false;
const int pos = m_surface->gridToPos(gridPos);
while (it != m_search->hits().constEnd()) {
if (pos < it->start)
return false;
if (pos >= it->end) {
++it;
continue;
}
break;
}
if (it == m_search->hits().constEnd())
return false;
p.fillRect(cellRect, m_currentColors[ColorIndex::FindMatch]);
return true;
}
bool TerminalWidget::paintSelection(QPainter &p, const QRectF &cellRect, const QPoint gridPos) const
{
bool isInSelection = false;
const int pos = m_surface->gridToPos(gridPos);
if (m_selection) {
const int pos = m_surface->gridToPos(gridPos);
isInSelection = pos >= m_selection->start && pos < m_selection->end;
}
if (isInSelection)
p.fillRect(cellRect, TerminalSettings::instance().selectionColor.value());
else if (!(std::holds_alternative<int>(cell.backgroundColor)
&& std::get<int>(cell.backgroundColor) == 17))
p.fillRect(cellRect, toQColor(cell.backgroundColor));
if (isInSelection) {
p.fillRect(cellRect, m_currentColors[ColorIndex::Selection]);
}
return isInSelection;
}
int TerminalWidget::paintCell(QPainter &p,
const QRectF &cellRect,
QPoint gridPos,
const Internal::TerminalCell &cell,
QFont &f) const
QFont &f,
QList<SearchHit>::const_iterator &searchIt) const
{
paintSelectionOrBackground(p, cell, cellRect, gridPos);
bool paintBackground = !paintSelection(p, cellRect, gridPos)
&& !paintFindMatches(p, searchIt, cellRect, gridPos);
bool isDefaultBg = std::holds_alternative<int>(cell.backgroundColor)
&& std::get<int>(cell.backgroundColor) == 17;
if (paintBackground && !isDefaultBg)
p.fillRect(cellRect, toQColor(cell.backgroundColor));
p.setPen(toQColor(cell.foregroundColor));
@@ -865,6 +958,14 @@ void TerminalWidget::paintCells(QPainter &p, QPaintEvent *event) const
qCeil((event->rect().y() + event->rect().height()) / m_cellSize.height())
+ scrollOffset);
QList<SearchHit>::const_iterator searchIt
= std::lower_bound(m_search->hits().constBegin(),
m_search->hits().constEnd(),
startRow,
[this](const SearchHit &hit, int value) {
return m_surface->posToGrid(hit.start).y() < value;
});
for (int cellY = startRow; cellY < endRow; ++cellY) {
for (int cellX = 0; cellX < m_surface->liveSize().width();) {
const auto cell = m_surface->fetchCell(cellX, cellY);
@@ -872,7 +973,7 @@ void TerminalWidget::paintCells(QPainter &p, QPaintEvent *event) const
QRectF cellRect(gridToGlobal({cellX, cellY}),
QSizeF{m_cellSize.width() * cell.width, m_cellSize.height()});
int numCells = paintCell(p, cellRect, {cellX, cellY}, cell, f);
int numCells = paintCell(p, cellRect, {cellX, cellY}, cell, f, searchIt);
cellX += numCells;
}
@@ -907,7 +1008,7 @@ void TerminalWidget::paintEvent(QPaintEvent *event)
if (paintLog().isDebugEnabled())
p.fillRect(event->rect(), QColor::fromRgb(rand() % 60, rand() % 60, rand() % 60));
else
p.fillRect(event->rect(), m_currentColors[Internal::ColorIndex::Background]);
p.fillRect(event->rect(), m_currentColors[ColorIndex::Background]);
int scrollOffset = verticalScrollBar()->value();
int offset = -(scrollOffset * m_cellSize.height());
@@ -923,7 +1024,7 @@ void TerminalWidget::paintEvent(QPaintEvent *event)
p.restore();
p.fillRect(QRectF{{0, 0}, QSizeF{(qreal) width(), topMargin()}},
m_currentColors[Internal::ColorIndex::Background]);
m_currentColors[ColorIndex::Background]);
if (selectionLog().isDebugEnabled()) {
if (m_selection)
@@ -946,8 +1047,20 @@ void TerminalWidget::keyPressEvent(QKeyEvent *event)
m_cursorBlinkState = true;
}
if (event->key() == Qt::Key_Escape) {
if (m_selection)
TerminalCommands::widgetActions().clearSelection.trigger();
else {
QTC_ASSERT(Core::ActionManager::command(Core::Constants::S_RETURNTOEDITOR), return);
Core::ActionManager::command(Core::Constants::S_RETURNTOEDITOR)->action()->trigger();
}
return;
}
auto oldSelection = m_selection;
if (TerminalCommands::triggerAction(event)) {
setSelection(std::nullopt);
if (oldSelection && oldSelection == m_selection)
setSelection(std::nullopt);
return;
}
@@ -1023,7 +1136,7 @@ void TerminalWidget::updateViewport()
viewport()->update();
}
void TerminalWidget::updateViewport(const QRect &rect)
void TerminalWidget::updateViewportRect(const QRect &rect)
{
viewport()->update(rect);
}
@@ -1297,7 +1410,7 @@ bool TerminalWidget::event(QEvent *event)
if (event->type() == QEvent::Paint) {
QPainter p(this);
p.fillRect(QRect(QPoint(0, 0), size()), m_currentColors[Internal::ColorIndex::Background]);
p.fillRect(QRect(QPoint(0, 0), size()), m_currentColors[ColorIndex::Background]);
return true;
}