store history using sqlite, add suggestion with open

This commit is contained in:
2026-10-15 23:22:32 +02:00
parent a841178cf7
commit 13c1488b0e
12 changed files with 435 additions and 5 deletions
+58 -1
View File
@@ -2,6 +2,7 @@
#include "adblock.h"
#include "commandline.h"
#include "completionview.h"
#include "hints.h"
#include "statusbar.h"
#include "urlutil.h"
@@ -11,8 +12,10 @@
#include <QEvent>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QRegularExpression>
#include <QVBoxLayout>
#include <QWebEnginePage>
#include <QWebEngineProfile>
#include <QWebEngineScript>
#include <QWebEngineView>
#include <QWidget>
@@ -21,6 +24,14 @@ namespace {
constexpr int kScrollStep = 60;
constexpr double kZoomStep = 0.1;
constexpr int kCompletionLimit = 100;
// ":open <query>" or ":o <query>"; captures the prefix and the query.
const QRegularExpression &openCommand()
{
static const QRegularExpression re(QStringLiteral("^(:(?:open|o) +)(.*)$"));
return re;
}
} // namespace
@@ -31,6 +42,7 @@ BrowserWindow::BrowserWindow(QWebEngineProfile *profile, AdBlocker *adBlocker,
, m_view(new QWebEngineView(profile, this))
, m_status(new StatusBar(this))
, m_commandLine(new CommandLine(this))
, m_completion(new CompletionView(this))
{
setupUi();
connectView();
@@ -42,6 +54,8 @@ BrowserWindow::BrowserWindow(QWebEngineProfile *profile, AdBlocker *adBlocker,
connect(m_commandLine, &CommandLine::cancelled, this, [this] {
setMode(Mode::Normal);
});
connect(m_commandLine, &QLineEdit::textEdited, this, &BrowserWindow::updateCompletion);
connect(m_commandLine, &CommandLine::completionCycled, this, &BrowserWindow::cycleCompletion);
connect(m_adBlocker, &AdBlocker::message, m_status, &StatusBar::showMessage);
qApp->installEventFilter(this);
@@ -61,6 +75,7 @@ void BrowserWindow::setupUi()
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(m_view, 1);
layout->addWidget(m_completion);
layout->addWidget(m_status);
layout->addWidget(m_commandLine);
setCentralWidget(central);
@@ -80,11 +95,17 @@ void BrowserWindow::connectView()
connect(m_view, &QWebEngineView::titleChanged, this, [this](const QString &title) {
setWindowTitle(title.isEmpty() ? QStringLiteral("att")
: QStringLiteral("%1 - att").arg(title));
m_history.updateTitle(m_view->url(), title);
});
connect(m_view, &QWebEngineView::loadFinished, this, [this](bool ok) {
m_status->setProgress(100);
if (!ok)
if (!ok) {
m_status->showMessage(QStringLiteral("Failed to load page"), true);
return;
}
// Here rather than on urlChanged, which also fires for every redirect
// hop and in-page #anchor jump.
m_history.addVisit(m_view->url(), m_view->title());
});
}
@@ -96,6 +117,9 @@ void BrowserWindow::setMode(Mode mode)
m_mode = mode;
m_pendingKeys.clear();
if (mode != Mode::Command)
m_completion->setEntries({});
switch (mode) {
case Mode::Normal:
m_status->setMode(QString());
@@ -109,6 +133,7 @@ void BrowserWindow::setMode(Mode mode)
break;
case Mode::Command:
m_status->hide();
updateCompletion();
break;
case Mode::Hint:
m_status->setMode(QStringLiteral("hint"));
@@ -363,6 +388,14 @@ void BrowserWindow::runCommand(const QString &command)
m_view->forward();
} else if (name == QLatin1String("stop")) {
m_view->stop();
} else if (name == QLatin1String("history-clear")) {
if (m_history.clear()) {
// Also forget Chromium's own record, which colours visited links.
m_view->page()->profile()->clearAllVisitedLinks();
m_status->showMessage(QStringLiteral("History cleared"));
} else {
m_status->showMessage(QStringLiteral("Could not clear history"), true);
}
} else if (name == QLatin1String("adblock-update")) {
m_adBlocker->update();
} else if (name == QLatin1String("adblock-toggle")) {
@@ -374,6 +407,30 @@ void BrowserWindow::runCommand(const QString &command)
}
}
void BrowserWindow::updateCompletion()
{
const QRegularExpressionMatch match = openCommand().match(m_commandLine->text());
if (!match.hasMatch()) {
m_completion->setEntries({});
return;
}
const QStringList words = match.captured(2).split(QLatin1Char(' '), Qt::SkipEmptyParts);
m_completion->setEntries(m_history.search(words, kCompletionLimit));
}
void BrowserWindow::cycleCompletion(int step)
{
if (!m_completion->isVisible())
return;
const QString url = m_completion->cycle(step);
if (url.isEmpty())
return;
// Keep whichever of ":o " / ":open " the user typed.
const QRegularExpressionMatch match = openCommand().match(m_commandLine->text());
const QString prefix = match.hasMatch() ? match.captured(1) : QStringLiteral(":open ");
m_commandLine->setCommandText(prefix + url);
}
void BrowserWindow::openUrl(const QString &input)
{
const QUrl url = urlutil::fromUserInput(input);