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

@@ -387,6 +387,7 @@ QmlDesigner_ScrollBarHandleColor=ff505050
TerminalForeground=ffffffff TerminalForeground=ffffffff
TerminalBackground=ff000000 TerminalBackground=ff000000
TerminalSelection=7fffffff TerminalSelection=7fffffff
TerminalFindMatch=7fffff00
TerminalAnsi0=000000 TerminalAnsi0=000000
TerminalAnsi1=8b1b10 TerminalAnsi1=8b1b10
TerminalAnsi2=4aa32e TerminalAnsi2=4aa32e

View File

@@ -355,6 +355,7 @@ QmlDesigner_ScrollBarHandleColor=ff7a7a7a
TerminalForeground=ff000000 TerminalForeground=ff000000
TerminalBackground=ffffffff TerminalBackground=ffffffff
TerminalSelection=3f000000 TerminalSelection=3f000000
TerminalFindMatch=7fffff00
TerminalAnsi0=000000 TerminalAnsi0=000000
TerminalAnsi1=8b1b10 TerminalAnsi1=8b1b10
TerminalAnsi2=4aa32e TerminalAnsi2=4aa32e

View File

@@ -399,6 +399,7 @@ PaletteTextDisabled=textDisabled
TerminalForeground=ff000000 TerminalForeground=ff000000
TerminalBackground=ffffffff TerminalBackground=ffffffff
TerminalSelection=3f000000 TerminalSelection=3f000000
TerminalFindMatch=7fffff00
TerminalAnsi0=000000 TerminalAnsi0=000000
TerminalAnsi1=8b1b10 TerminalAnsi1=8b1b10
TerminalAnsi2=4aa32e TerminalAnsi2=4aa32e

View File

@@ -499,6 +499,7 @@ PalettePlaceholderText=ff808081
TerminalForeground=ffffffff TerminalForeground=ffffffff
TerminalBackground=ff000000 TerminalBackground=ff000000
TerminalSelection=7fffffff TerminalSelection=7fffffff
TerminalFindMatch=7fffff00
TerminalAnsi0=000000 TerminalAnsi0=000000
TerminalAnsi1=8b1b10 TerminalAnsi1=8b1b10
TerminalAnsi2=4aa32e TerminalAnsi2=4aa32e

View File

@@ -391,6 +391,7 @@ PalettePlaceholderText=ff7f7f80
TerminalForeground=ffffffff TerminalForeground=ffffffff
TerminalBackground=ff000000 TerminalBackground=ff000000
TerminalSelection=7fffffff TerminalSelection=7fffffff
TerminalFindMatch=7fffff00
TerminalAnsi0=000000 TerminalAnsi0=000000
TerminalAnsi1=8b1b10 TerminalAnsi1=8b1b10
TerminalAnsi2=4aa32e TerminalAnsi2=4aa32e

View File

@@ -364,6 +364,7 @@ QmlDesigner_ScrollBarHandleColor=ffcccccc
TerminalForeground=ff000000 TerminalForeground=ff000000
TerminalBackground=ffffffff TerminalBackground=ffffffff
TerminalSelection=3f000000 TerminalSelection=3f000000
TerminalFindMatch=7fffff00
TerminalAnsi0=000000 TerminalAnsi0=000000
TerminalAnsi1=8b1b10 TerminalAnsi1=8b1b10
TerminalAnsi2=4aa32e TerminalAnsi2=4aa32e

View File

@@ -362,6 +362,7 @@ QmlDesigner_ScrollBarHandleColor=ff595b5c
TerminalForeground=ff000000 TerminalForeground=ff000000
TerminalBackground=ffffffff TerminalBackground=ffffffff
TerminalSelection=3f000000 TerminalSelection=3f000000
TerminalFindMatch=7fffff00
TerminalAnsi0=000000 TerminalAnsi0=000000
TerminalAnsi1=8b1b10 TerminalAnsi1=8b1b10
TerminalAnsi2=4aa32e TerminalAnsi2=4aa32e

View File

@@ -423,6 +423,7 @@ public:
TerminalForeground, TerminalForeground,
TerminalBackground, TerminalBackground,
TerminalSelection, TerminalSelection,
TerminalFindMatch,
TerminalAnsi0, TerminalAnsi0,
TerminalAnsi1, TerminalAnsi1,

View File

@@ -14,6 +14,7 @@ add_qtc_plugin(Terminal
terminalpane.cpp terminalpane.h terminalpane.cpp terminalpane.h
terminalplugin.cpp terminalplugin.h terminalplugin.cpp terminalplugin.h
terminalprocessimpl.cpp terminalprocessimpl.h terminalprocessimpl.cpp terminalprocessimpl.h
terminalsearch.cpp terminalsearch.h
terminalsettings.cpp terminalsettings.h terminalsettings.cpp terminalsettings.h
terminalsettingspage.cpp terminalsettingspage.h terminalsettingspage.cpp terminalsettingspage.h
terminalsurface.cpp terminalsurface.h terminalsurface.cpp terminalsurface.h

View File

@@ -48,10 +48,11 @@ QPoint CellIterator::gridPos() const
return m_surface->posToGrid(m_pos); return m_surface->posToGrid(m_pos);
} }
void CellIterator::updateChar() bool CellIterator::updateChar()
{ {
QPoint cell = m_surface->posToGrid(m_pos); QPoint cell = m_surface->posToGrid(m_pos);
m_char = m_surface->fetchCharAt(cell.x(), cell.y()); m_char = m_surface->fetchCharAt(cell.x(), cell.y());
return m_char != 0;
} }
CellIterator &CellIterator::operator-=(int n) CellIterator &CellIterator::operator-=(int n)
@@ -63,7 +64,9 @@ CellIterator &CellIterator::operator-=(int n)
throw new std::runtime_error("-= n too big!"); throw new std::runtime_error("-= n too big!");
m_pos -= n; m_pos -= n;
updateChar();
while (!updateChar() && m_pos > 0 && m_skipZeros)
m_pos--;
m_state = State::INSIDE; m_state = State::INSIDE;
@@ -79,10 +82,14 @@ CellIterator &CellIterator::operator+=(int n)
if (n == 0) if (n == 0)
return *this; return *this;
if (m_pos + n < m_maxpos) { if (m_pos + n < m_maxpos + 1) {
m_state = State::INSIDE; m_state = State::INSIDE;
m_pos += n; m_pos += n;
updateChar(); while (!updateChar() && m_pos < (m_maxpos + 1) && m_skipZeros)
m_pos++;
if (m_pos == m_maxpos + 1)
m_state = State::END;
} else { } else {
*this = m_surface->end(); *this = m_surface->end();
} }

