2023-02-23 12:47:39 +01:00
|
|
|
// Copyright (C) 2022 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
|
|
|
|
|
|
|
|
|
#include "terminalwidget.h"
|
2023-05-23 07:15:42 +02:00
|
|
|
#include "terminalconstants.h"
|
2023-02-23 12:47:39 +01:00
|
|
|
#include "terminalsettings.h"
|
2023-05-23 07:15:42 +02:00
|
|
|
#include "terminaltr.h"
|
2023-02-23 12:47:39 +01:00
|
|
|
|
2023-03-24 12:53:21 +01:00
|
|
|
#include <aggregation/aggregate.h>
|
|
|
|
|
|
|
|
|
|
#include <coreplugin/actionmanager/actionmanager.h>
|
|
|
|
|
#include <coreplugin/coreconstants.h>
|
2023-03-03 17:18:56 +01:00
|
|
|
#include <coreplugin/editormanager/editormanager.h>
|
2023-03-23 15:04:02 +01:00
|
|
|
#include <coreplugin/fileutils.h>
|
2023-02-23 12:47:39 +01:00
|
|
|
#include <coreplugin/icore.h>
|
2023-07-04 09:22:30 +02:00
|
|
|
#include <coreplugin/messagemanager.h>
|
2023-02-23 12:47:39 +01:00
|
|
|
|
|
|
|
|
#include <utils/algorithm.h>
|
|
|
|
|
#include <utils/environment.h>
|
2023-03-10 13:55:17 +01:00
|
|
|
#include <utils/fileutils.h>
|
2023-02-23 12:47:39 +01:00
|
|
|
#include <utils/hostosinfo.h>
|
2023-03-02 14:03:54 +01:00
|
|
|
#include <utils/processinterface.h>
|
2023-05-23 07:15:42 +02:00
|
|
|
#include <utils/proxyaction.h>
|
2023-02-23 12:47:39 +01:00
|
|
|
#include <utils/stringutils.h>
|
|
|
|
|
|
|
|
|
|
#include <QApplication>
|
2023-03-03 17:18:56 +01:00
|
|
|
#include <QCache>
|
2023-02-23 12:47:39 +01:00
|
|
|
#include <QClipboard>
|
2023-03-03 17:18:56 +01:00
|
|
|
#include <QDesktopServices>
|
2023-03-01 08:15:58 +01:00
|
|
|
#include <QElapsedTimer>
|
2023-02-23 12:47:39 +01:00
|
|
|
#include <QGlyphRun>
|
|
|
|
|
#include <QLoggingCategory>
|
2023-03-23 12:28:44 +01:00
|
|
|
#include <QMenu>
|
2023-04-25 14:59:18 +02:00
|
|
|
#include <QMimeData>
|
2023-02-23 12:47:39 +01:00
|
|
|
#include <QPaintEvent>
|
|
|
|
|
#include <QPainter>
|
2023-03-03 17:18:56 +01:00
|
|
|
#include <QPainterPath>
|
|
|
|
|
#include <QPixmapCache>
|
2023-02-23 12:47:39 +01:00
|
|
|
#include <QRawFont>
|
|
|
|
|
#include <QRegularExpression>
|
|
|
|
|
#include <QScrollBar>
|
2023-03-03 17:18:56 +01:00
|
|
|
#include <QTextItem>
|
2023-02-23 12:47:39 +01:00
|
|
|
#include <QTextLayout>
|
2023-03-03 17:18:56 +01:00
|
|
|
#include <QToolTip>
|
2023-02-23 12:47:39 +01:00
|
|
|
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
using namespace Utils::Terminal;
|
2023-05-23 07:15:42 +02:00
|
|
|
using namespace Core;
|
2023-02-23 12:47:39 +01:00
|
|
|
|
|
|
|
|
namespace Terminal {
|
|
|
|
|
TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &openParameters)
|
2023-07-20 08:34:09 +02:00
|
|
|
: Core::SearchableTerminal(parent)
|
2023-06-02 17:10:58 +03:00
|
|
|
, m_context(Utils::Id("TerminalWidget_").withSuffix(QString::number((uintptr_t) this)))
|
2023-02-23 12:47:39 +01:00
|
|
|
, m_openParameters(openParameters)
|
|
|
|
|
{
|
2023-05-23 07:15:42 +02:00
|
|
|
auto contextObj = new IContext(this);
|
|
|
|
|
contextObj->setWidget(this);
|
|
|
|
|
contextObj->setContext(m_context);
|
|
|
|
|
ICore::addContextObject(contextObj);
|
|
|
|
|
|
2023-02-23 12:47:39 +01:00
|
|
|
setupFont();
|
|
|
|
|
setupColors();
|
2023-03-02 15:21:50 +01:00
|
|
|
setupActions();
|
2023-02-23 12:47:39 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
surfaceChanged();
|
2023-02-23 12:47:39 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
setAllowBlinkingCursor(settings().allowBlinkingCursor());
|
2023-03-09 22:33:47 +01:00
|
|
|
|
2023-07-06 11:33:41 +02:00
|
|
|
connect(&settings(), &AspectContainer::applied, this, [this] {
|
2023-02-23 12:47:39 +01:00
|
|
|
// Setup colors first, as setupFont will redraw the screen.
|
|
|
|
|
setupColors();
|
|
|
|
|
setupFont();
|
2023-03-05 23:55:37 +01:00
|
|
|
configBlinkTimer();
|
2023-07-17 13:51:56 +02:00
|
|
|
setAllowBlinkingCursor(settings().allowBlinkingCursor());
|
2023-02-23 12:47:39 +01:00
|
|
|
});
|
2023-03-24 12:53:21 +01:00
|
|
|
}
|
|
|
|
|
|
2023-02-23 12:47:39 +01:00
|
|
|
void TerminalWidget::setupPty()
|
|
|
|
|
{
|
2023-05-03 16:00:22 +02:00
|
|
|
m_process = std::make_unique<Process>();
|
2023-02-23 12:47:39 +01:00
|
|
|
|
|
|
|
|
CommandLine shellCommand = m_openParameters.shellCommand.value_or(
|
2023-07-18 14:33:48 +02:00
|
|
|
CommandLine{settings().shell(), settings().shellArguments(), CommandLine::Raw});
|
2023-02-23 12:47:39 +01:00
|
|
|
|
2023-06-13 16:01:47 +02:00
|
|
|
Environment env = m_openParameters.environment.value_or(Environment{})
|
|
|
|
|
.appliedToEnvironment(shellCommand.executable().deviceEnvironment());
|
2023-03-28 14:48:58 +02:00
|
|
|
|
2023-02-23 15:21:26 +01:00
|
|
|
// For git bash on Windows
|
|
|
|
|
env.prependOrSetPath(shellCommand.executable().parentDir());
|
2023-03-01 08:15:58 +01:00
|
|
|
if (env.hasKey("CLINK_NOAUTORUN"))
|
|
|
|
|
env.unset("CLINK_NOAUTORUN");
|
2023-02-23 15:21:26 +01:00
|
|
|
|
2023-03-01 08:15:58 +01:00
|
|
|
m_process->setProcessMode(ProcessMode::Writer);
|
2023-03-14 12:10:59 +01:00
|
|
|
m_process->setPtyData(Utils::Pty::Data());
|
2023-03-01 08:15:58 +01:00
|
|
|
m_process->setCommand(shellCommand);
|
2023-03-01 09:57:43 +01:00
|
|
|
if (m_openParameters.workingDirectory.has_value())
|
|
|
|
|
m_process->setWorkingDirectory(*m_openParameters.workingDirectory);
|
2023-03-01 08:15:58 +01:00
|
|
|
m_process->setEnvironment(env);
|
2023-02-23 12:47:39 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
if (m_shellIntegration)
|
|
|
|
|
m_shellIntegration->prepareProcess(*m_process.get());
|
2023-03-10 13:55:17 +01:00
|
|
|
|
2023-05-03 16:00:22 +02:00
|
|
|
connect(m_process.get(), &Process::readyReadStandardOutput, this, [this]() {
|
2023-03-01 10:48:05 +01:00
|
|
|
onReadyRead(false);
|
2023-02-23 12:47:39 +01:00
|
|
|
});
|
|
|
|
|
|
2023-05-03 16:00:22 +02:00
|
|
|
connect(m_process.get(), &Process::done, this, [this] {
|
2023-07-04 09:22:30 +02:00
|
|
|
QString errorMessage;
|
|
|
|
|
|
2023-03-01 08:15:58 +01:00
|
|
|
if (m_process) {
|
|
|
|
|
if (m_process->exitCode() != 0) {
|
2023-07-04 09:22:30 +02:00
|
|
|
errorMessage
|
|
|
|
|
= Tr::tr("Terminal process exited with code %1").arg(m_process->exitCode());
|
2023-02-23 12:47:39 +01:00
|
|
|
|
2023-03-01 08:15:58 +01:00
|
|
|
if (!m_process->errorString().isEmpty())
|
2023-07-04 09:22:30 +02:00
|
|
|
errorMessage += QString(" (%1)").arg(m_process->errorString());
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_openParameters.m_exitBehavior == ExitBehavior::Restart) {
|
2023-03-01 10:48:05 +01:00
|
|
|
QMetaObject::invokeMethod(
|
|
|
|
|
this,
|
|
|
|
|
[this] {
|
2023-03-01 08:15:58 +01:00
|
|
|
m_process.reset();
|
2023-03-07 17:55:38 +01:00
|
|
|
setupSurface();
|
2023-02-23 12:47:39 +01:00
|
|
|
setupPty();
|
|
|
|
|
},
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_openParameters.m_exitBehavior == ExitBehavior::Close)
|
|
|
|
|
deleteLater();
|
|
|
|
|
|
|
|
|
|
if (m_openParameters.m_exitBehavior == ExitBehavior::Keep) {
|
2023-07-04 09:22:30 +02:00
|
|
|
if (!errorMessage.isEmpty()) {
|
|
|
|
|
QByteArray msg = QString("\r\n\033[31m%1").arg(errorMessage).toUtf8();
|
2023-02-23 12:47:39 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
writeToTerminal(msg, true);
|
2023-07-04 09:22:30 +02:00
|
|
|
} else {
|
|
|
|
|
QString exitMsg = Tr::tr("Process exited with code: %1")
|
|
|
|
|
.arg(m_process ? m_process->exitCode() : -1);
|
|
|
|
|
QByteArray msg = QString("\r\n%1").arg(exitMsg).toUtf8();
|
2023-07-17 13:51:56 +02:00
|
|
|
writeToTerminal(msg, true);
|
2023-07-04 09:22:30 +02:00
|
|
|
}
|
|
|
|
|
} else if (!errorMessage.isEmpty()) {
|
|
|
|
|
Core::MessageManager::writeFlashing(errorMessage);
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
|
|
|
|
});
|
2023-03-01 08:15:58 +01:00
|
|
|
|
2023-05-03 16:00:22 +02:00
|
|
|
connect(m_process.get(), &Process::started, this, [this] {
|
2023-03-07 17:55:38 +01:00
|
|
|
if (m_shellName.isEmpty())
|
|
|
|
|
m_shellName = m_process->commandLine().executable().fileName();
|
2023-03-02 16:59:03 +01:00
|
|
|
if (HostOsInfo::isWindowsHost() && m_shellName.endsWith(QTC_WIN_EXE_SUFFIX))
|
|
|
|
|
m_shellName.chop(QStringLiteral(QTC_WIN_EXE_SUFFIX).size());
|
|
|
|
|
|
2023-03-01 08:15:58 +01:00
|
|
|
applySizeChange();
|
|
|
|
|
emit started(m_process->processId());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
m_process->start();
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TerminalWidget::setupFont()
|
|
|
|
|
{
|
|
|
|
|
QFont f;
|
|
|
|
|
f.setFixedPitch(true);
|
2023-07-18 14:33:48 +02:00
|
|
|
f.setFamily(settings().font());
|
|
|
|
|
f.setPointSize(settings().fontSize());
|
2023-02-23 12:47:39 +01:00
|
|
|
|
|
|
|
|
setFont(f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TerminalWidget::setupColors()
|
|
|
|
|
{
|
|
|
|
|
// Check if the colors have changed.
|
2023-03-24 12:53:21 +01:00
|
|
|
std::array<QColor, 20> newColors;
|
2023-02-23 12:47:39 +01:00
|
|
|
for (int i = 0; i < 16; ++i) {
|
2023-07-18 14:33:48 +02:00
|
|
|
newColors[i] = settings().colors[i]();
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
2023-07-17 13:51:56 +02:00
|
|
|
newColors[(size_t) WidgetColorIdx::Background] = settings().backgroundColor.value();
|
|
|
|
|
newColors[(size_t) WidgetColorIdx::Foreground] = settings().foregroundColor.value();
|
|
|
|
|
newColors[(size_t) WidgetColorIdx::Selection] = settings().selectionColor.value();
|
|
|
|
|
newColors[(size_t) WidgetColorIdx::FindMatch] = settings().findMatchColor.value();
|
2023-02-23 12:47:39 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
setColors(newColors);
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
|
|
|
|
|
2023-06-06 09:37:13 +02:00
|
|
|
static bool contextMatcher(QObject *, Qt::ShortcutContext)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TerminalWidget::registerShortcut(Command *cmd)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(cmd, return);
|
|
|
|
|
auto addShortCut = [this, cmd] {
|
|
|
|
|
for (const auto &keySequence : cmd->keySequences()) {
|
2023-06-08 07:34:56 +02:00
|
|
|
if (!keySequence.isEmpty()) {
|
|
|
|
|
m_shortcutMap.addShortcut(cmd->action(),
|
|
|
|
|
keySequence,
|
|
|
|
|
Qt::ShortcutContext::WindowShortcut,
|
|
|
|
|
contextMatcher);
|
|
|
|
|
}
|
2023-06-06 09:37:13 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
auto removeShortCut = [this, cmd] { m_shortcutMap.removeShortcut(0, cmd->action()); };
|
|
|
|
|
addShortCut();
|
|
|
|
|
|
|
|
|
|
connect(cmd, &Command::keySequenceChanged, this, [addShortCut, removeShortCut]() {
|
|
|
|
|
removeShortCut();
|
|
|
|
|
addShortCut();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RegisteredAction TerminalWidget::registerAction(Id commandId, const Context &context)
|
2023-06-06 08:23:09 +02:00
|
|
|
{
|
|
|
|
|
QAction *action = new QAction;
|
2023-06-06 09:37:13 +02:00
|
|
|
Command *cmd = ActionManager::registerAction(action, commandId, context);
|
|
|
|
|
|
|
|
|
|
registerShortcut(cmd);
|
2023-06-06 08:23:09 +02:00
|
|
|
|
|
|
|
|
return RegisteredAction(action, [commandId](QAction *a) {
|
|
|
|
|
ActionManager::unregisterAction(a, commandId);
|
|
|
|
|
delete a;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-02 15:21:50 +01:00
|
|
|
void TerminalWidget::setupActions()
|
|
|
|
|
{
|
2023-06-06 08:23:09 +02:00
|
|
|
m_copy = registerAction(Constants::COPY, m_context);
|
|
|
|
|
m_paste = registerAction(Constants::PASTE, m_context);
|
|
|
|
|
m_close = registerAction(Core::Constants::CLOSE, m_context);
|
|
|
|
|
m_clearTerminal = registerAction(Constants::CLEAR_TERMINAL, m_context);
|
|
|
|
|
m_clearSelection = registerAction(Constants::CLEARSELECTION, m_context);
|
|
|
|
|
m_moveCursorWordLeft = registerAction(Constants::MOVECURSORWORDLEFT, m_context);
|
|
|
|
|
m_moveCursorWordRight = registerAction(Constants::MOVECURSORWORDRIGHT, m_context);
|
|
|
|
|
|
|
|
|
|
connect(m_copy.get(), &QAction::triggered, this, &TerminalWidget::copyToClipboard);
|
|
|
|
|
connect(m_paste.get(), &QAction::triggered, this, &TerminalWidget::pasteFromClipboard);
|
|
|
|
|
connect(m_close.get(), &QAction::triggered, this, &TerminalWidget::closeTerminal);
|
|
|
|
|
connect(m_clearTerminal.get(), &QAction::triggered, this, &TerminalWidget::clearContents);
|
|
|
|
|
connect(m_clearSelection.get(), &QAction::triggered, this, &TerminalWidget::clearSelection);
|
|
|
|
|
connect(m_moveCursorWordLeft.get(),
|
|
|
|
|
&QAction::triggered,
|
|
|
|
|
this,
|
|
|
|
|
&TerminalWidget::moveCursorWordLeft);
|
|
|
|
|
connect(m_moveCursorWordRight.get(),
|
|
|
|
|
&QAction::triggered,
|
|
|
|
|
this,
|
|
|
|
|
&TerminalWidget::moveCursorWordRight);
|
2023-05-23 07:15:42 +02:00
|
|
|
|
2023-06-06 09:37:13 +02:00
|
|
|
unlockGlobalAction(Core::Constants::EXIT);
|
|
|
|
|
unlockGlobalAction(Core::Constants::OPTIONS);
|
|
|
|
|
unlockGlobalAction("Preferences.Terminal.General");
|
|
|
|
|
unlockGlobalAction(Core::Constants::FIND_IN_DOCUMENT);
|
2023-05-23 07:15:42 +02:00
|
|
|
}
|
2023-03-23 11:04:54 +01:00
|
|
|
|
2023-05-23 07:15:42 +02:00
|
|
|
void TerminalWidget::closeTerminal()
|
|
|
|
|
{
|
|
|
|
|
deleteLater();
|
2023-03-02 15:21:50 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-25 08:17:13 +02:00
|
|
|
qint64 TerminalWidget::writeToPty(const QByteArray &data)
|
2023-02-23 12:47:39 +01:00
|
|
|
{
|
2023-03-07 17:55:38 +01:00
|
|
|
if (m_process && m_process->isRunning())
|
2023-07-25 08:17:13 +02:00
|
|
|
return m_process->writeRaw(data);
|
|
|
|
|
|
|
|
|
|
return data.size();
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
void TerminalWidget::resizePty(QSize newSize)
|
|
|
|
|
{
|
|
|
|
|
if (m_process && m_process->ptyData())
|
|
|
|
|
m_process->ptyData()->resize(newSize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TerminalWidget::surfaceChanged()
|
2023-02-23 12:47:39 +01:00
|
|
|
{
|
2023-07-20 08:34:09 +02:00
|
|
|
Core::SearchableTerminal::surfaceChanged();
|
|
|
|
|
|
2023-03-10 13:55:17 +01:00
|
|
|
m_shellIntegration.reset(new ShellIntegration());
|
2023-07-17 13:51:56 +02:00
|
|
|
setSurfaceIntegration(m_shellIntegration.get());
|
|
|
|
|
|
|
|
|
|
connect(m_shellIntegration.get(),
|
|
|
|
|
&ShellIntegration::titleChanged,
|
2023-07-03 12:21:30 +03:00
|
|
|
this,
|
|
|
|
|
[this](const QString &title) {
|
|
|
|
|
const FilePath titleFile = FilePath::fromUserInput(title);
|
|
|
|
|
if (!m_title.isEmpty()
|
|
|
|
|
|| m_openParameters.shellCommand.value_or(CommandLine{}).executable()
|
|
|
|
|
!= titleFile) {
|
|
|
|
|
m_title = titleFile.isFile() ? titleFile.baseName() : title;
|
|
|
|
|
}
|
|
|
|
|
emit titleChanged();
|
|
|
|
|
});
|
2023-03-24 22:14:18 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
connect(m_shellIntegration.get(),
|
|
|
|
|
&ShellIntegration::commandChanged,
|
|
|
|
|
this,
|
|
|
|
|
[this](const CommandLine &command) {
|
|
|
|
|
m_currentCommand = command;
|
|
|
|
|
emit commandChanged(m_currentCommand);
|
|
|
|
|
});
|
|
|
|
|
connect(m_shellIntegration.get(),
|
|
|
|
|
&ShellIntegration::currentDirChanged,
|
|
|
|
|
this,
|
|
|
|
|
[this](const QString ¤tDir) {
|
|
|
|
|
m_cwd = FilePath::fromUserInput(currentDir);
|
|
|
|
|
emit cwdChanged(m_cwd);
|
|
|
|
|
});
|
2023-03-22 15:39:55 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-03 12:21:30 +03:00
|
|
|
QString TerminalWidget::title() const
|
|
|
|
|
{
|
|
|
|
|
const FilePath dir = cwd();
|
|
|
|
|
QString title = m_title;
|
|
|
|
|
if (title.isEmpty())
|
|
|
|
|
title = currentCommand().isEmpty() ? shellName() : currentCommand().executable().fileName();
|
|
|
|
|
if (dir.isEmpty())
|
|
|
|
|
return title;
|
|
|
|
|
return title + " - " + dir.fileName();
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-23 11:04:54 +01:00
|
|
|
void TerminalWidget::updateCopyState()
|
|
|
|
|
{
|
|
|
|
|
if (!hasFocus())
|
|
|
|
|
return;
|
|
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
m_copy->setEnabled(selection().has_value());
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
void TerminalWidget::setClipboard(const QString &text)
|
2023-02-23 12:47:39 +01:00
|
|
|
{
|
2023-03-03 17:18:56 +01:00
|
|
|
setClipboardAndSelection(text);
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
2023-03-03 17:18:56 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
std::optional<TerminalSolution::TerminalView::Link> TerminalWidget::toLink(const QString &text)
|
2023-02-23 12:47:39 +01:00
|
|
|
{
|
2023-07-17 13:51:56 +02:00
|
|
|
if (text.size() > 0) {
|
|
|
|
|
QString result = chopIfEndsWith(text, ':');
|
2023-02-23 12:47:39 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
if (!result.isEmpty()) {
|
|
|
|
|
if (result.startsWith("~/"))
|
|
|
|
|
result = QDir::homePath() + result.mid(1);
|
2023-02-23 12:47:39 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
Utils::Link link = Utils::Link::fromString(result, true);
|
2023-02-23 12:47:39 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
if (!link.targetFilePath.isEmpty() && !link.targetFilePath.isAbsolutePath())
|
|
|
|
|
link.targetFilePath = m_cwd.pathAppended(link.targetFilePath.path());
|
2023-02-23 12:47:39 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
if (link.hasValidTarget()
|
|
|
|
|
&& (link.targetFilePath.scheme().toString().startsWith("http")
|
|
|
|
|
|| link.targetFilePath.exists())) {
|
|
|
|
|
return Link{link.targetFilePath.toString(), link.targetLine, link.targetColumn};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-23 14:51:24 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
return std::nullopt;
|
2023-03-23 14:51:24 +01:00
|
|
|
}
|
|
|
|
|
|
2023-03-01 10:48:05 +01:00
|
|
|
void TerminalWidget::onReadyRead(bool forceFlush)
|
2023-02-23 12:47:39 +01:00
|
|
|
{
|
2023-03-01 08:15:58 +01:00
|
|
|
QByteArray data = m_process->readAllRawStandardOutput();
|
2023-03-03 17:18:56 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
writeToTerminal(data, forceFlush);
|
2023-03-02 15:21:50 +01:00
|
|
|
}
|
|
|
|
|
|
2023-03-07 17:55:38 +01:00
|
|
|
void TerminalWidget::setShellName(const QString &shellName)
|
|
|
|
|
{
|
|
|
|
|
m_shellName = shellName;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-02 16:59:03 +01:00
|
|
|
QString TerminalWidget::shellName() const
|
|
|
|
|
{
|
|
|
|
|
return m_shellName;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-10 13:55:17 +01:00
|
|
|
FilePath TerminalWidget::cwd() const
|
|
|
|
|
{
|
|
|
|
|
return m_cwd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CommandLine TerminalWidget::currentCommand() const
|
|
|
|
|
{
|
|
|
|
|
return m_currentCommand;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 17:55:38 +01:00
|
|
|
std::optional<Id> TerminalWidget::identifier() const
|
|
|
|
|
{
|
|
|
|
|
return m_openParameters.identifier;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QProcess::ProcessState TerminalWidget::processState() const
|
|
|
|
|
{
|
|
|
|
|
if (m_process)
|
|
|
|
|
return m_process->state();
|
|
|
|
|
|
|
|
|
|
return QProcess::NotRunning;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TerminalWidget::restart(const OpenTerminalParameters &openParameters)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(!m_process || !m_process->isRunning(), return);
|
|
|
|
|
m_openParameters = openParameters;
|
|
|
|
|
m_process.reset();
|
2023-07-17 13:51:56 +02:00
|
|
|
TerminalView::restart();
|
2023-03-07 17:55:38 +01:00
|
|
|
setupPty();
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
void TerminalWidget::selectionChanged(const std::optional<Selection> &newSelection)
|
2023-02-23 12:47:39 +01:00
|
|
|
{
|
2023-07-31 09:30:22 +02:00
|
|
|
SearchableTerminal::selectionChanged(newSelection);
|
2023-07-20 08:34:09 +02:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
updateCopyState();
|
2023-02-23 12:47:39 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
if (selection() && selection()->final) {
|
|
|
|
|
QString text = textFromSelection();
|
2023-03-24 12:53:21 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
QClipboard *clipboard = QApplication::clipboard();
|
|
|
|
|
if (clipboard->supportsSelection())
|
|
|
|
|
clipboard->setText(text, QClipboard::Selection);
|
2023-03-24 12:53:21 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
void TerminalWidget::linkActivated(const Link &link)
|
2023-03-06 18:37:45 +01:00
|
|
|
{
|
2023-07-17 13:51:56 +02:00
|
|
|
FilePath filePath = FilePath::fromUserInput(link.text);
|
2023-03-06 18:37:45 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
if (filePath.scheme().toString().startsWith("http")) {
|
|
|
|
|
QDesktopServices::openUrl(filePath.toUrl());
|
2023-03-07 17:55:38 +01:00
|
|
|
return;
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
if (filePath.isDir())
|
|
|
|
|
Core::FileUtils::showInFileSystemView(filePath);
|
2023-03-03 17:18:56 +01:00
|
|
|
else
|
2023-07-17 13:51:56 +02:00
|
|
|
EditorManager::openEditorAt(Utils::Link{filePath, link.targetLine, link.targetColumn});
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
void TerminalWidget::focusInEvent(QFocusEvent *event)
|
2023-02-23 12:47:39 +01:00
|
|
|
{
|
2023-07-17 13:51:56 +02:00
|
|
|
TerminalView::focusInEvent(event);
|
2023-03-23 11:04:54 +01:00
|
|
|
updateCopyState();
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
void TerminalWidget::contextMenuRequested(const QPoint &pos)
|
2023-02-23 12:47:39 +01:00
|
|
|
{
|
2023-07-17 13:51:56 +02:00
|
|
|
QMenu *contextMenu = new QMenu(this);
|
|
|
|
|
QAction *configureAction = new QAction(contextMenu);
|
|
|
|
|
configureAction->setText(Tr::tr("Configure..."));
|
|
|
|
|
connect(configureAction, &QAction::triggered, this, [] {
|
|
|
|
|
ICore::showOptionsDialog("Terminal.General");
|
2023-03-24 22:42:10 +01:00
|
|
|
});
|
2023-03-01 16:03:18 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
contextMenu->addAction(ActionManager::command(Constants::COPY)->action());
|
|
|
|
|
contextMenu->addAction(ActionManager::command(Constants::PASTE)->action());
|
|
|
|
|
contextMenu->addSeparator();
|
|
|
|
|
contextMenu->addAction(ActionManager::command(Constants::CLEAR_TERMINAL)->action());
|
|
|
|
|
contextMenu->addSeparator();
|
|
|
|
|
contextMenu->addAction(configureAction);
|
2023-03-01 16:03:18 +01:00
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
contextMenu->popup(mapToGlobal(pos));
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
|
|
|
|
|
2023-04-25 14:59:18 +02:00
|
|
|
void TerminalWidget::dragEnterEvent(QDragEnterEvent *event)
|
|
|
|
|
{
|
|
|
|
|
if (event->mimeData()->hasUrls()) {
|
|
|
|
|
event->setDropAction(Qt::CopyAction);
|
|
|
|
|
event->accept();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TerminalWidget::dropEvent(QDropEvent *event)
|
|
|
|
|
{
|
|
|
|
|
QString urls = Utils::transform(event->mimeData()->urls(), [](const QUrl &url) {
|
|
|
|
|
return QString("\"%1\"").arg(url.toDisplayString(QUrl::PreferLocalFile));
|
|
|
|
|
}).join(" ");
|
|
|
|
|
|
|
|
|
|
writeToPty(urls.toUtf8());
|
|
|
|
|
event->setDropAction(Qt::CopyAction);
|
|
|
|
|
event->accept();
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-23 12:47:39 +01:00
|
|
|
void TerminalWidget::showEvent(QShowEvent *event)
|
|
|
|
|
{
|
|
|
|
|
Q_UNUSED(event);
|
|
|
|
|
|
2023-03-01 08:15:58 +01:00
|
|
|
if (!m_process)
|
2023-02-23 12:47:39 +01:00
|
|
|
setupPty();
|
|
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
TerminalView::showEvent(event);
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
void TerminalWidget::handleEscKey(QKeyEvent *event)
|
2023-02-23 12:47:39 +01:00
|
|
|
{
|
2023-07-17 13:51:56 +02:00
|
|
|
bool sendToTerminal = settings().sendEscapeToTerminal();
|
|
|
|
|
bool send = false;
|
|
|
|
|
if (sendToTerminal && event->modifiers() == Qt::NoModifier)
|
|
|
|
|
send = true;
|
|
|
|
|
else if (!sendToTerminal && event->modifiers() == Qt::ShiftModifier)
|
|
|
|
|
send = true;
|
|
|
|
|
|
|
|
|
|
if (send) {
|
|
|
|
|
event->setModifiers(Qt::NoModifier);
|
|
|
|
|
TerminalView::keyPressEvent(event);
|
|
|
|
|
return;
|
2023-06-06 09:37:13 +02:00
|
|
|
}
|
|
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
if (selection()) {
|
|
|
|
|
clearSelection();
|
|
|
|
|
} else {
|
|
|
|
|
QAction *returnAction = ActionManager::command(Core::Constants::S_RETURNTOEDITOR)
|
|
|
|
|
->actionForContext(Core::Constants::C_GLOBAL);
|
|
|
|
|
QTC_ASSERT(returnAction, return);
|
|
|
|
|
returnAction->trigger();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TerminalWidget::event(QEvent *event)
|
|
|
|
|
{
|
|
|
|
|
if (event->type() == QEvent::ShortcutOverride) {
|
|
|
|
|
auto keyEvent = static_cast<QKeyEvent *>(event);
|
|
|
|
|
if (keyEvent->key() == Qt::Key_Escape && keyEvent->modifiers() == Qt::NoModifier
|
|
|
|
|
&& settings().sendEscapeToTerminal()) {
|
|
|
|
|
event->accept();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (settings().lockKeyboard()) {
|
|
|
|
|
event->accept();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event->type() == QEvent::KeyPress) {
|
2023-06-06 09:37:13 +02:00
|
|
|
auto k = static_cast<QKeyEvent *>(event);
|
|
|
|
|
|
2023-07-17 13:51:56 +02:00
|
|
|
if (k->key() == Qt::Key_Escape) {
|
|
|
|
|
handleEscKey(k);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-06 11:33:41 +02:00
|
|
|
if (settings().lockKeyboard() && m_shortcutMap.tryShortcut(k))
|
2023-06-06 09:37:13 +02:00
|
|
|
return true;
|
|
|
|
|
|
2023-02-23 12:47:39 +01:00
|
|
|
keyPressEvent(k);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2023-07-17 13:51:56 +02:00
|
|
|
return TerminalView::event(event);
|
2023-02-23 12:47:39 +01:00
|
|
|
}
|
|
|
|
|
|
2023-05-23 07:15:42 +02:00
|
|
|
void TerminalWidget::initActions()
|
|
|
|
|
{
|
|
|
|
|
Core::Context context(Utils::Id("TerminalWidget"));
|
|
|
|
|
|
|
|
|
|
static QAction copy;
|
|
|
|
|
static QAction paste;
|
|
|
|
|
static QAction clearSelection;
|
|
|
|
|
static QAction clearTerminal;
|
|
|
|
|
static QAction moveCursorWordLeft;
|
|
|
|
|
static QAction moveCursorWordRight;
|
|
|
|
|
static QAction close;
|
|
|
|
|
|
|
|
|
|
copy.setText(Tr::tr("Copy"));
|
|
|
|
|
paste.setText(Tr::tr("Paste"));
|
|
|
|
|
clearSelection.setText(Tr::tr("Clear Selection"));
|
|
|
|
|
clearTerminal.setText(Tr::tr("Clear Terminal"));
|
|
|
|
|
moveCursorWordLeft.setText(Tr::tr("Move Cursor Word Left"));
|
|
|
|
|
moveCursorWordRight.setText(Tr::tr("Move Cursor Word Right"));
|
|
|
|
|
close.setText(Tr::tr("Close Terminal"));
|
|
|
|
|
|
|
|
|
|
ActionManager::registerAction(©, Constants::COPY, context)
|
|
|
|
|
->setDefaultKeySequences({QKeySequence(
|
|
|
|
|
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+C") : QLatin1String("Ctrl+Shift+C"))});
|
|
|
|
|
|
|
|
|
|
ActionManager::registerAction(&paste, Constants::PASTE, context)
|
|
|
|
|
->setDefaultKeySequences({QKeySequence(
|
|
|
|
|
HostOsInfo::isMacHost() ? QLatin1String("Ctrl+V") : QLatin1String("Ctrl+Shift+V"))});
|
|
|
|
|
|
|
|
|
|
ActionManager::registerAction(&clearSelection, Constants::CLEARSELECTION, context);
|
|
|
|
|
|
|
|
|
|
ActionManager::registerAction(&moveCursorWordLeft, Constants::MOVECURSORWORDLEFT, context)
|
|
|
|
|
->setDefaultKeySequences({QKeySequence("Alt+Left")});
|
|
|
|
|
|
|
|
|
|
ActionManager::registerAction(&moveCursorWordRight, Constants::MOVECURSORWORDRIGHT, context)
|
|
|
|
|
->setDefaultKeySequences({QKeySequence("Alt+Right")});
|
|
|
|
|
|
|
|
|
|
ActionManager::registerAction(&clearTerminal, Constants::CLEAR_TERMINAL, context);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-06 09:37:13 +02:00
|
|
|
void TerminalWidget::unlockGlobalAction(const Utils::Id &commandId)
|
2023-05-23 07:15:42 +02:00
|
|
|
{
|
2023-06-06 09:37:13 +02:00
|
|
|
Command *cmd = ActionManager::command(commandId);
|
|
|
|
|
QTC_ASSERT(cmd, return);
|
|
|
|
|
registerShortcut(cmd);
|
2023-05-23 07:15:42 +02:00
|
|
|
}
|
|
|
|
|
|
2023-02-23 12:47:39 +01:00
|
|
|
} // namespace Terminal
|