summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-03 20:56:59 +0200
committerDanilo M. <danix@danix.xyz>2026-08-04 12:54:19 +0200
commita6f73f77890d801ecd7209e97ba948c06fdd35c2 (patch)
treebc3254e448f02c08d218b91246ee0233dab68334 /src
parent45e5887f5b33e5f30d3c16187bb5d6e707983804 (diff)
downloadqtmaildir-a6f73f77890d801ecd7209e97ba948c06fdd35c2.tar.gz
qtmaildir-a6f73f77890d801ecd7209e97ba948c06fdd35c2.zip
feat(completion): select candidates per context
tag: and is: share the tag list, since notmuch aliases them. path: offers each account maildir in both bare and recursive forms, the latter being what Account::scopedQuery builds and not something a user would guess. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src')
-rw-r--r--src/querycompleter.cpp82
-rw-r--r--src/querycompleter.h31
2 files changed, 113 insertions, 0 deletions
diff --git a/src/querycompleter.cpp b/src/querycompleter.cpp
index 77c62c1..6b4c69a 100644
--- a/src/querycompleter.cpp
+++ b/src/querycompleter.cpp
@@ -18,6 +18,8 @@
#include "querycompleter.h"
+#include "config.h"
+
#include <QCoreApplication>
namespace {
@@ -186,3 +188,83 @@ CompletionContext completionContext(const QString &text, int cursor)
}
return ctx;
}
+
+QueryCompleter::QueryCompleter(QLineEdit *edit, const Config &config,
+ QObject *parent)
+ : QObject(parent), m_edit(edit), m_config(config)
+{
+}
+
+void QueryCompleter::setTags(const QStringList &tags)
+{
+ m_tags = tags;
+}
+
+QList<CompletionEntry> QueryCompleter::entriesFor(
+ const CompletionContext &context) const
+{
+ if (context.kind == CompletionContext::None)
+ return {};
+
+ if (context.kind == CompletionContext::Prefix)
+ return prefixVocabulary();
+
+ // notmuch treats is:x as a synonym for tag:x, so both take the tag list.
+ if (context.prefix == QStringLiteral("tag")
+ || context.prefix == QStringLiteral("is")) {
+ QList<CompletionEntry> entries;
+ entries.reserve(m_tags.size());
+ for (const QString &tag : m_tags)
+ entries.append({ tag, QString() });
+ return entries;
+ }
+
+ if (context.prefix == QStringLiteral("date")) {
+ QList<CompletionEntry> entries;
+ const QList<CompletionEntry> vocabulary = dateVocabulary();
+ for (const CompletionEntry &entry : vocabulary) {
+ // Entries that are themselves ranges cannot go inside a range.
+ if (!context.allowRangeEntries
+ && entry.value.contains(QStringLiteral("..")))
+ continue;
+ entries.append(entry);
+ }
+ return entries;
+ }
+
+ if (context.prefix == QStringLiteral("mimetype")) {
+ QList<CompletionEntry> entries = mimetypeVocabulary();
+ entries.append(m_config.extraMimetypes());
+ return entries;
+ }
+
+ if (context.prefix == QStringLiteral("path")) {
+ QList<CompletionEntry> entries;
+ const QList<Account> accounts = m_config.accounts();
+ for (const Account &account : accounts) {
+ if (account.maildir.isEmpty())
+ continue;
+ entries.append({ account.maildir,
+ VocabularyStrings::tr("account directory") });
+ entries.append({ account.maildir + QStringLiteral("/**"),
+ VocabularyStrings::tr("and everything below it") });
+ }
+ return entries;
+ }
+
+ // from:, to:, folder:, subject:, attachment:, thread:, id: complete no
+ // values. Addresses need an enumerator libnotmuch does not expose;
+ // folder: matches a Maildir folder name that config cannot enumerate, and
+ // the rest are free text.
+ return {};
+}
+
+QStringList QueryCompleter::candidatesFor(const CompletionContext &context) const
+{
+ QStringList values;
+ const QList<CompletionEntry> entries = entriesFor(context);
+ values.reserve(entries.size());
+ for (const CompletionEntry &entry : entries)
+ values.append(entry.value);
+ return values;
+}
diff --git a/src/querycompleter.h b/src/querycompleter.h
index 7c00ce2..e9ffc64 100644
--- a/src/querycompleter.h
+++ b/src/querycompleter.h
@@ -19,10 +19,15 @@
#pragma once
#include <QList>
+#include <QObject>
#include <QString>
+#include <QStringList>
#include "completionentry.h"
+class Config;
+class QLineEdit;
+
/// Where the cursor sits in a query, and therefore what should be offered.
///
/// A plain value type produced by a pure function so the parsing rules can be
@@ -72,3 +77,29 @@ QList<CompletionEntry> dateVocabulary();
/// The built-in mimetypes, before the user's extra_mimetypes are appended.
QList<CompletionEntry> mimetypeVocabulary();
+
+/// Completion for the notmuch query bar.
+///
+/// completionContext() above decides which context the cursor sits in; this
+/// class owns the candidates offered for that context.
+class QueryCompleter : public QObject
+{
+ Q_OBJECT
+public:
+ /// `edit` may be null in tests that exercise candidate selection only.
+ QueryCompleter(QLineEdit *edit, const Config &config,
+ QObject *parent = nullptr);
+
+ /// Replaces the tag candidates. Called with the worker's allTagsReady.
+ void setTags(const QStringList &tags);
+
+ /// The candidate values for a context, in the order they are offered.
+ QStringList candidatesFor(const CompletionContext &context) const;
+
+private:
+ QList<CompletionEntry> entriesFor(const CompletionContext &context) const;
+
+ QLineEdit *m_edit = nullptr;
+ const Config &m_config;
+ QStringList m_tags;
+};