View File

@@ -82,13 +82,15 @@ public:
} }
int position() const { return m_pos; } int position() const { return m_pos; }
void setSkipZeros(bool skipZeros) { m_skipZeros = skipZeros; }
private: private:
void updateChar(); bool updateChar();
const TerminalSurface *m_surface{nullptr}; const TerminalSurface *m_surface{nullptr};
int m_pos{-1}; int m_pos{-1};
int m_maxpos{-1}; int m_maxpos{-1};
bool m_skipZeros{false};
mutable std::u32string::value_type m_char; mutable std::u32string::value_type m_char;
}; };

View File

@@ -30,6 +30,8 @@ QtcPlugin {
"terminalplugin.h", "terminalplugin.h",
"terminalprocessimpl.cpp", "terminalprocessimpl.cpp",
"terminalprocessimpl.h", "terminalprocessimpl.h",
"terminalsearch.cpp",
"terminalsearch.h",
"terminalsettings.cpp", "terminalsettings.cpp",
"terminalsettings.h", "terminalsettings.h",
"terminalsettingspage.cpp", "terminalsettingspage.cpp",

View File

@@ -2,9 +2,11 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "terminalcommands.h" #include "terminalcommands.h"
#include "terminaltr.h"
#include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/coreconstants.h> #include <coreplugin/coreconstants.h>
#include <coreplugin/find/textfindconstants.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
@@ -37,81 +39,87 @@ TerminalCommands::TerminalCommands() {}
void TerminalCommands::init(const Core::Context &context) void TerminalCommands::init(const Core::Context &context)
{ {
initWidgetActions(context); m_context = context;
initPaneActions(context); initWidgetActions();
initPaneActions();
initGlobalCommands(); initGlobalCommands();
} }
void TerminalCommands::initWidgetActions(const Core::Context &context) void TerminalCommands::registerAction(QAction &action,
const Utils::Id &id,
QList<QKeySequence> shortCuts)
{ {
Command *command = ActionManager::instance()->registerAction(&m_widgetActions.copy, Command *cmd = ActionManager::instance()->registerAction(&action, id, m_context);
COPY, cmd->setKeySequences(shortCuts);
context); m_commands.push_back(cmd);
command->setDefaultKeySequences(
{QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+C")
: QLatin1String("Ctrl+Shift+C")),
QKeySequence(Qt::Key_Return)});
m_commands.push_back(command);
command = ActionManager::instance()->registerAction(&m_widgetActions.paste, PASTE, context);
command->setDefaultKeySequence(QKeySequence(
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+V") : QLatin1String("Ctrl+Shift+V")));
m_commands.push_back(command);
command = ActionManager::instance()->registerAction(&m_widgetActions.clearSelection,
CLEARSELECTION);
command->setDefaultKeySequence(QKeySequence("Esc"));
m_commands.push_back(command);
command = ActionManager::instance()->registerAction(&m_widgetActions.moveCursorWordLeft,
MOVECURSORWORDLEFT);
command->setDefaultKeySequence(QKeySequence("Alt+Left"));
m_commands.push_back(command);
command = ActionManager::instance()->registerAction(&m_widgetActions.moveCursorWordRight,
MOVECURSORWORDRIGHT);
command->setDefaultKeySequence(QKeySequence("Alt+Right"));
m_commands.push_back(command);
} }
void TerminalCommands::initPaneActions(const Core::Context &context) void TerminalCommands::initWidgetActions()
{ {
Command *command = ActionManager::instance()->registerAction(&m_paneActions.newTerminal, m_widgetActions.copy.setText(Tr::tr("Copy"));
NEWTERMINAL, m_widgetActions.paste.setText(Tr::tr("Paste"));
context); m_widgetActions.clearSelection.setText(Tr::tr("Clear Selection"));
command->setDefaultKeySequence(QKeySequence( m_widgetActions.clearTerminal.setText(Tr::tr("Clear Terminal"));
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+T") : QLatin1String("Ctrl+Shift+T"))); m_widgetActions.moveCursorWordLeft.setText(Tr::tr("Move Cursor Word Left"));
m_commands.push_back(command); m_widgetActions.moveCursorWordRight.setText(Tr::tr("Move Cursor Word Right"));
m_widgetActions.findNext.setText(Tr::tr("Find Next"));
m_widgetActions.findPrevious.setText(Tr::tr("Find Previous"));
command = ActionManager::instance()->registerAction(&m_paneActions.closeTerminal, registerAction(m_widgetActions.copy,
CLOSETERMINAL, COPY,
context); {QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+C")
command->setDefaultKeySequence(QKeySequence( : QLatin1String("Ctrl+Shift+C"))});
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+W") : QLatin1String("Ctrl+Shift+W")));
m_commands.push_back(command);
command = ActionManager::instance()->registerAction(&m_paneActions.nextTerminal, registerAction(m_widgetActions.paste,
NEXTTERMINAL, PASTE,
context); {QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+V")
command->setDefaultKeySequences( : QLatin1String("Ctrl+Shift+V"))});
{QKeySequence("ALT+TAB"),
QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Shift+[")
: QLatin1String("Ctrl+PgUp"))});
m_commands.push_back(command);
command = ActionManager::instance()->registerAction(&m_paneActions.prevTerminal, registerAction(m_widgetActions.clearSelection, CLEARSELECTION);
PREVTERMINAL,
context);
command->setDefaultKeySequences(
{QKeySequence("ALT+SHIFT+TAB"),
QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Shift+]")
: QLatin1String("Ctrl+PgDown"))});
m_commands.push_back(command);
command = ActionManager::instance()->registerAction(&m_paneActions.minMax, MINMAX, context); registerAction(m_widgetActions.moveCursorWordLeft,
command->setDefaultKeySequence(QKeySequence( MOVECURSORWORDLEFT,
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Return") : QLatin1String("Alt+Return"))); {QKeySequence("Alt+Left")});
m_commands.push_back(command);
registerAction(m_widgetActions.moveCursorWordRight,
MOVECURSORWORDRIGHT,
{QKeySequence("Alt+Right")});
}
void TerminalCommands::initPaneActions()
{
m_paneActions.newTerminal.setText(Tr::tr("New Terminal"));
m_paneActions.closeTerminal.setText(Tr::tr("Close Terminal"));
m_paneActions.nextTerminal.setText(Tr::tr("Next Terminal"));
m_paneActions.prevTerminal.setText(Tr::tr("Previous Terminal"));
m_paneActions.minMax.setText(Tr::tr("Minimize/Maximize Terminal"));
registerAction(m_paneActions.newTerminal,
NEWTERMINAL,
{QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+T")
: QLatin1String("Ctrl+Shift+T"))});
registerAction(m_paneActions.closeTerminal,
CLOSETERMINAL,
{QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+W")
: QLatin1String("Ctrl+Shift+W"))});
registerAction(m_paneActions.nextTerminal,
NEXTTERMINAL,
{QKeySequence("ALT+TAB"),
QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Shift+[")
: QLatin1String("Ctrl+PgUp"))});
registerAction(m_paneActions.prevTerminal,
PREVTERMINAL,
{QKeySequence("ALT+SHIFT+TAB"),
QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Shift+]")
: QLatin1String("Ctrl+PgDown"))});
registerAction(m_paneActions.minMax,
MINMAX,
{QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Return")
: QLatin1String("Alt+Return"))});
} }
void TerminalCommands::initGlobalCommands() void TerminalCommands::initGlobalCommands()
@@ -153,13 +161,20 @@ QAction *TerminalCommands::openSettingsAction()
return ActionManager::command("Preferences.Terminal.General")->action(); return ActionManager::command("Preferences.Terminal.General")->action();
} }
void TerminalCommands::registerOpenCloseTerminalPaneCommand() void TerminalCommands::lazyInitCommand(const Utils::Id &id)
{ {
Command* terminalCmd = ActionManager::command("QtCreator.Pane.Terminal"); Command *cmd = ActionManager::command(id);
QTC_ASSERT(terminalCmd, return); QTC_ASSERT(cmd, return);
m_commands.append(cmd);
}
if (!m_commands.contains(terminalCmd)) void TerminalCommands::lazyInitCommands()
m_commands.append(terminalCmd); {
static const Utils::Id terminalPaneCmd("QtCreator.Pane.Terminal");
lazyInitCommand(terminalPaneCmd);
lazyInitCommand(Core::Constants::FIND_IN_DOCUMENT);
lazyInitCommand(Core::Constants::FIND_NEXT);
lazyInitCommand(Core::Constants::FIND_PREVIOUS);
} }
} // namespace Terminal } // namespace Terminal

