aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--TODO.org70
-rw-r--r--config.lua57
-rw-r--r--spec/WebViewStackSpec.cpp26
-rw-r--r--src/InputMediator.cpp7
-rw-r--r--src/InputMediator.hpp5
-rw-r--r--src/LuaRuntime.cpp122
-rw-r--r--src/LuaRuntime.hpp21
-rw-r--r--src/main.cpp1
-rw-r--r--src/widgets/MainWindow.cpp4
-rw-r--r--src/widgets/WebViewStack.cpp1
-rw-r--r--src/widgets/WebViewStack.hpp5
11 files changed, 188 insertions, 131 deletions
diff --git a/TODO.org b/TODO.org
index a2b3749..aae520b 100644
--- a/TODO.org
+++ b/TODO.org
@@ -5,35 +5,75 @@
- [X] event loop for async work
- [X] example: trigger dmenu for simple selection
- [X] Fix segfault on quit
-- [ ] Tab history navigation
-- [ ] Tab next/prev
-- [ ] Get tab list api
-- [ ] Assign ID to each tab (reference in lua api)
-- [ ] Tab select by id
-- [ ] Config loading and runtime path
+- [X] Tab history navigation
+- [X] Tab next/prev
+- [X] Get tab list api
+- [X] Assign ID to each tab (reference in lua api)
+- [X] Tab select by id
- [ ] Multi-window
+- [ ] Handle resource cleanup + signal disconnecting
+- [ ] Socket for opening window in current session (lua eval)
+- [ ] Log stdout, errors and results from lua somewhere
+- [ ] Events/autocommands
+- [ ] History storage
+- [ ] History completion
** Next
+- [ ] Config loading and runtime path
- [ ] Conflict in keymap (keymap already exists)
-- [ ] History persistance
-- [ ] History completion
-- [ ] Open url parsing (add protocol if missing, remove quotes, etc)
-- [ ] Destructor for completer instance
+- [ ] Open url sanitize/humanize (add protocol if missing, remove quotes, etc)
- [ ] Bookmarking
+- [ ] Downloading
** Later
- [ ] Support multiple modes for keymap.set: `web.keymap.set({'n','i'}, ...)`
-- [ ] Search aliasing (lua api)
-- [ ] downloading
- [ ] Search text in page
- [ ] Notifications
- [ ] Dev tools
-- [ ] Search engine search if its not a url / aliases
+- [ ] Permission requests handling
+- [ ] Pipe page contents to external program
+- [ ] `web.inspect`
** Later later
- [ ] Fullscreen
+- [ ] Custom file picker
- [ ] remote debugging with cdp (spider-repl)
-- [ ] Pipe page contents to external program
** Maybe
-- [ ] Custom file picker
+- [ ] Floating webviews?
+- [ ] Move modal keymap management into lua?
+
+** Notes
+- Tab = 0, nil, none: Current tab
+- Win = 0: Current window
+- Win = nil, none: All windows or current window based on operation
+- Keep track of last focused window (current window is last focused)
+#+begin_src lua
+web.tab.set_url(url) -- Set url for current tab
+web.tab.set_url(url, { tab = 1 }) -- Set url for tab 1
+web.tab.set_url(url, { tab = 1, win = 1 }) -- Set url for tab 1 in win 1
+web.tab.set_url(url, { win = 1 }) -- Set url for current tab in win 1
+
+web.tab.new(url, { win = 1 }) -- New tab in win 1
+web.tab.new(url) -- New tab in current window
+
+web.tab.close({ tab = 1 }) -- Close tab 1
+web.tab.close() -- Close current tab in current window
+
+web.tab.list({ win = nil }) -- List all tabs
+web.tab.list({ win = 1 }) -- List win 1 tabs
+
+web.tab.select({ tab = 1, win = 1 }) -- Select win 1 tab
+web.tab.select({ tab = 1, win = 0 }) -- Select tab 1 in current window
+web.tab.select({ tab = 1 }) -- Select tab 1
+
+web.tab.current({ win = 1 }) -- Current tab in win 1
+web.tab.current() -- Current tab in current win
+
+web.history.back(1) -- Back for current tab in current window
+web.history.back(1, { tab = 1 }) -- Back for tab 1
+web.history.back(1, { win = 1 }) -- Back for current tab in win 1
+
+web.keymap.set('n', '<c-r>', ..., { tab = 1 }) -- Set keymap for tab 1
+web.keymap.set('n', '<c-r>', ..., { win = 1 }) -- Set keymap for win 1
+#+end_src
diff --git a/config.lua b/config.lua
index 053db68..b98c4a1 100644
--- a/config.lua
+++ b/config.lua
@@ -5,12 +5,23 @@ web = web
--- @type table
uv = uv
+local function trim(s) return s:gsub("^%s*(.-)%s*$", "%1") end
+
+local function get_current_tab_index()
+ local currentTab = web.tabs.current();
+ for index, tab in ipairs(web.tabs.list()) do
+ if tab.id == currentTab then
+ return index
+ end
+ end
+end
+
local Dmenu = {}
function Dmenu.select(list, opts, callback)
local selection = nil
local stdin = uv.new_pipe();
local stdout = uv.new_pipe();
- local args = { '-p', opts.prompt or '>' }
+ local args = { '-p', opts.prompt or '>', '-it', opts.input or '' }
uv.spawn('dmenu', { args = args, stdio = { stdin, stdout, nil } }, function(code)
uv.close(stdout)
uv.close(stdin)
@@ -31,10 +42,6 @@ function Dmenu.select(list, opts, callback)
uv.shutdown(stdin)
end
-local function trim(s)
- return s:gsub("^%s*(.-)%s*$", "%1")
-end
-
local urls = {
'https://excalidraw.com',
'https://lite.duckduckgo.com',
@@ -57,10 +64,20 @@ web.keymap.set('n', '<s-o>', function()
web.open(trim(result))
end)
end)
+-- Update current url
+web.keymap.set('n', '<c-l>', function()
+ local tabs = web.tabs.list()
+ local tab = tabs[get_current_tab_index()];
+ if tab == nil then return end
+ Dmenu.select(urls, { prompt = 'Set url:', input = tab.url }, function(err, result)
+ if err or not result then return end
+ web.open(trim(result))
+ end)
+end)
-- Run lua code
web.keymap.set('n', 'q', function()
- Dmenu.select({}, { prompt = 'Lua >' }, function(err, result)
+ Dmenu.select({}, { prompt = 'Lua:' }, function(err, result)
if err or not result then return end
local run, run_err = load(result)
if run_err then print(run_err) end
@@ -71,30 +88,27 @@ end)
-- History back/forward
web.keymap.set('n', '<s-h>', function() web.history.back(); end)
web.keymap.set('n', '<s-l>', function() web.history.forward(); end)
+
+-- Close tab
web.keymap.set('n', '<c-w>', function() web.tabs.close(); end)
-- Tab select
web.keymap.set('n', 'b', function()
- local tabsList = {}
- for _, tab in ipairs(web.tabs.list()) do
- table.insert(tabsList, tab.id .. ': ' .. tab.title .. ' (' .. tab.url)
+ local tabs_list = {}
+ local tabs = web.tabs.list()
+ for index, tab in ipairs(web.tabs.list()) do
+ table.insert(tabs_list, index .. ': ' .. tab.title .. ' (' .. tab.url .. ')')
end
- Dmenu.select(tabsList, { prompt = 'Tab:' }, function(err, result)
+ Dmenu.select(tabs_list, { prompt = 'Tab:' }, function(err, result)
if err or not result then return end
- local id, _ = trim(result):gsub("%s*:.*$", "")
- web.tabs.select(id)
+ local index_str, _ = trim(result):gsub("%s*:.*$", "")
+ local index = tonumber(index_str)
+ if tabs[index] then
+ web.tabs.select(tabs[index].id)
+ end
end)
end)
-local function get_current_tab_index()
- local currentTab = web.tabs.current();
- for index, tab in ipairs(web.tabs.list()) do
- if tab.id == currentTab then
- return index
- end
- end
-end
-
-- Next tab
web.keymap.set('n', 'tn', function()
local tabs = web.tabs.list()
@@ -106,7 +120,6 @@ end)
-- Prev tab
web.keymap.set('n', 'tp', function()
- print('-----------')
local tabs = web.tabs.list()
if #tabs <= 1 then return end
local index = get_current_tab_index() - 1;
diff --git a/spec/WebViewStackSpec.cpp b/spec/WebViewStackSpec.cpp
index 4324976..aed3748 100644
--- a/spec/WebViewStackSpec.cpp
+++ b/spec/WebViewStackSpec.cpp
@@ -172,15 +172,15 @@ private slots:
// }
// }
- void test_close_webview() {
- context("when closeWebView is called");
- context("- with out of bounds index");
+ void test_close() {
+ context("when close is called");
+ context("- with invalid id");
it("does nothing") {
Configuration configuration;
WebViewStack webview_stack(&configuration);
webview_stack.open_url(QUrl("https://a.com"), OpenType::OpenUrl);
- webview_stack.close(1);
+ webview_stack.close(99);
QCOMPARE(webview_stack.count(), 1);
QCOMPARE(webview_stack.urls(),
@@ -189,7 +189,7 @@ private slots:
QCOMPARE(webview_stack.current_url(), QUrl("https://a.com"));
}
- context("when closeWebView is called");
+ context("when close is called on current webview");
context("- and there is only 1 tab");
it("closes the tab and opens empty tab in its place") {
Configuration configuration;
@@ -197,7 +197,7 @@ private slots:
webview_stack.open_url(QUrl("https://a.com"), OpenType::OpenUrl);
QCOMPARE(webview_stack.count(), 1);
- webview_stack.close(0);
+ webview_stack.close(webview_stack.current_webview_id());
QCOMPARE(webview_stack.count(), 1);
QCOMPARE(webview_stack.urls(),
@@ -206,8 +206,8 @@ private slots:
QCOMPARE(webview_stack.current_url(), configuration.new_tab_url);
}
- context("when closeWebView is called");
- context("- with the current tab index");
+ context("when close is called");
+ context("- with the current tab id");
context("- and there are some tabs after");
it("closes the tab and focuses the next tab") {
Configuration configuration;
@@ -217,7 +217,7 @@ private slots:
QCOMPARE(webview_stack.count(), 3);
QCOMPARE(webview_stack.current_webview_index(), 1);
- webview_stack.close(1);
+ webview_stack.close(webview_stack.current_webview_id());
QCOMPARE(webview_stack.count(), 2);
QCOMPARE(webview_stack.urls(),
@@ -227,10 +227,10 @@ private slots:
QCOMPARE(webview_stack.current_url(), QUrl("https://a2.com"));
}
- context("when closeWebView is called");
- context("- with the current tab index");
+ context("when close is called");
+ context("- with the current tab id");
context("- which is the last tab");
- it("closes the tab and focusses previous tab") {
+ it("closes the tab and focuses previous tab") {
Configuration configuration;
WebViewStack webview_stack(&configuration);
webview_stack.open_url(QUrl("https://a1.com"), OpenType::OpenUrlInBgTab);
@@ -238,7 +238,7 @@ private slots:
QCOMPARE(webview_stack.count(), 3);
QCOMPARE(webview_stack.current_webview_index(), 2);
- webview_stack.close(2);
+ webview_stack.close(webview_stack.current_webview_id());
QCOMPARE(webview_stack.count(), 2);
QCOMPARE(webview_stack.urls(),
diff --git a/src/InputMediator.cpp b/src/InputMediator.cpp
index 91393c2..175a1b4 100644
--- a/src/InputMediator.cpp
+++ b/src/InputMediator.cpp
@@ -13,19 +13,22 @@ InputMediator::InputMediator(WebViewStack *webview_stack,
KeymapEvaluator *keymap_evaluator)
: webview_stack(webview_stack), lua_runtime(lua_runtime),
keymap_evaluator(keymap_evaluator) {
- connect(lua_runtime, &LuaRuntime::url_opened, webview_stack,
- &WebViewStack::open_url);
connect(lua_runtime, &LuaRuntime::keymap_added, this,
&InputMediator::add_keymap);
+
connect(lua_runtime, &LuaRuntime::history_back_requested, webview_stack,
&WebViewStack::webview_history_back);
connect(lua_runtime, &LuaRuntime::history_forward_requested, webview_stack,
&WebViewStack::webview_history_forward);
+
+ connect(lua_runtime, &LuaRuntime::url_opened, webview_stack,
+ &WebViewStack::open_url);
connect(lua_runtime, &LuaRuntime::webview_closed, webview_stack,
&WebViewStack::close);
connect(lua_runtime, &LuaRuntime::webview_selected, webview_stack,
&WebViewStack::focus_webview);
+ // TODO: Think of how to handle this for multi-window
lua_runtime->set_current_tab_id_fetcher(
[this]() { return this->webview_stack->current_webview_id(); });
lua_runtime->set_webview_data_list_fetcher(
diff --git a/src/InputMediator.hpp b/src/InputMediator.hpp
index d1dc122..63c7a76 100644
--- a/src/InputMediator.hpp
+++ b/src/InputMediator.hpp
@@ -16,12 +16,9 @@ public:
KeymapEvaluator *keymap_evaluator);
~InputMediator() override;
- // DELEGATE(webview_stack, open_url, open_url)
- // DELEGATE(webview_stack, current_url, current_url)
- // DELEGATE(webview_stack, close_current, close_current_webview)
DELEGATE(keymap_evaluator, evaluate, evaluate_keymap)
-protected:
+protected slots:
void add_keymap(const QString &mode_string, const QString &keyseq,
std::function<void()> action);
diff --git a/src/LuaRuntime.cpp b/src/LuaRuntime.cpp
index 0b5e698..edf3b74 100644
--- a/src/LuaRuntime.cpp
+++ b/src/LuaRuntime.cpp
@@ -60,6 +60,60 @@ QVariant LuaRuntime::evaluate_sync(const QString &code) {
return get_lua_value(-1); // TODO: error handling
}
+void LuaRuntime::load_file(const QString &path) {
+ queue_task([this, path]() {
+ preserve_top(state, {
+ if (luaL_dofile(state, path.toStdString().c_str()) != LUA_OK) {
+ qDebug() << "Load file error:" << lua_tostring(state, -1);
+ }
+ })
+ });
+}
+
+void LuaRuntime::init_web_lib() {
+ // NOLINTBEGIN(modernize-avoid-c-arrays)
+
+ // web
+ luaL_Reg web[] = {
+ {"open", &LuaRuntime::lua_open_url},
+ {nullptr, nullptr},
+ };
+ luaL_newlib(state, web);
+ lua_setglobal(state, web_global_name);
+ lua_getglobal(state, web_global_name);
+
+ // Keymap api (web.keymap)
+ luaL_Reg webkeymap[] = {
+ {"set", &LuaRuntime::lua_keymap_set},
+ {nullptr, nullptr},
+ };
+ luaL_newlib(state, webkeymap);
+ lua_setfield(state, -2, "keymap");
+
+ // Tab actions (web.tabs)
+ luaL_Reg webtabs[] = {
+ {"close", &LuaRuntime::lua_tab_close},
+ {"new", &LuaRuntime::lua_tab_create},
+ {"current", &LuaRuntime::lua_tab_current},
+ {"list", &LuaRuntime::lua_tab_list},
+ {"select", &LuaRuntime::lua_tab_select},
+ {nullptr, nullptr},
+ };
+ luaL_newlib(state, webtabs);
+ lua_setfield(state, -2, "tabs");
+
+ // History navigation
+ luaL_Reg webhistory[] = {
+ {"back", &LuaRuntime::lua_history_back},
+ {"forward", &LuaRuntime::lua_history_forward},
+ {nullptr, nullptr},
+ };
+ luaL_newlib(state, webhistory);
+ lua_setfield(state, -2, "history");
+
+ // NOLINTEND(modernize-avoid-c-arrays)
+}
+
QVariant LuaRuntime::get_lua_value(int idx, QVariant default_value) {
if (lua_isnoneornil(state, idx))
return default_value;
@@ -76,21 +130,21 @@ QVariant LuaRuntime::get_lua_value(int idx, QVariant default_value) {
return lua_tostring(state, idx);
}
-int LuaRuntime::lua_on_url_open(lua_State *state) {
+int LuaRuntime::lua_open_url(lua_State *state) {
const char *url = luaL_optstring(state, 1, "");
auto *runtime = LuaRuntime::instance();
emit runtime->url_opened(url, OpenType::OpenUrl);
return 1;
}
-int LuaRuntime::lua_on_url_tab_open(lua_State *state) {
+int LuaRuntime::lua_tab_create(lua_State *state) {
const char *url = luaL_optstring(state, 1, "");
auto *runtime = LuaRuntime::instance();
emit runtime->url_opened(url, OpenType::OpenUrlInTab);
return 1;
}
-int LuaRuntime::lua_add_keymap(lua_State *state) {
+int LuaRuntime::lua_keymap_set(lua_State *state) {
const char *mode = lua_tostring(state, 1);
const char *keyseq = lua_tostring(state, 2);
@@ -113,68 +167,14 @@ int LuaRuntime::lua_add_keymap(lua_State *state) {
return 1;
}
-void LuaRuntime::load_file(const QString &path) {
- queue_task([this, path]() {
- preserve_top(state, {
- if (luaL_dofile(state, path.toStdString().c_str()) != LUA_OK) {
- qDebug() << "Load file error:" << lua_tostring(state, -1);
- }
- })
- });
-}
-
-void LuaRuntime::init_web_lib() {
- // NOLINTBEGIN(modernize-avoid-c-arrays)
-
- // web
- luaL_Reg web[] = {
- {"open", &LuaRuntime::lua_on_url_open},
- {nullptr, nullptr},
- };
- luaL_newlib(state, web);
- lua_setglobal(state, web_global_name);
- lua_getglobal(state, web_global_name);
-
- // Keymap api (web.keymap)
- luaL_Reg webkeymap[] = {
- {"set", &LuaRuntime::lua_add_keymap},
- {nullptr, nullptr},
- };
- luaL_newlib(state, webkeymap);
- lua_setfield(state, -2, "keymap");
-
- // Tab actions (web.tabs)
- luaL_Reg webtabs[] = {
- {"close", &LuaRuntime::lua_tab_closed},
- {"new", &LuaRuntime::lua_on_url_tab_open},
- {"current", &LuaRuntime::lua_get_current_tab_id},
- {"list", &LuaRuntime::lua_get_tab_list},
- {"select", &LuaRuntime::lua_tab_selected},
- {nullptr, nullptr},
- };
- luaL_newlib(state, webtabs);
- lua_setfield(state, -2, "tabs");
-
- // History navigation
- luaL_Reg webhistory[] = {
- {"back", &LuaRuntime::lua_history_back},
- {"forward", &LuaRuntime::lua_history_forward},
- {nullptr, nullptr},
- };
- luaL_newlib(state, webhistory);
- lua_setfield(state, -2, "history");
-
- // NOLINTEND(modernize-avoid-c-arrays)
-}
-
-int LuaRuntime::lua_get_current_tab_id(lua_State *state) {
+int LuaRuntime::lua_tab_current(lua_State *state) {
auto *runtime = LuaRuntime::instance();
auto tab_id = runtime->fetch_current_tab_id();
lua_pushinteger(state, tab_id);
return 1;
}
-int LuaRuntime::lua_get_tab_list(lua_State *state) {
+int LuaRuntime::lua_tab_list(lua_State *state) {
auto *runtime = LuaRuntime::instance();
auto tabs = runtime->fetch_webview_data_list();
lua_newtable(state);
@@ -235,7 +235,7 @@ int LuaRuntime::lua_history_forward(lua_State *state) {
return 1;
}
-int LuaRuntime::lua_tab_closed(lua_State *state) {
+int LuaRuntime::lua_tab_close(lua_State *state) {
auto *runtime = LuaRuntime::instance();
WebViewId tab_id;
@@ -249,7 +249,7 @@ int LuaRuntime::lua_tab_closed(lua_State *state) {
return 1;
}
-int LuaRuntime::lua_tab_selected(lua_State *state) {
+int LuaRuntime::lua_tab_select(lua_State *state) {
if (lua_isnoneornil(state, 1))
return 1; // TODO: return nil (for others too)
diff --git a/src/LuaRuntime.hpp b/src/LuaRuntime.hpp
index 209f8b7..e534d0e 100644
--- a/src/LuaRuntime.hpp
+++ b/src/LuaRuntime.hpp
@@ -40,29 +40,30 @@ public:
DEFINE_FETCHER(QList<WebViewData>(), webview_data_list)
signals:
- void url_opened(QString url, OpenType open_type);
void evaluation_completed(QVariant value);
void evaluation_failed(QString value);
- void keymap_added(QString mode, QString keyseq, std::function<void()>);
void history_back_requested(WebViewId webview_id, qsizetype history_index);
void history_forward_requested(WebViewId webview_id, qsizetype history_index);
+ void keymap_added(QString mode, QString keyseq, std::function<void()>);
+ void url_opened(QString url, OpenType open_type);
void webview_closed(WebViewId webview_id);
void webview_selected(WebViewId webview_id);
- // void output_produced(QVariant value);
protected:
LuaRuntime();
~LuaRuntime() override;
void init_web_lib();
- static int lua_on_url_open(lua_State *state);
- static int lua_on_url_tab_open(lua_State *state);
- static int lua_add_keymap(lua_State *state);
- static int lua_get_current_tab_id(lua_State *state);
+
+ // Lua api
static int lua_history_back(lua_State *state);
static int lua_history_forward(lua_State *state);
- static int lua_tab_closed(lua_State *state);
- static int lua_get_tab_list(lua_State *state);
- static int lua_tab_selected(lua_State *state);
+ static int lua_keymap_set(lua_State *state);
+ static int lua_open_url(lua_State *state);
+ static int lua_tab_close(lua_State *state);
+ static int lua_tab_create(lua_State *state);
+ static int lua_tab_current(lua_State *state);
+ static int lua_tab_list(lua_State *state);
+ static int lua_tab_select(lua_State *state);
private:
lua_State *state;
diff --git a/src/main.cpp b/src/main.cpp
index 4a30628..d676688 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,5 +1,4 @@
#include <QApplication>
-#include <QMainWindow>
#include "LuaRuntime.hpp"
#include "widgets/MainWindow.hpp"
diff --git a/src/widgets/MainWindow.cpp b/src/widgets/MainWindow.cpp
index c3c392e..d66611b 100644
--- a/src/widgets/MainWindow.cpp
+++ b/src/widgets/MainWindow.cpp
@@ -45,10 +45,10 @@ MainWindow::MainWindow() {
LuaRuntime::instance()->load_file("./config.lua");
// TODO: remove
- keymap_evaluator->add_keymap(KeyMode::Normal, "i", [keymap_evaluator]() {
+ keymap_evaluator->add_keymap(KeyMode::Normal, "i", [&keymap_evaluator]() {
keymap_evaluator->set_current_mode(KeyMode::Insert);
});
- keymap_evaluator->add_keymap(KeyMode::Insert, "<esc>", [keymap_evaluator]() {
+ keymap_evaluator->add_keymap(KeyMode::Insert, "<esc>", [&keymap_evaluator]() {
keymap_evaluator->set_current_mode(KeyMode::Normal);
});
keymap_evaluator->add_keymap(KeyMode::Normal, "<c-t>a",
diff --git a/src/widgets/WebViewStack.cpp b/src/widgets/WebViewStack.cpp
index fda4f28..aee4af9 100644
--- a/src/widgets/WebViewStack.cpp
+++ b/src/widgets/WebViewStack.cpp
@@ -103,6 +103,7 @@ void WebViewStack::close(WebViewId webview_id) {
// TODO: Focus on different webview
// focus_webview();
+ // TODO: Close window on empty
if (webview_list.isEmpty()) {
create_new_webview(configuration->new_tab_url, true);
}
diff --git a/src/widgets/WebViewStack.hpp b/src/widgets/WebViewStack.hpp
index b675af5..9df088e 100644
--- a/src/widgets/WebViewStack.hpp
+++ b/src/widgets/WebViewStack.hpp
@@ -31,12 +31,15 @@ public:
QWebEngineProfile *profile = new QWebEngineProfile,
QWidget *parent = nullptr);
- std::vector<QUrl> urls(); // TODO: Remove
QList<WebViewData> get_webview_list();
WebView *current_webview();
WebViewId current_webview_id();
uint32_t count();
QUrl current_url();
+
+ /// @deprecated TODO: Remove
+ std::vector<QUrl> urls();
+ /// @deprecated TODO: Remove
uint32_t current_webview_index();
protected: