aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO.org8
-rw-r--r--include/CommandParser.hpp9
-rw-r--r--include/LuaRuntime.hpp2
-rw-r--r--include/completion/CommandsModel.hpp32
-rw-r--r--include/completion/Completer.hpp14
-rw-r--r--include/completion/CompleterDelegate.hpp15
-rw-r--r--include/widgets/CommandInput.hpp3
-rw-r--r--src/CommandParser.cpp10
-rw-r--r--src/LuaRuntime.cpp4
-rw-r--r--src/completion/CommandsModel.cpp42
-rw-r--r--src/completion/Completer.cpp46
-rw-r--r--src/completion/CompleterDelegate.cpp40
-rw-r--r--src/widgets/CommandInput.cpp40
-rw-r--r--src/widgets/MainWindow.cpp11
-rwxr-xr-xtemplates/qclass.sh34
15 files changed, 287 insertions, 23 deletions
diff --git a/TODO.org b/TODO.org
index d101052..7c0ff36 100644
--- a/TODO.org
+++ b/TODO.org
@@ -1,14 +1,18 @@
** Current
- [X] Multi tabs
- [X] Open in new tab/new window
-- [ ] Lua for command input
+- [X] Lua for command input
+- [X] Simpler shell-ey/vimscript-ey language for command input
+- [X] Command completion
+- [ ] Buffer list/search
- [ ] Modal keys
- [ ] Tab history navigation
+- [ ] Refactor MainWindow
** Next
- [ ] History persistance
+- [ ] History completion
- [ ] Sketch lua api for managing ui
-- [ ] Simpler shell-ey/vimscript-ey language for command input
** Later
- [ ] Fullscreen
diff --git a/include/CommandParser.hpp b/include/CommandParser.hpp
index 128b5f4..dea04c5 100644
--- a/include/CommandParser.hpp
+++ b/include/CommandParser.hpp
@@ -12,13 +12,10 @@ enum CommandType {
TabPrev,
};
-class Cmd {
-public:
+struct Cmd {
CommandType command;
- QStringList args;
-
- Cmd(CommandType command = Noop, QStringList args = QStringList())
- : command(command), args(args) {}
+ QString argsString;
+ QString rawInput;
};
class CommandParser {
diff --git a/include/LuaRuntime.hpp b/include/LuaRuntime.hpp
index 85bb1ae..96c0004 100644
--- a/include/LuaRuntime.hpp
+++ b/include/LuaRuntime.hpp
@@ -13,7 +13,7 @@ public:
return &inst;
}
- void evaluate(const char *code);
+ void evaluate(QString code);
protected:
LuaRuntime();
diff --git a/include/completion/CommandsModel.hpp b/include/completion/CommandsModel.hpp
new file mode 100644
index 0000000..6cb25f9
--- /dev/null
+++ b/include/completion/CommandsModel.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <QAbstractListModel>
+#include <QWidget>
+#include <QtCore>
+
+struct Command {
+ QString name;
+ QString description;
+};
+
+const QList<Command> commands = {
+ {.name = "open", .description = "Open a url in the current tab"},
+ {.name = "tabopen", .description = "Open a url in a new tab"},
+ {.name = "bnext", .description = "Go to next tab"},
+ {.name = "bprev", .description = "Go to previous tab"},
+};
+
+class CommandsModel : public QAbstractListModel {
+ Q_OBJECT
+
+public:
+ CommandsModel(QObject *parent = nullptr);
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+ QHash<int, QByteArray> roleNames() const override;
+ QVariant data(const QModelIndex &index,
+ int role = Qt::DisplayRole) const override;
+
+private:
+ QList<Command> items;
+};
diff --git a/include/completion/Completer.hpp b/include/completion/Completer.hpp
new file mode 100644
index 0000000..b67de61
--- /dev/null
+++ b/include/completion/Completer.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <QCompleter>
+#include <QWidget>
+#include <QtCore>
+
+class Completer : public QCompleter {
+ Q_OBJECT
+
+public:
+ Completer(QObject *parent = nullptr);
+
+ // bool eventFilter(QObject *watched, QEvent *event) override;
+};
diff --git a/include/completion/CompleterDelegate.hpp b/include/completion/CompleterDelegate.hpp
new file mode 100644
index 0000000..fc7a321
--- /dev/null
+++ b/include/completion/CompleterDelegate.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <QStyledItemDelegate>
+#include <QWidget>
+#include <QtCore>
+
+class CompleterDelegate : public QStyledItemDelegate {
+ Q_OBJECT
+
+public:
+ explicit CompleterDelegate(QObject *parent = nullptr);
+
+ void paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+};
diff --git a/include/widgets/CommandInput.hpp b/include/widgets/CommandInput.hpp
index a6ca0ed..3ce9500 100644
--- a/include/widgets/CommandInput.hpp
+++ b/include/widgets/CommandInput.hpp
@@ -1,8 +1,9 @@
#pragma once
#include <QBoxLayout>
-#include <QKeyEvent>
#include <QLineEdit>
+#include <QWidget>
+#include <QtCore>
class CommandInput : public QWidget {
Q_OBJECT
diff --git a/src/CommandParser.cpp b/src/CommandParser.cpp
index ccb84bc..7a8eb00 100644
--- a/src/CommandParser.cpp
+++ b/src/CommandParser.cpp
@@ -9,11 +9,15 @@ Cmd CommandParser::parse(QString input) {
auto parts = input.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
if (parts.isEmpty())
- return Cmd();
+ return {.command = Noop, .argsString = "", .rawInput = input};
+
+ auto cmdStr = parts.first();
+ auto cmd = toCommandType(cmdStr);
+ auto rawArgs = input.slice(cmdStr.length());
- auto cmd = toCommandType(parts.first());
parts.removeFirst();
- return Cmd(cmd, parts);
+
+ return {.command = cmd, .argsString = rawArgs, .rawInput = input};
}
CommandType CommandParser::toCommandType(QString cmd) {
diff --git a/src/LuaRuntime.cpp b/src/LuaRuntime.cpp
index 37845d3..6867b61 100644
--- a/src/LuaRuntime.cpp
+++ b/src/LuaRuntime.cpp
@@ -15,7 +15,9 @@ LuaRuntime::LuaRuntime() {
lua_setglobal(state, "web");
}
-void LuaRuntime::evaluate(const char *code) { luaL_dostring(state, code); }
+void LuaRuntime::evaluate(QString code) {
+ luaL_dostring(state, code.toStdString().c_str());
+}
int LuaRuntime::lua_onUrlOpen(lua_State *state) {
const char *url = luaL_optstring(state, 1, "");
diff --git a/src/completion/CommandsModel.cpp b/src/completion/CommandsModel.cpp
new file mode 100644
index 0000000..0f66233
--- /dev/null
+++ b/src/completion/CommandsModel.cpp
@@ -0,0 +1,42 @@
+#include <QAbstractListModel>
+#include <QWidget>
+#include <QtCore>
+
+#include "completion/CommandsModel.hpp"
+
+CommandsModel::CommandsModel(QObject *parent) : QAbstractListModel(parent) {
+ items = commands;
+}
+
+QVariant CommandsModel::data(const QModelIndex &index, int role) const {
+ if (!index.isValid() || index.row() >= items.size())
+ return QVariant();
+
+ const Command &item = items.at(index.row());
+
+ if (role == Qt::DisplayRole)
+ return item.name;
+ else if (role == Qt::ToolTipRole)
+ return item.description;
+ else if (role == Qt::UserRole)
+ return item.description;
+
+ return QVariant();
+}
+
+QHash<int, QByteArray> CommandsModel::roleNames() const {
+ QHash<int, QByteArray> roles;
+ roles[Qt::DisplayRole] = "name";
+ roles[Qt::UserRole] = "description";
+ return roles;
+}
+
+int CommandsModel::rowCount(const QModelIndex &parent) const {
+ Q_UNUSED(parent);
+ return items.size();
+}
+
+int CommandsModel::columnCount(const QModelIndex &parent) const {
+ Q_UNUSED(parent);
+ return 2; // name + description
+}
diff --git a/src/completion/Completer.cpp b/src/completion/Completer.cpp
new file mode 100644
index 0000000..96a363e
--- /dev/null
+++ b/src/completion/Completer.cpp
@@ -0,0 +1,46 @@
+#include <QAbstractItemView>
+#include <QHeaderView>
+#include <QLineEdit>
+#include <QTreeView>
+#include <QWidget>
+#include <QtCore>
+
+#include "completion/Completer.hpp"
+#include "completion/CompleterDelegate.hpp"
+
+const char *completerStyles = R"(
+ background-color: #111;
+ color: #fff;
+ border-radius: 0;
+)";
+
+Completer::Completer(QObject *parent) : QCompleter(parent) {
+ setFilterMode(Qt::MatchContains);
+ setCaseSensitivity(Qt::CaseInsensitive);
+ setCompletionRole(Qt::DisplayRole);
+ setCompletionMode(QCompleter::PopupCompletion);
+
+ auto treeView = new QTreeView();
+ setPopup(treeView);
+ treeView->setStyleSheet(completerStyles);
+ treeView->setItemDelegate(new CompleterDelegate(treeView));
+ treeView->setRootIsDecorated(false);
+ treeView->setUniformRowHeights(true);
+ treeView->header()->hide();
+ treeView->setColumnWidth(1, 160);
+
+ complete();
+}
+
+// bool Completer::eventFilter(QObject *watched, QEvent *event) {
+// auto isFocusIn = event->type() == QEvent::FocusIn;
+// auto isKeyRelease = event->type() == QEvent::KeyRelease;
+//
+// QLineEdit *lineEdit = qobject_cast<QLineEdit *>(watched);
+// bool isInputEmpty = lineEdit && lineEdit->text().isEmpty();
+//
+// if ((isFocusIn || isKeyRelease) && isInputEmpty)
+// complete();
+//
+// return QCompleter::eventFilter(watched, event);
+// }
diff --git a/src/completion/CompleterDelegate.cpp b/src/completion/CompleterDelegate.cpp
new file mode 100644
index 0000000..a35305f
--- /dev/null
+++ b/src/completion/CompleterDelegate.cpp
@@ -0,0 +1,40 @@
+#include <QPainter>
+#include <QWidget>
+#include <QtCore/qlogging.h>
+#include <QtCore/qnamespace.h>
+#include <QtCore>
+
+#include "completion/CompleterDelegate.hpp"
+
+CompleterDelegate::CompleterDelegate(QObject *parent)
+ : QStyledItemDelegate(parent) {}
+
+void CompleterDelegate::paint(QPainter *painter,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const {
+ painter->save();
+
+ // Style
+ if (option.state & QStyle::State_Selected) {
+ painter->fillRect(option.rect, QColor("#aaa"));
+ painter->setPen(Qt::black);
+ } else {
+ painter->fillRect(option.rect, QColor("#111"));
+ painter->setPen(Qt::white);
+ }
+
+ // Draw text
+ QString text;
+ QRect rect;
+ if (index.column() == 0) {
+ text = index.data(Qt::DisplayRole).toString();
+ rect = option.rect.adjusted(5, 0, 0, 0);
+ painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, text);
+ } else if (index.column() == 1) {
+ text = index.data(Qt::UserRole).toString();
+ rect = option.rect.adjusted(0, 0, -5, 0);
+ painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, text);
+ }
+
+ painter->restore();
+}
diff --git a/src/widgets/CommandInput.cpp b/src/widgets/CommandInput.cpp
index 464065a..3ea0f38 100644
--- a/src/widgets/CommandInput.cpp
+++ b/src/widgets/CommandInput.cpp
@@ -1,23 +1,57 @@
+#include <QCompleter>
#include <QHBoxLayout>
#include <QKeyEvent>
+#include <QLabel>
#include <QLineEdit>
+#include <QStringListModel>
#include <QVBoxLayout>
#include <QWidget>
+#include <QWidgetAction>
#include <QWindow>
+#include <QtCore/qnamespace.h>
+#include <QtCore/qstringlistmodel.h>
+#include <QtWidgets/qboxlayout.h>
+#include "completion/CommandsModel.hpp"
+#include "completion/Completer.hpp"
#include "widgets/CommandInput.hpp"
+const char *rootStyles = R"(
+ background-color: #000;
+ color: #fff;
+ border-radius: 0;
+)";
+
CommandInput::CommandInput(QString defaultInput, QWidget *parentNode)
: QWidget(parentNode) {
setContentsMargins(0, 0, 0, 0);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
- setStyleSheet("background-color: #000; color: #fff; border-radius: 0;");
+ setStyleSheet(rootStyles);
auto layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
- input = new QLineEdit(defaultInput, this);
+
+ auto listModel = new CommandsModel();
+ auto completer = new Completer(this);
+ completer->setModel(listModel);
+
+ auto cmdLineBox = new QWidget();
+ layout->addWidget(cmdLineBox);
+ auto cmdLineLayout = new QHBoxLayout();
+ cmdLineBox->setLayout(cmdLineLayout);
+ cmdLineBox->layout()->setContentsMargins(0, 0, 0, 0);
+ cmdLineLayout->setSpacing(0);
+
+ auto promptPrefix = new QLabel(tr(":"));
+ promptPrefix->setContentsMargins(0, 0, 0, 0);
+ input = new QLineEdit(defaultInput);
input->setFocusPolicy(Qt::StrongFocus);
- layout->addWidget(input);
+ input->setCompleter(completer);
+ // input->installEventFilter(completer);
+
+ cmdLineLayout->addWidget(promptPrefix);
+ cmdLineLayout->addWidget(input);
+
setFixedHeight(input->sizeHint().height());
}
diff --git a/src/widgets/MainWindow.cpp b/src/widgets/MainWindow.cpp
index 9ac5f12..3191719 100644
--- a/src/widgets/MainWindow.cpp
+++ b/src/widgets/MainWindow.cpp
@@ -37,7 +37,7 @@ MainWindow::MainWindow() {
luaRuntime = LuaRuntime::instance();
connect(luaRuntime, &LuaRuntime::urlOpenned, this,
[this](QString url, OpenType openType) {
- this->browserManager->openUrl(QUrl(url), openType);
+ browserManager->openUrl(QUrl(url), openType);
});
}
@@ -56,19 +56,18 @@ void MainWindow::showURLInput() {
void MainWindow::evaluateCommand(QString command) {
hideURLInput();
- // TODO: Temporary hack
CommandParser parser;
auto cmd = parser.parse(command);
switch (cmd.command) {
case CommandType::LuaEval:
- luaRuntime->evaluate(cmd.args.join(" ").toStdString().c_str());
+ luaRuntime->evaluate(cmd.argsString);
break;
case CommandType::Open:
- browserManager->openUrl(cmd.args.first(), OpenType::OpenUrl);
+ browserManager->openUrl(cmd.argsString, OpenType::OpenUrl);
break;
case CommandType::TabOpen:
- browserManager->openUrl(cmd.args.at(1), OpenType::OpenUrlInTab);
+ browserManager->openUrl(cmd.argsString, OpenType::OpenUrlInTab);
break;
case CommandType::TabNext:
browserManager->nextWebView();
@@ -88,7 +87,7 @@ void MainWindow::keyPressEvent(QKeyEvent *event) {
toggleURLInput();
} else if (combo.key() == Qt::Key_T &&
combo.keyboardModifiers().testFlag(Qt::ControlModifier)) {
- browserManager->createNewWebView(QUrl("https://duckduckgo.com"), true);
+ browserManager->createNewWebView(QUrl("https://lite.duckduckgo.com"), true);
} else if (combo.key() == Qt::Key_J &&
combo.keyboardModifiers().testFlag(Qt::ControlModifier)) {
browserManager->nextWebView();
diff --git a/templates/qclass.sh b/templates/qclass.sh
new file mode 100755
index 0000000..8ad085a
--- /dev/null
+++ b/templates/qclass.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env sh
+
+set -e -o pipefail
+
+class_name="$1"
+path="$2"
+
+[ -z "$class_name" ] && exit 1
+
+mkdir -p "./include/$path";
+mkdir -p "./src/$path";
+
+# Header
+echo "#pragma once
+
+#include <QtCore>
+#include <QWidget>
+
+class $class_name : public QWidget {
+ Q_OBJECT
+
+ public:
+ $class_name();
+};
+" > "./include/$path/$class_name.hpp"
+
+# Implementation
+echo "#include <QtCore>
+#include <QWidget>
+
+#include \"$path/$class_name.hpp\"
+
+$class_name::$class_name(): QWidget() {}
+" > "./src/$path/$class_name.cpp"