summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-03 20:41:57 +0200
committerDanilo M. <danix@danix.xyz>2026-08-04 12:54:06 +0200
commitf6f11e5142911f0c4a57b08d6425d1cf99187627 (patch)
treeb5c91d17469cc6d75f67aff0e0fd99474245ab51
parentffe78f9c626ddb8d661930f911e8c1622cddfabb (diff)
downloadqtmaildir-f6f11e5142911f0c4a57b08d6425d1cf99187627.tar.gz
qtmaildir-f6f11e5142911f0c4a57b08d6425d1cf99187627.zip
feat(completion): complete nothing inside a quoted literal
subject:"foo bar has the cursor in free text, where offering keywords would be wrong.
-rw-r--r--src/querycompleter.cpp15
-rw-r--r--tests/test_querycompleter.cpp18
2 files changed, 33 insertions, 0 deletions
diff --git a/src/querycompleter.cpp b/src/querycompleter.cpp
index 1e86c1b..45ef581 100644
--- a/src/querycompleter.cpp
+++ b/src/querycompleter.cpp
@@ -20,6 +20,18 @@
namespace {
+/// Whether the cursor sits inside a double-quoted literal. Counts quotes from
+/// the start: an odd count before the cursor means the quote is still open.
+bool insideQuotes(const QString &text, int cursor)
+{
+ int quotes = 0;
+ for (int i = 0; i < cursor; ++i) {
+ if (text.at(i) == QLatin1Char('"'))
+ ++quotes;
+ }
+ return (quotes % 2) != 0;
+}
+
/// Start of the token the cursor sits in. The boundary is whitespace or '(',
/// so "tag:inbox and su" has its last token starting at 14, not at 0.
int tokenStart(const QString &text, int cursor)
@@ -43,6 +55,9 @@ CompletionContext completionContext(const QString &text, int cursor)
if (cursor < 0 || cursor > text.size())
return ctx;
+ if (insideQuotes(text, cursor))
+ return ctx; // kind stays None
+
const int start = tokenStart(text, cursor);
const QString token = text.mid(start, cursor - start);
diff --git a/tests/test_querycompleter.cpp b/tests/test_querycompleter.cpp
index 4621b19..c86edac 100644
--- a/tests/test_querycompleter.cpp
+++ b/tests/test_querycompleter.cpp
@@ -33,6 +33,8 @@ private slots:
void valueReplaceSpanExcludesThePrefix();
void prefixIsLowercased();
void emptyValueAfterColonStillCompletes();
+ void insideQuotesCompletesNothing();
+ void afterClosedQuotesCompletesAgain();
};
void TestQueryCompleter::emptyTextCompletesPrefix()
@@ -103,5 +105,21 @@ void TestQueryCompleter::emptyValueAfterColonStillCompletes()
QCOMPARE(ctx.replaceLength, 0);
}
+void TestQueryCompleter::insideQuotesCompletesNothing()
+{
+ // subject:"foo bar| is a literal, not a keyword position.
+ const QString text = QStringLiteral("subject:\"foo bar");
+ const CompletionContext ctx = completionContext(text, text.size());
+ QCOMPARE(ctx.kind, CompletionContext::None);
+}
+
+void TestQueryCompleter::afterClosedQuotesCompletesAgain()
+{
+ const QString text = QStringLiteral("subject:\"foo bar\" and ta");
+ const CompletionContext ctx = completionContext(text, text.size());
+ QCOMPARE(ctx.kind, CompletionContext::Prefix);
+ QCOMPARE(ctx.stem, QStringLiteral("ta"));
+}
+
QTEST_MAIN(TestQueryCompleter)
#include "test_querycompleter.moc"