View File

@@ -3,6 +3,10 @@
#pragma once #pragma once
#include <utils/id.h>
#include <coreplugin/icontext.h>
#include <QAction> #include <QAction>
#include <QCoreApplication> #include <QCoreApplication>
#include <QKeyEvent> #include <QKeyEvent>
@@ -10,29 +14,29 @@
namespace Core { namespace Core {
class Command; class Command;
class Context; class Context;
} } // namespace Core
namespace Terminal { namespace Terminal {
struct WidgetActions struct WidgetActions
{ {
QAction copy{QCoreApplication::translate("QtC::Terminal", "Copy")}; QAction copy;
QAction paste{QCoreApplication::translate("QtC::Terminal", "Paste")}; QAction paste;
QAction clearSelection{QCoreApplication::translate("QtC::Terminal", "Clear Selection")}; QAction clearSelection;
QAction clearTerminal{QCoreApplication::translate("QtC::Terminal", "Clear Terminal")}; QAction clearTerminal;
QAction moveCursorWordLeft{QCoreApplication::translate("QtC::Terminal", QAction moveCursorWordLeft;
"Move Cursor Word Left")}; QAction moveCursorWordRight;
QAction moveCursorWordRight{QCoreApplication::translate("QtC::Terminal", QAction findNext;
"Move Cursor Word Right")}; QAction findPrevious;
}; };
struct PaneActions struct PaneActions
{ {
QAction newTerminal{QCoreApplication::translate("QtC::Terminal", "New Terminal")}; QAction newTerminal;
QAction closeTerminal{QCoreApplication::translate("QtC::Terminal", "Close Terminal")}; QAction closeTerminal;
QAction nextTerminal{QCoreApplication::translate("QtC::Terminal", "Next Terminal")}; QAction nextTerminal;
QAction prevTerminal{QCoreApplication::translate("QtC::Terminal", "Previous Terminal")}; QAction prevTerminal;
QAction minMax{QCoreApplication::translate("QtC::Terminal", "Minimize/Maximize Terminal")}; QAction minMax;
}; };
class TerminalCommands class TerminalCommands
@@ -51,17 +55,21 @@ public:
static QAction *openSettingsAction(); static QAction *openSettingsAction();
void registerOpenCloseTerminalPaneCommand(); void lazyInitCommands();
protected: protected:
void initWidgetActions(const Core::Context &context); void initWidgetActions();
void initPaneActions(const Core::Context &context); void initPaneActions();
void initGlobalCommands(); void initGlobalCommands();
void lazyInitCommand(const Utils::Id &id);
void registerAction(QAction &action, const Utils::Id &id, QList<QKeySequence> shortcuts = {});
private: private:
WidgetActions m_widgetActions; WidgetActions m_widgetActions;
PaneActions m_paneActions; PaneActions m_paneActions;
QList<Core::Command *> m_commands; QList<Core::Command *> m_commands;
Core::Context m_context;
}; };
} // namespace Terminal } // namespace Terminal

