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 <QMenu>
#include <QCompleter>
//TODO: delete
#include <QAction>
#include <QShortcut>
#include <QStringListModel>
// TODO: Find a way to get to this without copying it here
// source: libr/core/core.c:585..
#define CMDS (sizeof (radare_argv)/sizeof(const char*))
static const char *radare_argv[] = {
// remark: u.* is missing
static const QStringList radareArgs(
{
"?", "?v", "whereis", "which", "ls", "rm", "mkdir", "pwd", "cat", "less",
"dH", "ds", "dso", "dsl", "dc", "dd", "dm", "db ", "db-",
"dp", "dr", "dcu", "dmd", "dmp", "dml",
"ec","ecs", "eco",
"dp", "dr", "dcu", "dmd", "dmp", "dml",
"ec", "ecs", "eco",
"S", "S.", "S*", "S-", "S=", "Sa", "Sa-", "Sd", "Sl", "SSj", "Sr",
"s", "s+", "s++", "s-", "s--", "s*", "sa", "sb", "sr",
"!", "!!",
@ -27,10 +29,11 @@ static const char *radare_argv[] = {
"V", "v",
"aa", "ab", "af", "ar", "ag", "at", "a?", "ax", "ad",
"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",
"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",
"q", "q!",
"f", "fl", "fr", "f-", "f*", "fs", "fS", "fr", "fo", "f?",
@ -55,20 +58,52 @@ static const char *radare_argv[] = {
"z/", "z/*",
"zc",
"zs", "zs+", "zs-", "zs-*", "zsr",
"#!pipe",
NULL
};
"#!pipe"
});
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) :
QWidget(parent),
ui(new Ui::ConsoleWidget),
core(core)
core(core),
debugOutputEnabled(true),
maxHistoryEntries(100),
lastHistoryPosition(invalidHistoryPos)
{
ui->setupUi(this);
// Adjust console lineedit
ui->consoleInputLineEdit->setTextMargins(10, 0, 0, 0);
ui->inputLineEdit->setTextMargins(10, 0, 0, 0);
/*
ui->consoleOutputTextEdit->setFont(QFont("Monospace", 8));
@ -77,16 +112,47 @@ ConsoleWidget::ConsoleWidget(QRCore *core, QWidget *parent) :
*/
// Adjust text margins of consoleOutputTextEdit
QTextDocument *console_docu = ui->consoleOutputTextEdit->document();
QTextDocument *console_docu = ui->outputTextEdit->document();
console_docu->setDocumentMargin(10);
// 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
ui->consoleOutputTextEdit->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->consoleOutputTextEdit, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showConsoleContextMenu(const QPoint &)));
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);
}
ConsoleWidget::~ConsoleWidget()
@ -96,94 +162,140 @@ ConsoleWidget::~ConsoleWidget()
void ConsoleWidget::addOutput(const QString &msg)
{
ui->consoleOutputTextEdit->appendPlainText(msg);
ui->consoleOutputTextEdit->verticalScrollBar()->setValue(ui->consoleOutputTextEdit->verticalScrollBar()->maximum());
ui->outputTextEdit->appendPlainText(msg);
scrollOutputToEnd();
}
void ConsoleWidget::addDebugOutput(const QString &msg)
{
ui->consoleOutputTextEdit->appendHtml("<font color=\"red\"> [DEBUG]:\t" + msg + "</font>");
ui->consoleOutputTextEdit->verticalScrollBar()->setValue(ui->consoleOutputTextEdit->verticalScrollBar()->maximum());
if (debugOutputEnabled)
{
ui->outputTextEdit->appendHtml("<font color=\"red\"> [DEBUG]:\t" + msg + "</font>");
scrollOutputToEnd();
}
}
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();
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)
if (!isForbidden(input))
{
QStringListModel *completerModel = (QStringListModel *)(completer->model());
if (completerModel != NULL)
completerModel->setStringList(completerModel->stringList() << input);
ui->outputTextEdit->appendPlainText(this->core->cmd(input));
scrollOutputToEnd();
historyAdd(input);
}
else
{
addDebugOutput(tr("command forbidden: ") + input);
}
ui->consoleInputLineEdit->setText("");
ui->inputLineEdit->clear();
}
}
void ConsoleWidget::on_consoleExecButton_clicked()
void ConsoleWidget::on_execButton_clicked()
{
on_consoleInputLineEdit_returnPressed();
on_inputLineEdit_returnPressed();
}
void ConsoleWidget::showConsoleContextMenu(const QPoint &pt)
void ConsoleWidget::showCustomContextMenu(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;
QMenu *menu = new QMenu(ui->outputTextEdit);
menu->addActions(actions);
menu->exec(ui->outputTextEdit->mapToGlobal(pt));
menu->deleteLater();
}
void ConsoleWidget::on_actionConsoleSync_with_core_triggered()
void ConsoleWidget::syncWithCoreToggled(bool checked)
{
// 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())
if (checked)
{
completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
// Uhm... shouldn't it be called always?
completer->complete();
//Enable core syncronization
}
else
{
completer->setCompletionMode(QCompleter::PopupCompletion);
// 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 QAction;
namespace Ui
{
class ConsoleWidget;
}
class ConsoleWidget : public QWidget
{
Q_OBJECT
@ -21,24 +24,39 @@ public:
void addOutput(const QString &msg);
void addDebugOutput(const QString &msg);
void setDebugOutputEnabled(bool enabled) { debugOutputEnabled = enabled; }
void setMaxHistoryEntries(int max) { maxHistoryEntries = max; }
public slots:
void focusInputLineEdit();
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 on_actionConsoleSync_with_core_triggered();
void syncWithCoreToggled(bool checked);
void on_showHistoToolButton_clicked();
void historyNext();
void historyPrev();
void clear();
private:
void scrollOutputToEnd();
void historyAdd(const QString &input);
void invalidateHistoryPosition();
Ui::ConsoleWidget *ui;
QRCore *core;
QList<QAction *> actions;
bool debugOutputEnabled;
int maxHistoryEntries;
int lastHistoryPosition;
QStringList history;
};
#endif // CONSOLEWIDGET_H

View File

@ -30,7 +30,7 @@
<number>0</number>
</property>
<item>
<widget class="QPlainTextEdit" name="consoleOutputTextEdit">
<widget class="QPlainTextEdit" name="outputTextEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -81,7 +81,7 @@
<number>3</number>
</property>
<item>
<widget class="QLineEdit" name="consoleInputLineEdit">
<widget class="QLineEdit" name="inputLineEdit">
<property name="font">
<font>
<pointsize>12</pointsize>
@ -90,9 +90,6 @@
<property name="styleSheet">
<string notr="true">border-top: 1px solid rgb(255, 255, 255);</string>
</property>
<property name="text">
<string/>
</property>
<property name="frame">
<bool>false</bool>
</property>
@ -105,54 +102,7 @@
</widget>
</item>
<item>
<widget class="QToolButton" name="showHistoToolButton">
<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">
<widget class="QToolButton" name="execButton">
<property name="toolTip">
<string>Execute command</string>
</property>