Add basic completion and command history

This commit is contained in:
ballessay 2017-04-28 01:57:13 +02:00 committed by C. Balles
parent bdd308d183
commit 4fea009a97
3 changed files with 216 additions and 136 deletions

View File

@ -7,18 +7,20 @@
#include <QScrollBar> #include <QScrollBar>
#include <QMenu> #include <QMenu>
#include <QCompleter> #include <QCompleter>
//TODO: delete #include <QAction>
#include <QShortcut>
#include <QStringListModel> #include <QStringListModel>
// TODO: Find a way to get to this without copying it here // TODO: Find a way to get to this without copying it here
// source: libr/core/core.c:585.. // source: libr/core/core.c:585..
#define CMDS (sizeof (radare_argv)/sizeof(const char*)) // remark: u.* is missing
static const char *radare_argv[] = { static const QStringList radareArgs(
{
"?", "?v", "whereis", "which", "ls", "rm", "mkdir", "pwd", "cat", "less", "?", "?v", "whereis", "which", "ls", "rm", "mkdir", "pwd", "cat", "less",
"dH", "ds", "dso", "dsl", "dc", "dd", "dm", "db ", "db-", "dH", "ds", "dso", "dsl", "dc", "dd", "dm", "db ", "db-",
"dp", "dr", "dcu", "dmd", "dmp", "dml", "dp", "dr", "dcu", "dmd", "dmp", "dml",
"ec","ecs", "eco", "ec", "ecs", "eco",
"S", "S.", "S*", "S-", "S=", "Sa", "Sa-", "Sd", "Sl", "SSj", "Sr", "S", "S.", "S*", "S-", "S=", "Sa", "Sa-", "Sd", "Sl", "SSj", "Sr",
"s", "s+", "s++", "s-", "s--", "s*", "sa", "sb", "sr", "s", "s+", "s++", "s-", "s--", "s*", "sa", "sb", "sr",
"!", "!!", "!", "!!",
@ -27,10 +29,11 @@ static const char *radare_argv[] = {
"V", "v", "V", "v",
"aa", "ab", "af", "ar", "ag", "at", "a?", "ax", "ad", "aa", "ab", "af", "ar", "ag", "at", "a?", "ax", "ad",
"ae", "aec", "aex", "aep", "aea", "aeA", "aes", "aeso", "aesu", "aesue", "aer", "aei", "aeim", "aef", "ae", "aec", "aex", "aep", "aea", "aeA", "aes", "aeso", "aesu", "aesue", "aer", "aei", "aeim", "aef",
"aaa", "aac","aae", "aai", "aar", "aan", "aas", "aat", "aap", "aav", "aaa", "aac", "aae", "aai", "aar", "aan", "aas", "aat", "aap", "aav",
"af", "afa", "afan", "afc", "afC", "afi", "afb", "afbb", "afn", "afr", "afs", "af*", "afv", "afvn", "af", "afa", "afan", "afc", "afC", "afi", "afb", "afbb", "afn", "afr", "afs", "af*", "afv", "afvn",
"aga", "agc", "agd", "agl", "agfl", "aga", "agc", "agd", "agl", "agfl",
"e", "et", "e-", "e*", "e!", "e?", "env ", // see forbbidenArgs
//"e", "et", "e-", "e*", "e!", "e?", "env ",
"i", "ii", "iI", "is", "iS", "iz", "i", "ii", "iI", "is", "iS", "iz",
"q", "q!", "q", "q!",
"f", "fl", "fr", "f-", "f*", "fs", "fS", "fr", "fo", "f?", "f", "fl", "fr", "f-", "f*", "fs", "fS", "fr", "fo", "f?",
@ -55,20 +58,52 @@ static const char *radare_argv[] = {
"z/", "z/*", "z/", "z/*",
"zc", "zc",
"zs", "zs+", "zs-", "zs-*", "zsr", "zs", "zs+", "zs-", "zs-*", "zsr",
"#!pipe", "#!pipe"
NULL });
};
static const QStringList forbiddenArgs({"e", "et", "e-", "e*", "e!", "e?", "env"});
static const int invalidHistoryPos = -1;
static bool isForbidden(const QString &input)
{
static const QRegExp delimiters("[;&]");
const QStringList &commands = input.split(delimiters, QString::SkipEmptyParts);
for (const QString &command : commands)
{
const QString &trimmedCommand = command.trimmed();
if (forbiddenArgs.contains(trimmedCommand)) return true;
for (const QString &arg : forbiddenArgs)
{
if (trimmedCommand.startsWith(arg + " ")) return true;
}
}
return false;
}
ConsoleWidget::ConsoleWidget(QRCore *core, QWidget *parent) : ConsoleWidget::ConsoleWidget(QRCore *core, QWidget *parent) :
QWidget(parent), QWidget(parent),
ui(new Ui::ConsoleWidget), ui(new Ui::ConsoleWidget),
core(core) core(core),
debugOutputEnabled(true),
maxHistoryEntries(100),
lastHistoryPosition(invalidHistoryPos)
{ {
ui->setupUi(this); ui->setupUi(this);
// Adjust console lineedit // Adjust console lineedit
ui->consoleInputLineEdit->setTextMargins(10, 0, 0, 0); ui->inputLineEdit->setTextMargins(10, 0, 0, 0);
/* /*
ui->consoleOutputTextEdit->setFont(QFont("Monospace", 8)); ui->consoleOutputTextEdit->setFont(QFont("Monospace", 8));
@ -77,16 +112,47 @@ ConsoleWidget::ConsoleWidget(QRCore *core, QWidget *parent) :
*/ */
// Adjust text margins of consoleOutputTextEdit // Adjust text margins of consoleOutputTextEdit
QTextDocument *console_docu = ui->consoleOutputTextEdit->document(); QTextDocument *console_docu = ui->outputTextEdit->document();
console_docu->setDocumentMargin(10); console_docu->setDocumentMargin(10);
// Fix output panel font // Fix output panel font
qhelpers::normalizeFont(ui->consoleOutputTextEdit); qhelpers::normalizeFont(ui->outputTextEdit);
QAction *action = new QAction(tr("Clear ouput"), ui->outputTextEdit);
connect(action, SIGNAL(triggered(bool)), ui->outputTextEdit, SLOT(clear()));
actions.append(action);
action = new QAction(tr("Sync with core"), ui->outputTextEdit);
action->setCheckable(true);
connect(action, SIGNAL(toggled(bool)), this, SLOT(syncWithCoreToggled(bool)));
actions.append(action);
// Completion
QCompleter *completer = new QCompleter(radareArgs, this);
completer->setMaxVisibleItems(20);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setFilterMode(Qt::MatchStartsWith);
ui->inputLineEdit->setCompleter(completer);
// Set console output context menu // Set console output context menu
ui->consoleOutputTextEdit->setContextMenuPolicy(Qt::CustomContextMenu); ui->outputTextEdit->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->consoleOutputTextEdit, SIGNAL(customContextMenuRequested(const QPoint &)), connect(ui->outputTextEdit, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showConsoleContextMenu(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);
} }
ConsoleWidget::~ConsoleWidget() ConsoleWidget::~ConsoleWidget()
@ -96,94 +162,140 @@ ConsoleWidget::~ConsoleWidget()
void ConsoleWidget::addOutput(const QString &msg) void ConsoleWidget::addOutput(const QString &msg)
{ {
ui->consoleOutputTextEdit->appendPlainText(msg); ui->outputTextEdit->appendPlainText(msg);
ui->consoleOutputTextEdit->verticalScrollBar()->setValue(ui->consoleOutputTextEdit->verticalScrollBar()->maximum()); scrollOutputToEnd();
} }
void ConsoleWidget::addDebugOutput(const QString &msg) void ConsoleWidget::addDebugOutput(const QString &msg)
{ {
ui->consoleOutputTextEdit->appendHtml("<font color=\"red\"> [DEBUG]:\t" + msg + "</font>"); if (debugOutputEnabled)
ui->consoleOutputTextEdit->verticalScrollBar()->setValue(ui->consoleOutputTextEdit->verticalScrollBar()->maximum()); {
ui->outputTextEdit->appendHtml("<font color=\"red\"> [DEBUG]:\t" + msg + "</font>");
scrollOutputToEnd();
}
} }
void ConsoleWidget::focusInputLineEdit() void ConsoleWidget::focusInputLineEdit()
{ {
ui->consoleInputLineEdit->setFocus(); ui->inputLineEdit->setFocus();
} }
void ConsoleWidget::on_consoleInputLineEdit_returnPressed() void ConsoleWidget::on_inputLineEdit_returnPressed()
{ {
if (this->core) QString input = ui->inputLineEdit->text();
if (!input.isEmpty() && core != nullptr)
{ {
QString input = ui->consoleInputLineEdit->text(); if (!isForbidden(input))
ui->consoleOutputTextEdit->appendPlainText(this->core->cmd(input));
ui->consoleOutputTextEdit->verticalScrollBar()->setValue(ui->consoleOutputTextEdit->verticalScrollBar()->maximum());
// Add new command to history
QCompleter *completer = ui->consoleInputLineEdit->completer();
if (completer != NULL)
{ {
QStringListModel *completerModel = (QStringListModel *)(completer->model()); ui->outputTextEdit->appendPlainText(this->core->cmd(input));
if (completerModel != NULL) scrollOutputToEnd();
completerModel->setStringList(completerModel->stringList() << input);
}
ui->consoleInputLineEdit->setText(""); historyAdd(input);
}
}
void ConsoleWidget::on_consoleExecButton_clicked()
{
on_consoleInputLineEdit_returnPressed();
}
void ConsoleWidget::showConsoleContextMenu(const QPoint &pt)
{
// Set console output popup menu
QMenu *menu = ui->consoleOutputTextEdit->createStandardContextMenu();
menu->clear();
// TODO:
// menu->addAction(ui->actionClear_ConsoleOutput);
// menu->addAction(ui->actionConsoleSync_with_core);
//ui->actionConsoleSync_with_core->setChecked(true);
ui->consoleOutputTextEdit->setContextMenuPolicy(Qt::CustomContextMenu);
menu->exec(ui->consoleOutputTextEdit->mapToGlobal(pt));
delete menu;
}
void ConsoleWidget::on_actionConsoleSync_with_core_triggered()
{
// TODO:
// if (ui->actionConsoleSync_with_core->isChecked())
// {
// //Enable core syncronization
// }
// else
// {
// // Disable core sync
// }
}
void ConsoleWidget::on_actionClear_ConsoleOutput_triggered()
{
ui->consoleOutputTextEdit->clear();
}
void ConsoleWidget::on_showHistoToolButton_clicked()
{
QCompleter *completer = ui->consoleInputLineEdit->completer();
if (completer == NULL)
return;
if (ui->showHistoToolButton->isChecked())
{
completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
// Uhm... shouldn't it be called always?
completer->complete();
} }
else else
{ {
completer->setCompletionMode(QCompleter::PopupCompletion); addDebugOutput(tr("command forbidden: ") + input);
}
ui->inputLineEdit->clear();
} }
} }
void ConsoleWidget::on_execButton_clicked()
{
on_inputLineEdit_returnPressed();
}
void ConsoleWidget::showCustomContextMenu(const QPoint &pt)
{
QMenu *menu = new QMenu(ui->outputTextEdit);
menu->addActions(actions);
menu->exec(ui->outputTextEdit->mapToGlobal(pt));
menu->deleteLater();
}
void ConsoleWidget::syncWithCoreToggled(bool checked)
{
if (checked)
{
//Enable core syncronization
}
else
{
// Disable core sync
}
}
void ConsoleWidget::historyNext()
{
if (!history.isEmpty())
{
if (lastHistoryPosition > invalidHistoryPos)
{
if (lastHistoryPosition >= history.size())
{
lastHistoryPosition = history.size() -1 ;
}
--lastHistoryPosition;
if (lastHistoryPosition >= 0)
{
ui->inputLineEdit->setText(history.at(lastHistoryPosition));
}
else
{
ui->inputLineEdit->clear();
}
}
}
}
void ConsoleWidget::historyPrev()
{
if (!history.isEmpty())
{
if (lastHistoryPosition >= history.size() -1)
{
lastHistoryPosition = history.size() - 2;
}
ui->inputLineEdit->setText(history.at(++lastHistoryPosition));
}
}
void ConsoleWidget::clear()
{
ui->inputLineEdit->clear();
invalidateHistoryPosition();
// Close the potential shown completer popup
ui->inputLineEdit->clearFocus();
ui->inputLineEdit->setFocus();
}
void ConsoleWidget::scrollOutputToEnd()
{
const int maxValue = ui->outputTextEdit->verticalScrollBar()->maximum();
ui->outputTextEdit->verticalScrollBar()->setValue(maxValue);
}
void ConsoleWidget::historyAdd(const QString &input)
{
if (history.size() + 1 > maxHistoryEntries)
{
history.removeLast();
}
history.prepend(input);
invalidateHistoryPosition();
}
void ConsoleWidget::invalidateHistoryPosition()
{
lastHistoryPosition = invalidHistoryPos;
}

View File

@ -5,11 +5,14 @@
class QRCore; class QRCore;
class QAction;
namespace Ui namespace Ui
{ {
class ConsoleWidget; class ConsoleWidget;
} }
class ConsoleWidget : public QWidget class ConsoleWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT
@ -21,24 +24,39 @@ public:
void addOutput(const QString &msg); void addOutput(const QString &msg);
void addDebugOutput(const QString &msg); void addDebugOutput(const QString &msg);
void setDebugOutputEnabled(bool enabled) { debugOutputEnabled = enabled; }
void setMaxHistoryEntries(int max) { maxHistoryEntries = max; }
public slots: public slots:
void focusInputLineEdit(); void focusInputLineEdit();
private slots: private slots:
void on_consoleInputLineEdit_returnPressed(); void on_inputLineEdit_returnPressed();
void on_consoleExecButton_clicked(); void on_execButton_clicked();
void showConsoleContextMenu(const QPoint &pt); void showCustomContextMenu(const QPoint &pt);
void on_actionClear_ConsoleOutput_triggered(); void syncWithCoreToggled(bool checked);
void on_actionConsoleSync_with_core_triggered();
void on_showHistoToolButton_clicked(); void historyNext();
void historyPrev();
void clear();
private: private:
void scrollOutputToEnd();
void historyAdd(const QString &input);
void invalidateHistoryPosition();
Ui::ConsoleWidget *ui; Ui::ConsoleWidget *ui;
QRCore *core; QRCore *core;
QList<QAction *> actions;
bool debugOutputEnabled;
int maxHistoryEntries;
int lastHistoryPosition;
QStringList history;
}; };
#endif // CONSOLEWIDGET_H #endif // CONSOLEWIDGET_H

View File

@ -30,7 +30,7 @@
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
<widget class="QPlainTextEdit" name="consoleOutputTextEdit"> <widget class="QPlainTextEdit" name="outputTextEdit">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -81,7 +81,7 @@
<number>3</number> <number>3</number>
</property> </property>
<item> <item>
<widget class="QLineEdit" name="consoleInputLineEdit"> <widget class="QLineEdit" name="inputLineEdit">
<property name="font"> <property name="font">
<font> <font>
<pointsize>12</pointsize> <pointsize>12</pointsize>
@ -90,9 +90,6 @@
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">border-top: 1px solid rgb(255, 255, 255);</string> <string notr="true">border-top: 1px solid rgb(255, 255, 255);</string>
</property> </property>
<property name="text">
<string/>
</property>
<property name="frame"> <property name="frame">
<bool>false</bool> <bool>false</bool>
</property> </property>
@ -105,54 +102,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QToolButton" name="showHistoToolButton"> <widget class="QToolButton" name="execButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>18</height>
</size>
</property>
<property name="toolTip">
<string>Show commands history</string>
</property>
<property name="styleSheet">
<string notr="true">background-color: white;</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
<property name="arrowType">
<enum>Qt::DownArrow</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="consoleExecButton">
<property name="toolTip"> <property name="toolTip">
<string>Execute command</string> <string>Execute command</string>
</property> </property>