View File

@@ -33,6 +33,8 @@ TerminalPane::TerminalPane(QObject *parent)
, m_tabWidget(new QTabWidget) , m_tabWidget(new QTabWidget)
{ {
setupContext("Terminal.Pane", m_tabWidget); setupContext("Terminal.Pane", m_tabWidget);
setZoomButtonsEnabled(true);
TerminalCommands::instance().init(Core::Context("Terminal.Pane")); TerminalCommands::instance().init(Core::Context("Terminal.Pane"));
connect(this, &IOutputPane::zoomInRequested, this, [this] { connect(this, &IOutputPane::zoomInRequested, this, [this] {
@@ -47,16 +49,18 @@ TerminalPane::TerminalPane(QObject *parent)
QAction &newTerminal = TerminalCommands::instance().paneActions().newTerminal; QAction &newTerminal = TerminalCommands::instance().paneActions().newTerminal;
QAction &closeTerminal = TerminalCommands::instance().paneActions().closeTerminal; QAction &closeTerminal = TerminalCommands::instance().paneActions().closeTerminal;
newTerminal.setIcon(Icon({ newTerminal.setIcon(
{":/terminal/images/terminal.png", Theme::IconsBaseColor}, Icon({{":/terminal/images/terminal.png", Theme::IconsBaseColor},
{":/utils/images/iconoverlay_add_small.png", Theme::IconsRunToolBarColor}}).icon()); {":/utils/images/iconoverlay_add_small.png", Theme::IconsRunToolBarColor}})
.icon());
newTerminal.setToolTip(Tr::tr("Create a new Terminal.")); newTerminal.setToolTip(Tr::tr("Create a new Terminal."));
connect(&newTerminal, &QAction::triggered, this, [this] { openTerminal({}); }); connect(&newTerminal, &QAction::triggered, this, [this] { openTerminal({}); });
closeTerminal.setIcon(Icon({ closeTerminal.setIcon(
{":/terminal/images/terminal.png", Theme::IconsBaseColor}, Icon({{":/terminal/images/terminal.png", Theme::IconsBaseColor},
{":/utils/images/iconoverlay_close_small.png", Theme::IconsStopToolBarColor}}).icon()); {":/utils/images/iconoverlay_close_small.png", Theme::IconsStopToolBarColor}})
.icon());
closeTerminal.setToolTip(Tr::tr("Close the current Terminal.")); closeTerminal.setToolTip(Tr::tr("Close the current Terminal."));
closeTerminal.setEnabled(false); closeTerminal.setEnabled(false);
@@ -289,12 +293,6 @@ void TerminalPane::clearContents()
t->clearContents(); t->clearContents();
} }
void TerminalPane::visibilityChanged(bool visible)
{
if (visible)
TerminalCommands::instance().registerOpenCloseTerminalPaneCommand();
}
void TerminalPane::setFocus() void TerminalPane::setFocus()
{ {
if (const auto t = currentTerminal()) if (const auto t = currentTerminal())

View File

@@ -27,7 +27,6 @@ public:
QString displayName() const override; QString displayName() const override;
int priorityInStatusBar() const override; int priorityInStatusBar() const override;
void clearContents() override; void clearContents() override;
void visibilityChanged(bool visible) override;
void setFocus() override; void setFocus() override;
bool hasFocus() const override; bool hasFocus() const override;
bool canFocus() const override; bool canFocus() const override;

View File

@@ -7,6 +7,7 @@
#include "terminalprocessimpl.h" #include "terminalprocessimpl.h"
#include "terminalsettings.h" #include "terminalsettings.h"
#include "terminalsettingspage.h" #include "terminalsettingspage.h"
#include "terminalcommands.h"
#include <coreplugin/actionmanager/actioncontainer.h> #include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/actionmanager/actionmanager.h>
@@ -35,6 +36,12 @@ TerminalPlugin::~TerminalPlugin()
m_terminalPane = nullptr; m_terminalPane = nullptr;
} }
bool TerminalPlugin::delayedInitialize()
{
TerminalCommands::instance().lazyInitCommands();
return true;
}
void TerminalPlugin::extensionsInitialized() void TerminalPlugin::extensionsInitialized()
{ {
TerminalSettingsPage::instance().init(); TerminalSettingsPage::instance().init();

View File

@@ -20,6 +20,7 @@ public:
~TerminalPlugin() override; ~TerminalPlugin() override;
void extensionsInitialized() override; void extensionsInitialized() override;
bool delayedInitialize() override;
private: private:
TerminalPane *m_terminalPane{nullptr}; TerminalPane *m_terminalPane{nullptr};

View File

@@ -0,0 +1,280 @@
// 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 "terminalcommands.h"
#include <QElapsedTimer>
#include <QRegularExpression>
#include <chrono>
using namespace std::chrono_literals;
namespace Terminal {
using namespace Terminal::Internal;
constexpr std::chrono::milliseconds debounceInterval = 100ms;
TerminalSearch::TerminalSearch(TerminalSurface *surface)
: m_surface(surface)
{
m_debounceTimer.setInterval(debounceInterval);
m_debounceTimer.setSingleShot(true);
connect(surface, &TerminalSurface::invalidated, this, &TerminalSearch::updateHits);
connect(&m_debounceTimer, &QTimer::timeout, this, &TerminalSearch::debouncedUpdateHits);
connect(&TerminalCommands::widgetActions().findNext,
&QAction::triggered,
this,
&TerminalSearch::nextHit);
connect(&TerminalCommands::widgetActions().findPrevious,
&QAction::triggered,
this,
&TerminalSearch::previousHit);
}
void TerminalSearch::setCurrentSelection(std::optional<SearchHitWithText> selection)
{
m_currentSelection = selection;
}
void TerminalSearch::setSearchString(const QString &searchString, Core::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<SearchHit> TerminalSearch::search()
{
QList<SearchHit> hits;
std::function<bool(char32_t, char32_t)> compare;
if (m_findFlags.testFlag(Core::FindFlag::FindCaseSensitively))
compare = [](char32_t a, char32_t b) {
return std::tolower(a) == std::tolower(b) || isSpace(a, b);
};
else
compare = [](char32_t a, char32_t b) { return a == 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(Core::FindFlag::FindWholeWords)) {
searchString.push_back(std::numeric_limits<char32_t>::max());
searchString.insert(searchString.begin(), std::numeric_limits<char32_t>::max());
}
Internal::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 = SearchHit{it.position(),
static_cast<int>(it.position() + searchString.size())};
if (m_findFlags.testFlag(Core::FindFlag::FindWholeWords)) {
hit.start++;
hit.end--;
}
hits << hit;
it += m_currentSearchString.size();
}
}
}
return hits;
}
QList<SearchHit> TerminalSearch::searchRegex()
{
QList<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(Core::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 << SearchHit{s, e};
}
return hits;
}
void TerminalSearch::debouncedUpdateHits()
{
QElapsedTimer t;
t.start();
m_currentHit = -1;
const bool regex = m_findFlags.testFlag(Core::FindFlag::FindRegularExpression);
QList<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())
qDebug() << "Search took" << t.elapsed() << "ms";
}
Core::FindFlags TerminalSearch::supportedFindFlags() const
{
return Core::FindFlag::FindCaseSensitively | Core::FindFlag::FindBackward
| Core::FindFlag::FindRegularExpression | Core::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,
Core::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, Core::FindFlags findFlags)
{
if (txt == m_currentSearchString) {
if (m_debounceTimer.isActive())
return Result::NotYetFound;
else if (m_hits.isEmpty())
return Result::NotFound;
if (findFlags.testFlag(Core::FindFlag::FindBackward))
previousHit();
else
nextHit();
return Result::Found;
}
return findIncremental(txt, findFlags);
}
void TerminalSearch::highlightAll(const QString &txt, Core::FindFlags findFlags)
{
setSearchString(txt, findFlags);
}
} // namespace Terminal

View File

@@ -0,0 +1,82 @@
// 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 "terminalsurface.h"
#include <coreplugin/find/ifindsupport.h>
#include <coreplugin/find/textfindconstants.h>
#include <QTimer>
namespace Terminal {
struct SearchHit
{
int start{-1};
int end{-1};
bool operator!=(const SearchHit &other) const
{
return start != other.start || end != other.end;
}
bool operator==(const SearchHit &other) const { return !operator!=(other); }
};
struct SearchHitWithText : SearchHit
{
QString text;
};
class TerminalSearch : public Core::IFindSupport
{
Q_OBJECT
public:
TerminalSearch(Internal::TerminalSurface *surface);
void setCurrentSelection(std::optional<SearchHitWithText> selection);
void setSearchString(const QString &searchString, Core::FindFlags findFlags);
void nextHit();
void previousHit();
QList<SearchHit> hits() const { return m_hits; }
SearchHit currentHit() const
{
return m_currentHit >= 0 ? m_hits.at(m_currentHit) : SearchHit{};
}
public:
bool supportsReplace() const override { return false; }
Core::FindFlags supportedFindFlags() const override;
void resetIncrementalSearch() override;
void clearHighlights() override;
QString currentFindString() const override;
QString completedFindString() const override;
Result findIncremental(const QString &txt, Core::FindFlags findFlags) override;
Result findStep(const QString &txt, Core::FindFlags findFlags) override;
void highlightAll(const QString &, Core::FindFlags) override;
signals:
void hitsChanged();
void currentHitChanged();
protected:
void updateHits();
void debouncedUpdateHits();
QList<SearchHit> search();
QList<SearchHit> searchRegex();
private:
std::optional<SearchHitWithText> m_currentSelection;
QString m_currentSearchString;
Core::FindFlags m_findFlags;
Internal::TerminalSurface *m_surface;
int m_currentHit{-1};
QList<SearchHit> m_hits;
QTimer m_debounceTimer;
};
} // namespace Terminal

View File

@@ -110,6 +110,11 @@ TerminalSettings::TerminalSettings()
"Selection", "Selection",
Utils::creatorTheme()->color(Theme::TerminalSelection)); Utils::creatorTheme()->color(Theme::TerminalSelection));
setupColor(this,
findMatchColor,
"Find matches",
Utils::creatorTheme()->color(Theme::TerminalFindMatch));
setupColor(this, colors[0], "0", Utils::creatorTheme()->color(Theme::TerminalAnsi0)); setupColor(this, colors[0], "0", Utils::creatorTheme()->color(Theme::TerminalAnsi0));
setupColor(this, colors[8], "8", Utils::creatorTheme()->color(Theme::TerminalAnsi8)); setupColor(this, colors[8], "8", Utils::creatorTheme()->color(Theme::TerminalAnsi8));

View File

@@ -22,6 +22,7 @@ public:
Utils::ColorAspect foregroundColor; Utils::ColorAspect foregroundColor;
Utils::ColorAspect backgroundColor; Utils::ColorAspect backgroundColor;
Utils::ColorAspect selectionColor; Utils::ColorAspect selectionColor;
Utils::ColorAspect findMatchColor;
Utils::ColorAspect colors[16]; Utils::ColorAspect colors[16];

View File

@@ -399,6 +399,7 @@ QWidget *TerminalSettingsPage::widget()
Tr::tr("Foreground"), settings.foregroundColor, st, Tr::tr("Foreground"), settings.foregroundColor, st,
Tr::tr("Background"), settings.backgroundColor, st, Tr::tr("Background"), settings.backgroundColor, st,
Tr::tr("Selection"), settings.selectionColor, st, Tr::tr("Selection"), settings.selectionColor, st,
Tr::tr("Find match"), settings.findMatchColor, st,
}, },
Row { Row {
settings.colors[0], settings.colors[1], settings.colors[0], settings.colors[1],

View File

@@ -7,6 +7,10 @@
#include "terminalsettings.h" #include "terminalsettings.h"
#include "terminalsurface.h" #include "terminalsurface.h"
#include <aggregation/aggregate.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/fileutils.h> #include <coreplugin/fileutils.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
@@ -48,6 +52,15 @@ using namespace Utils::Terminal;
namespace Terminal { namespace Terminal {
namespace ColorIndex {
enum Indices {
Foreground = Internal::ColorIndex::Foreground,
Background = Internal::ColorIndex::Background,
Selection,
FindMatch,
};
}
using namespace std::chrono_literals; using namespace std::chrono_literals;
// Minimum time between two refreshes. (30fps) // Minimum time between two refreshes. (30fps)
@@ -72,7 +85,7 @@ TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &op
m_cursorBlinkState = !m_cursorBlinkState; m_cursorBlinkState = !m_cursorBlinkState;
else else
m_cursorBlinkState = true; 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); setAttribute(Qt::WA_InputMethodEnabled);
@@ -107,6 +120,18 @@ TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &op
setupFont(); setupFont();
configBlinkTimer(); 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() void TerminalWidget::setupPty()
@@ -203,12 +228,14 @@ void TerminalWidget::setupFont()
void TerminalWidget::setupColors() void TerminalWidget::setupColors()
{ {
// Check if the colors have changed. // Check if the colors have changed.
std::array<QColor, 18> newColors; std::array<QColor, 20> newColors;
for (int i = 0; i < 16; ++i) { for (int i = 0; i < 16; ++i) {
newColors[i] = TerminalSettings::instance().colors[i].value(); newColors[i] = TerminalSettings::instance().colors[i].value();
} }
newColors[16] = TerminalSettings::instance().foregroundColor.value(); newColors[ColorIndex::Background] = TerminalSettings::instance().backgroundColor.value();
newColors[17] = 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) if (m_currentColors == newColors)
return; return;
@@ -250,6 +277,16 @@ void TerminalWidget::setupSurface()
{ {
m_shellIntegration.reset(new ShellIntegration()); m_shellIntegration.reset(new ShellIntegration());
m_surface = std::make_unique<Internal::TerminalSurface>(QSize{80, 60}, m_shellIntegration.get()); 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(), connect(m_surface.get(),
&Internal::TerminalSurface::writeToPty, &Internal::TerminalSurface::writeToPty,
@@ -263,7 +300,8 @@ void TerminalWidget::setupSurface()
this, this,
[this](const QRect &rect) { [this](const QRect &rect) {
setSelection(std::nullopt); setSelection(std::nullopt);
updateViewport(gridToViewport(rect)); updateViewportRect(gridToViewport(rect));
verticalScrollBar()->setValue(m_surface->fullSize().height());
}); });
connect(m_surface.get(), connect(m_surface.get(),
&Internal::TerminalSurface::cursorChanged, &Internal::TerminalSurface::cursorChanged,
@@ -282,7 +320,8 @@ void TerminalWidget::setupSurface()
m_cursor = newCursor; m_cursor = newCursor;
updateViewport(gridToViewport(QRect{QPoint{startX, startY}, QPoint{endX, endY}})); updateViewportRect(
gridToViewport(QRect{QPoint{startX, startY}, QPoint{endX, endY}}));
configBlinkTimer(); configBlinkTimer();
}); });
connect(m_surface.get(), &Internal::TerminalSurface::altscreenChanged, this, [this] { 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) if (idx >= 0 && idx < 18)
return m_currentColors[idx]; return m_currentColors[idx];
return m_currentColors[Internal::ColorIndex::Background]; return m_currentColors[ColorIndex::Background];
} }
return std::get<QColor>(color); return std::get<QColor>(color);
} }
@@ -460,24 +499,34 @@ QString TerminalWidget::textFromSelection() const
Internal::CellIterator end = m_surface->iteratorAt(m_selection->end); Internal::CellIterator end = m_surface->iteratorAt(m_selection->end);
std::u32string s; std::u32string s;
bool previousWasZero = false;
for (; it != end; ++it) { for (; it != end; ++it) {
if (it.gridPos().x() == 0 && !s.empty()) if (it.gridPos().x() == 0 && !s.empty() && previousWasZero)
s += U'\n'; s += U'\n';
if (*it != 0)
if (*it != 0) {
previousWasZero = false;
s += *it; s += *it;
} else {
previousWasZero = true;
}
} }
return QString::fromUcs4(s.data(), static_cast<int>(s.size())); 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()) if (selectionLog().isDebugEnabled())
updateViewport(); updateViewport();
if (selection == m_selection) { if (selection == m_selection)
return false; return false;
}
m_selection = selection; m_selection = selection;
@@ -485,13 +534,25 @@ bool TerminalWidget::setSelection(const std::optional<Selection> &selection)
if (m_selection && m_selection->final) { if (m_selection && m_selection->final) {
qCDebug(selectionLog) << "Copy enabled:" << selection.has_value(); qCDebug(selectionLog) << "Copy enabled:" << selection.has_value();
QString text = textFromSelection();
QClipboard *clipboard = QApplication::clipboard(); QClipboard *clipboard = QApplication::clipboard();
if (clipboard->supportsSelection()) { if (clipboard->supportsSelection()) {
QString text = textFromSelection();
qCDebug(selectionLog) << "Selection set to clipboard: " << text; qCDebug(selectionLog) << "Selection set to clipboard: " << text;
clipboard->setText(text, QClipboard::Selection); 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()) if (!selectionLog().isDebugEnabled())
@@ -727,32 +788,64 @@ static void drawTextItemDecoration(QPainter &painter,
painter.setBrush(oldBrush); painter.setBrush(oldBrush);
} }
void TerminalWidget::paintSelectionOrBackground(QPainter &p, bool TerminalWidget::paintFindMatches(QPainter &p,
const Internal::TerminalCell &cell, QList<SearchHit>::const_iterator &it,
const QRectF &cellRect, const QRectF &cellRect,
const QPoint gridPos) const 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; bool isInSelection = false;
const int pos = m_surface->gridToPos(gridPos);
if (m_selection) { if (m_selection) {
const int pos = m_surface->gridToPos(gridPos);
isInSelection = pos >= m_selection->start && pos < m_selection->end; isInSelection = pos >= m_selection->start && pos < m_selection->end;
} }
if (isInSelection) if (isInSelection) {
p.fillRect(cellRect, TerminalSettings::instance().selectionColor.value()); p.fillRect(cellRect, m_currentColors[ColorIndex::Selection]);
else if (!(std::holds_alternative<int>(cell.backgroundColor) }
&& std::get<int>(cell.backgroundColor) == 17))
p.fillRect(cellRect, toQColor(cell.backgroundColor)); return isInSelection;
} }
int TerminalWidget::paintCell(QPainter &p, int TerminalWidget::paintCell(QPainter &p,
const QRectF &cellRect, const QRectF &cellRect,
QPoint gridPos, QPoint gridPos,
const Internal::TerminalCell &cell, 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)); 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()) qCeil((event->rect().y() + event->rect().height()) / m_cellSize.height())
+ scrollOffset); + 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 cellY = startRow; cellY < endRow; ++cellY) {
for (int cellX = 0; cellX < m_surface->liveSize().width();) { for (int cellX = 0; cellX < m_surface->liveSize().width();) {
const auto cell = m_surface->fetchCell(cellX, cellY); 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}), QRectF cellRect(gridToGlobal({cellX, cellY}),
QSizeF{m_cellSize.width() * cell.width, m_cellSize.height()}); 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; cellX += numCells;
} }
@@ -907,7 +1008,7 @@ void TerminalWidget::paintEvent(QPaintEvent *event)
if (paintLog().isDebugEnabled()) if (paintLog().isDebugEnabled())
p.fillRect(event->rect(), QColor::fromRgb(rand() % 60, rand() % 60, rand() % 60)); p.fillRect(event->rect(), QColor::fromRgb(rand() % 60, rand() % 60, rand() % 60));
else else
p.fillRect(event->rect(), m_currentColors[Internal::ColorIndex::Background]); p.fillRect(event->rect(), m_currentColors[ColorIndex::Background]);
int scrollOffset = verticalScrollBar()->value(); int scrollOffset = verticalScrollBar()->value();
int offset = -(scrollOffset * m_cellSize.height()); int offset = -(scrollOffset * m_cellSize.height());
@@ -923,7 +1024,7 @@ void TerminalWidget::paintEvent(QPaintEvent *event)
p.restore(); p.restore();
p.fillRect(QRectF{{0, 0}, QSizeF{(qreal) width(), topMargin()}}, p.fillRect(QRectF{{0, 0}, QSizeF{(qreal) width(), topMargin()}},
m_currentColors[Internal::ColorIndex::Background]); m_currentColors[ColorIndex::Background]);
if (selectionLog().isDebugEnabled()) { if (selectionLog().isDebugEnabled()) {
if (m_selection) if (m_selection)
@@ -946,8 +1047,20 @@ void TerminalWidget::keyPressEvent(QKeyEvent *event)
m_cursorBlinkState = true; 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)) { if (TerminalCommands::triggerAction(event)) {
setSelection(std::nullopt); if (oldSelection && oldSelection == m_selection)
setSelection(std::nullopt);
return; return;
} }
@@ -1023,7 +1136,7 @@ void TerminalWidget::updateViewport()
viewport()->update(); viewport()->update();
} }
void TerminalWidget::updateViewport(const QRect &rect) void TerminalWidget::updateViewportRect(const QRect &rect)
{ {
viewport()->update(rect); viewport()->update(rect);
} }
@@ -1297,7 +1410,7 @@ bool TerminalWidget::event(QEvent *event)
if (event->type() == QEvent::Paint) { if (event->type() == QEvent::Paint) {
QPainter p(this); 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; return true;
} }

