2017-04-26 22:56:19 +00:00
|
|
|
#include <QScrollBar>
|
|
|
|
#include <QMenu>
|
|
|
|
#include <QCompleter>
|
2017-04-27 23:57:13 +00:00
|
|
|
#include <QAction>
|
|
|
|
#include <QShortcut>
|
2017-04-26 22:56:19 +00:00
|
|
|
#include <QStringListModel>
|
2018-10-10 18:09:07 +00:00
|
|
|
#include <QTimer>
|
2019-05-17 17:48:59 +00:00
|
|
|
#include <QSettings>
|
2018-10-21 14:53:38 +00:00
|
|
|
#include <iostream>
|
2019-02-22 16:50:45 +00:00
|
|
|
#include "core/Cutter.h"
|
2017-10-02 16:18:40 +00:00
|
|
|
#include "ConsoleWidget.h"
|
|
|
|
#include "ui_ConsoleWidget.h"
|
2018-10-17 07:55:53 +00:00
|
|
|
#include "common/Helpers.h"
|
|
|
|
#include "common/SvgIconEngine.h"
|
2017-04-26 22:56:19 +00:00
|
|
|
|
|
|
|
|
2017-04-27 23:57:13 +00:00
|
|
|
static const int invalidHistoryPos = -1;
|
|
|
|
|
2019-05-17 17:48:59 +00:00
|
|
|
static const char *consoleWrapSettingsKey = "console.wrap";
|
2017-04-27 23:57:13 +00:00
|
|
|
|
2018-03-16 21:46:57 +00:00
|
|
|
ConsoleWidget::ConsoleWidget(MainWindow *main, QAction *action) :
|
|
|
|
CutterDockWidget(main, action),
|
2017-04-26 22:56:19 +00:00
|
|
|
ui(new Ui::ConsoleWidget),
|
2017-04-27 23:57:13 +00:00
|
|
|
debugOutputEnabled(true),
|
|
|
|
maxHistoryEntries(100),
|
|
|
|
lastHistoryPosition(invalidHistoryPos)
|
2017-04-26 22:56:19 +00:00
|
|
|
{
|
|
|
|
ui->setupUi(this);
|
|
|
|
|
|
|
|
// Adjust console lineedit
|
2017-04-27 23:57:13 +00:00
|
|
|
ui->inputLineEdit->setTextMargins(10, 0, 0, 0);
|
2017-04-26 22:56:19 +00:00
|
|
|
|
2017-11-26 21:54:44 +00:00
|
|
|
setupFont();
|
2017-04-26 22:56:19 +00:00
|
|
|
|
|
|
|
// Adjust text margins of consoleOutputTextEdit
|
2017-04-27 23:57:13 +00:00
|
|
|
QTextDocument *console_docu = ui->outputTextEdit->document();
|
2017-04-26 22:56:19 +00:00
|
|
|
console_docu->setDocumentMargin(10);
|
|
|
|
|
2018-03-16 21:46:57 +00:00
|
|
|
QAction *actionClear = new QAction(tr("Clear Output"), ui->outputTextEdit);
|
|
|
|
connect(actionClear, SIGNAL(triggered(bool)), ui->outputTextEdit, SLOT(clear()));
|
|
|
|
actions.append(actionClear);
|
2017-04-27 23:57:13 +00:00
|
|
|
|
2019-04-18 16:42:27 +00:00
|
|
|
actionWrapLines = new QAction(tr("Wrap Lines"), ui->outputTextEdit);
|
|
|
|
actionWrapLines->setCheckable(true);
|
2019-05-17 17:48:59 +00:00
|
|
|
setWrap(QSettings().value(consoleWrapSettingsKey, true).toBool());
|
2019-04-18 16:42:27 +00:00
|
|
|
connect(actionWrapLines, &QAction::triggered, this, [this] (bool checked) {
|
2019-05-17 17:48:59 +00:00
|
|
|
setWrap(checked);
|
2019-04-18 16:42:27 +00:00
|
|
|
});
|
|
|
|
actions.append(actionWrapLines);
|
|
|
|
|
2017-04-27 23:57:13 +00:00
|
|
|
// Completion
|
2019-04-26 15:07:11 +00:00
|
|
|
completer = new QCompleter(&completionModel, this);
|
2017-04-27 23:57:13 +00:00
|
|
|
completer->setMaxVisibleItems(20);
|
|
|
|
completer->setCaseSensitivity(Qt::CaseInsensitive);
|
|
|
|
completer->setFilterMode(Qt::MatchStartsWith);
|
|
|
|
ui->inputLineEdit->setCompleter(completer);
|
2017-04-26 22:56:19 +00:00
|
|
|
|
2019-04-26 15:07:11 +00:00
|
|
|
connect(ui->inputLineEdit, &QLineEdit::textChanged, this, &ConsoleWidget::updateCompletion);
|
|
|
|
updateCompletion();
|
|
|
|
|
2017-04-26 22:56:19 +00:00
|
|
|
// Set console output context menu
|
2017-04-27 23:57:13 +00:00
|
|
|
ui->outputTextEdit->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
connect(ui->outputTextEdit, SIGNAL(customContextMenuRequested(const QPoint &)),
|
|
|
|
this, SLOT(showCustomContextMenu(const QPoint &)));
|
|
|
|
|
|
|
|
// Esc clears inputLineEdit (like OmniBar)
|
|
|
|
QShortcut *clear_shortcut = new QShortcut(QKeySequence(Qt::Key_Escape), ui->inputLineEdit);
|
|
|
|
connect(clear_shortcut, SIGNAL(activated()), this, SLOT(clear()));
|
|
|
|
clear_shortcut->setContext(Qt::WidgetShortcut);
|
|
|
|
|
|
|
|
// Up and down arrows show history
|
|
|
|
QShortcut *historyOnUp = new QShortcut(QKeySequence(Qt::Key_Up), ui->inputLineEdit);
|
|
|
|
connect(historyOnUp, SIGNAL(activated()), this, SLOT(historyPrev()));
|
|
|
|
historyOnUp->setContext(Qt::WidgetShortcut);
|
|
|
|
|
|
|
|
QShortcut *historyOnDown = new QShortcut(QKeySequence(Qt::Key_Down), ui->inputLineEdit);
|
|
|
|
connect(historyOnDown, SIGNAL(activated()), this, SLOT(historyNext()));
|
|
|
|
historyOnDown->setContext(Qt::WidgetShortcut);
|
2017-11-26 21:54:44 +00:00
|
|
|
|
|
|
|
connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(setupFont()));
|
2017-04-26 22:56:19 +00:00
|
|
|
}
|
|
|
|
|
2019-04-26 15:07:11 +00:00
|
|
|
ConsoleWidget::~ConsoleWidget()
|
|
|
|
{
|
|
|
|
delete completer;
|
|
|
|
}
|
2017-04-26 22:56:19 +00:00
|
|
|
|
2017-11-26 21:54:44 +00:00
|
|
|
void ConsoleWidget::setupFont()
|
|
|
|
{
|
|
|
|
ui->outputTextEdit->setFont(Config()->getFont());
|
|
|
|
}
|
|
|
|
|
2017-04-26 22:56:19 +00:00
|
|
|
void ConsoleWidget::addOutput(const QString &msg)
|
|
|
|
{
|
2017-04-27 23:57:13 +00:00
|
|
|
ui->outputTextEdit->appendPlainText(msg);
|
|
|
|
scrollOutputToEnd();
|
2017-04-26 22:56:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ConsoleWidget::addDebugOutput(const QString &msg)
|
|
|
|
{
|
2018-03-21 20:32:32 +00:00
|
|
|
if (debugOutputEnabled) {
|
2017-04-27 23:57:13 +00:00
|
|
|
ui->outputTextEdit->appendHtml("<font color=\"red\"> [DEBUG]:\t" + msg + "</font>");
|
|
|
|
scrollOutputToEnd();
|
|
|
|
}
|
2017-04-26 22:56:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ConsoleWidget::focusInputLineEdit()
|
|
|
|
{
|
2017-04-27 23:57:13 +00:00
|
|
|
ui->inputLineEdit->setFocus();
|
2017-04-26 22:56:19 +00:00
|
|
|
}
|
|
|
|
|
2018-10-10 18:09:07 +00:00
|
|
|
void ConsoleWidget::removeLastLine()
|
|
|
|
{
|
|
|
|
ui->outputTextEdit->setFocus();
|
|
|
|
QTextCursor cur = ui->outputTextEdit->textCursor();
|
|
|
|
ui->outputTextEdit->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor);
|
|
|
|
ui->outputTextEdit->moveCursor(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
|
|
|
|
ui->outputTextEdit->moveCursor(QTextCursor::End, QTextCursor::KeepAnchor);
|
|
|
|
ui->outputTextEdit->textCursor().removeSelectedText();
|
|
|
|
ui->outputTextEdit->textCursor().deletePreviousChar();
|
|
|
|
ui->outputTextEdit->setTextCursor(cur);
|
|
|
|
}
|
|
|
|
|
2018-06-22 18:34:25 +00:00
|
|
|
void ConsoleWidget::executeCommand(const QString &command)
|
|
|
|
{
|
|
|
|
if (!commandTask.isNull()) {
|
|
|
|
return;
|
|
|
|
}
|
2018-06-24 19:16:57 +00:00
|
|
|
ui->inputLineEdit->setEnabled(false);
|
|
|
|
|
2018-10-10 18:09:07 +00:00
|
|
|
const int originalLines = ui->outputTextEdit->blockCount();
|
|
|
|
QTimer *timer = new QTimer(this);
|
|
|
|
timer->setInterval(500);
|
|
|
|
timer->setSingleShot(true);
|
|
|
|
connect(timer, &QTimer::timeout, [this]() {
|
|
|
|
ui->outputTextEdit->appendPlainText("Executing the command...");
|
|
|
|
});
|
2018-06-22 18:34:25 +00:00
|
|
|
|
2018-10-21 14:53:38 +00:00
|
|
|
QString cmd_line = "<br>[" + RAddressString(Core()->getOffset()) + "]> " + command + "<br>";
|
2018-11-11 11:10:32 +00:00
|
|
|
RVA oldOffset = Core()->getOffset();
|
2018-10-21 14:53:38 +00:00
|
|
|
commandTask = QSharedPointer<CommandTask>(new CommandTask(command, CommandTask::ColorMode::MODE_256, true));
|
2018-09-30 20:00:53 +00:00
|
|
|
connect(commandTask.data(), &CommandTask::finished, this, [this, cmd_line,
|
2018-11-11 11:10:32 +00:00
|
|
|
command, originalLines, oldOffset] (const QString & result) {
|
2018-10-21 14:53:38 +00:00
|
|
|
|
2018-10-10 18:09:07 +00:00
|
|
|
if (originalLines < ui->outputTextEdit->blockCount()) {
|
|
|
|
removeLastLine();
|
|
|
|
}
|
2018-10-21 14:53:38 +00:00
|
|
|
ui->outputTextEdit->appendHtml(cmd_line + result);
|
2018-06-22 18:34:25 +00:00
|
|
|
scrollOutputToEnd();
|
|
|
|
historyAdd(command);
|
|
|
|
commandTask = nullptr;
|
2018-06-24 19:16:57 +00:00
|
|
|
ui->inputLineEdit->setEnabled(true);
|
|
|
|
ui->inputLineEdit->setFocus();
|
2018-11-11 11:10:32 +00:00
|
|
|
if (oldOffset != Core()->getOffset()) {
|
|
|
|
Core()->updateSeek();
|
|
|
|
}
|
2018-06-22 18:34:25 +00:00
|
|
|
});
|
2018-10-10 18:09:07 +00:00
|
|
|
connect(commandTask.data(), &CommandTask::finished, timer, &QTimer::stop);
|
|
|
|
|
|
|
|
timer->start();
|
2018-06-22 18:34:25 +00:00
|
|
|
Core()->getAsyncTaskManager()->start(commandTask);
|
|
|
|
}
|
|
|
|
|
2019-05-17 17:48:59 +00:00
|
|
|
void ConsoleWidget::setWrap(bool wrap)
|
|
|
|
{
|
|
|
|
QSettings().setValue(consoleWrapSettingsKey, wrap);
|
|
|
|
actionWrapLines->setChecked(wrap);
|
|
|
|
ui->outputTextEdit->setLineWrapMode(wrap ? QPlainTextEdit::WidgetWidth: QPlainTextEdit::NoWrap);
|
|
|
|
}
|
|
|
|
|
2017-04-27 23:57:13 +00:00
|
|
|
void ConsoleWidget::on_inputLineEdit_returnPressed()
|
2017-04-26 22:56:19 +00:00
|
|
|
{
|
2017-04-27 23:57:13 +00:00
|
|
|
QString input = ui->inputLineEdit->text();
|
2018-10-13 13:44:20 +00:00
|
|
|
if (input.isEmpty()) {
|
|
|
|
return;
|
2017-04-26 22:56:19 +00:00
|
|
|
}
|
2018-10-13 13:44:20 +00:00
|
|
|
executeCommand(input);
|
|
|
|
ui->inputLineEdit->clear();
|
2017-04-26 22:56:19 +00:00
|
|
|
}
|
|
|
|
|
2017-04-27 23:57:13 +00:00
|
|
|
void ConsoleWidget::on_execButton_clicked()
|
2017-04-26 22:56:19 +00:00
|
|
|
{
|
2017-04-27 23:57:13 +00:00
|
|
|
on_inputLineEdit_returnPressed();
|
2017-04-26 22:56:19 +00:00
|
|
|
}
|
|
|
|
|
2017-04-27 23:57:13 +00:00
|
|
|
void ConsoleWidget::showCustomContextMenu(const QPoint &pt)
|
2017-04-26 22:56:19 +00:00
|
|
|
{
|
2019-04-18 16:42:27 +00:00
|
|
|
actionWrapLines->setChecked(ui->outputTextEdit->lineWrapMode() == QPlainTextEdit::WidgetWidth);
|
|
|
|
|
2017-04-27 23:57:13 +00:00
|
|
|
QMenu *menu = new QMenu(ui->outputTextEdit);
|
|
|
|
menu->addActions(actions);
|
|
|
|
menu->exec(ui->outputTextEdit->mapToGlobal(pt));
|
|
|
|
menu->deleteLater();
|
|
|
|
}
|
2017-04-26 22:56:19 +00:00
|
|
|
|
2017-04-27 23:57:13 +00:00
|
|
|
void ConsoleWidget::historyNext()
|
2017-04-26 22:56:19 +00:00
|
|
|
{
|
2018-03-21 20:32:32 +00:00
|
|
|
if (!history.isEmpty()) {
|
|
|
|
if (lastHistoryPosition > invalidHistoryPos) {
|
|
|
|
if (lastHistoryPosition >= history.size()) {
|
2017-06-03 12:27:23 +00:00
|
|
|
lastHistoryPosition = history.size() - 1 ;
|
2017-04-27 23:57:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
--lastHistoryPosition;
|
|
|
|
|
2018-03-21 20:32:32 +00:00
|
|
|
if (lastHistoryPosition >= 0) {
|
2017-04-27 23:57:13 +00:00
|
|
|
ui->inputLineEdit->setText(history.at(lastHistoryPosition));
|
2018-03-21 20:32:32 +00:00
|
|
|
} else {
|
2017-04-27 23:57:13 +00:00
|
|
|
ui->inputLineEdit->clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
2017-04-26 22:56:19 +00:00
|
|
|
}
|
|
|
|
|
2017-04-27 23:57:13 +00:00
|
|
|
void ConsoleWidget::historyPrev()
|
2017-04-26 22:56:19 +00:00
|
|
|
{
|
2018-03-21 20:32:32 +00:00
|
|
|
if (!history.isEmpty()) {
|
|
|
|
if (lastHistoryPosition >= history.size() - 1) {
|
2017-04-27 23:57:13 +00:00
|
|
|
lastHistoryPosition = history.size() - 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
ui->inputLineEdit->setText(history.at(++lastHistoryPosition));
|
|
|
|
}
|
2017-04-26 22:56:19 +00:00
|
|
|
}
|
|
|
|
|
2019-04-26 15:07:11 +00:00
|
|
|
void ConsoleWidget::updateCompletion()
|
|
|
|
{
|
|
|
|
auto current = ui->inputLineEdit->text();
|
|
|
|
auto completions = Core()->autocomplete(current, R_LINE_PROMPT_DEFAULT);
|
|
|
|
int lastSpace = current.lastIndexOf(' ');
|
|
|
|
if (lastSpace >= 0) {
|
|
|
|
current = current.left(lastSpace + 1);
|
|
|
|
for (auto &s : completions) {
|
|
|
|
s = current + s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
completionModel.setStringList(completions);
|
|
|
|
}
|
|
|
|
|
2017-04-27 23:57:13 +00:00
|
|
|
void ConsoleWidget::clear()
|
|
|
|
{
|
|
|
|
ui->inputLineEdit->clear();
|
|
|
|
|
|
|
|
invalidateHistoryPosition();
|
2017-04-26 22:56:19 +00:00
|
|
|
|
2017-04-27 23:57:13 +00:00
|
|
|
// Close the potential shown completer popup
|
|
|
|
ui->inputLineEdit->clearFocus();
|
|
|
|
ui->inputLineEdit->setFocus();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ConsoleWidget::scrollOutputToEnd()
|
2017-04-26 22:56:19 +00:00
|
|
|
{
|
2017-04-27 23:57:13 +00:00
|
|
|
const int maxValue = ui->outputTextEdit->verticalScrollBar()->maximum();
|
|
|
|
ui->outputTextEdit->verticalScrollBar()->setValue(maxValue);
|
|
|
|
}
|
2017-04-26 22:56:19 +00:00
|
|
|
|
2017-04-27 23:57:13 +00:00
|
|
|
void ConsoleWidget::historyAdd(const QString &input)
|
|
|
|
{
|
2018-03-21 20:32:32 +00:00
|
|
|
if (history.size() + 1 > maxHistoryEntries) {
|
2017-04-27 23:57:13 +00:00
|
|
|
history.removeLast();
|
2017-04-26 22:56:19 +00:00
|
|
|
}
|
2017-04-27 23:57:13 +00:00
|
|
|
|
|
|
|
history.prepend(input);
|
|
|
|
|
|
|
|
invalidateHistoryPosition();
|
|
|
|
}
|
|
|
|
void ConsoleWidget::invalidateHistoryPosition()
|
|
|
|
{
|
|
|
|
lastHistoryPosition = invalidHistoryPos;
|
2017-04-26 22:56:19 +00:00
|
|
|
}
|