diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-03 20:56:59 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-04 12:54:19 +0200 |
| commit | a6f73f77890d801ecd7209e97ba948c06fdd35c2 (patch) | |
| tree | bc3254e448f02c08d218b91246ee0233dab68334 /src | |
| parent | 45e5887f5b33e5f30d3c16187bb5d6e707983804 (diff) | |
| download | qtmaildir-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.cpp | 82 | ||||
| -rw-r--r-- | src/querycompleter.h | 31 |
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; +}; |