View File

@@ -3,8 +3,11 @@
#pragma once #pragma once
#include "terminalsearch.h"
#include "terminalsurface.h" #include "terminalsurface.h"
#include <aggregation/aggregate.h>
#include <utils/link.h> #include <utils/link.h>
#include <utils/qtcprocess.h> #include <utils/qtcprocess.h>
#include <utils/terminalhooks.h> #include <utils/terminalhooks.h>
@@ -26,6 +29,7 @@ class TerminalWidget : public QAbstractScrollArea
public: public:
TerminalWidget(QWidget *parent = nullptr, TerminalWidget(QWidget *parent = nullptr,
const Utils::Terminal::OpenTerminalParameters &openParameters = {}); const Utils::Terminal::OpenTerminalParameters &openParameters = {});
~TerminalWidget() override;
void setFont(const QFont &font); void setFont(const QFont &font);
@@ -42,6 +46,8 @@ public:
void clearContents(); void clearContents();
TerminalSearch *search() { return m_search.get(); }
struct Selection struct Selection
{ {
int start; int start;
@@ -113,14 +119,16 @@ protected:
const QRectF &cellRect, const QRectF &cellRect,
QPoint gridPos, QPoint gridPos,
const Internal::TerminalCell &cell, const Internal::TerminalCell &cell,
QFont &f) const; QFont &f,
QList<SearchHit>::const_iterator &searchIt) const;
void paintCells(QPainter &painter, QPaintEvent *event) const; void paintCells(QPainter &painter, QPaintEvent *event) const;
void paintCursor(QPainter &painter) const; void paintCursor(QPainter &painter) const;
void paintPreedit(QPainter &painter) const; void paintPreedit(QPainter &painter) const;
void paintSelectionOrBackground(QPainter &painter, bool paintFindMatches(QPainter &painter,
const Internal::TerminalCell &cell, QList<SearchHit>::const_iterator &searchIt,
const QRectF &cellRect, const QRectF &cellRect,
const QPoint gridPos) const; const QPoint gridPos) const;
bool paintSelection(QPainter &painter, const QRectF &cellRect, const QPoint gridPos) const;
void paintDebugSelection(QPainter &painter, const Selection &selection) const; void paintDebugSelection(QPainter &painter, const Selection &selection) const;
qreal topMargin() const; qreal topMargin() const;
@@ -132,7 +140,7 @@ protected:
QRect gridToViewport(QRect rect) const; QRect gridToViewport(QRect rect) const;
void updateViewport(); void updateViewport();
void updateViewport(const QRect &rect); void updateViewportRect(const QRect &rect);
int textLineFromPixel(int y) const; int textLineFromPixel(int y) const;
std::optional<int> textPosFromPoint(const QTextLayout &textLayout, QPoint p) const; std::optional<int> textPosFromPoint(const QTextLayout &textLayout, QPoint p) const;
@@ -156,7 +164,7 @@ protected:
void flushVTerm(bool force); void flushVTerm(bool force);
bool setSelection(const std::optional<Selection> &selection); bool setSelection(const std::optional<Selection> &selection, bool scroll = true);
QString textFromSelection() const; QString textFromSelection() const;
void configBlinkTimer(); void configBlinkTimer();
@@ -194,7 +202,7 @@ private:
QTimer m_scrollTimer; QTimer m_scrollTimer;
int m_scrollDirection{0}; int m_scrollDirection{0};
std::array<QColor, 18> m_currentColors; std::array<QColor, 20> m_currentColors;
Utils::Terminal::OpenTerminalParameters m_openParameters; Utils::Terminal::OpenTerminalParameters m_openParameters;
@@ -208,6 +216,11 @@ private:
Utils::FilePath m_cwd; Utils::FilePath m_cwd;
Utils::CommandLine m_currentCommand; Utils::CommandLine m_currentCommand;
std::unique_ptr<TerminalSearch> m_search;
Aggregation::Aggregate *m_aggregate{nullptr};
SearchHit m_lastSelectedHit{};
}; };
} // namespace Terminal } // namespace Terminal