MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 96.9% 127 / 0 / 131
Functions: 96.2% 25 / 0 / 26
Branches: 87.1% 222 / 0 / 255

app/NamedCommandCatalog.cpp
Line Branch Exec Source
1 #include "NamedCommandCatalog.h"
2 #include <QCoreApplication>
3
4 namespace {
5
6 constexpr CommandContexts kMail = CommandContext::MainMail | CommandContext::MailTab | CommandContext::Search;
7 // Sprint 101 CB-06: folder-view commands that act on the selected result(s)
8 // in a search tab too.
9 constexpr CommandContexts kList = CommandContext::MainMail | CommandContext::Search;
10 constexpr CommandContexts kViews = kMail | CommandContext::Calendar | CommandContext::Task |
11 CommandContext::Contact | CommandContext::Rules;
12 constexpr CommandContexts kMailAndRules = kMail | CommandContext::Rules;
13 // Sprint 100 (D-29): commands that also work in the settings area.
14 constexpr CommandContexts kAnywhere = kViews | CommandContext::Settings;
15
16 175 NamedCommandValue on(const char *registryId = nullptr) {
17 return {.word = "on", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Turn on"), .keywords = "an ein einschalten enable",
18 175 .registryId = registryId};
19 }
20 200 NamedCommandValue off(const char *registryId = nullptr) {
21 return {.word = "off", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Turn off"), .keywords = "aus ausschalten disable",
22 200 .registryId = registryId};
23 }
24 } // namespace
25
26 // Sprint 103 S103.6.7: the people commands (NamedCommandCatalogPeople.cpp).
27 QList<NamedCommandSpec> peopleCommands();
28
29 // The command set of Sprint 98 §5.3 (D-14…D-17): one lowercase English word
30 // per command, variants as fixed values, no aliases. Keywords hold German
31 // and English search words; they find a command, they never run it. One
32 // function per group of §5.3 (rule 14: no function above 120 lines).
33 namespace {
34 // Selection (verbs).
35 25 QList<NamedCommandSpec> selectionCommands() {
36 return {
37 {.name = "reply", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mail"),
38 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Reply to the sender"), .contexts = kMail,
39 .registryId = "mail.reply", .argument = CommandArgument::Values, .syntax = "[all]", .example = "reply all",
40 .values = {{.word = "all", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Reply to all recipients"),
41 .keywords = "allen alle everyone",
42 .registryId = "mail.reply_all"}},
43 .keywords = "antworten answer respond", .featured = CommandContext::MainMail | CommandContext::MailTab},
44 {.name = "forward", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mail"),
45 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Forward the message"), .contexts = kMail,
46 .registryId = "mail.forward", .keywords = "weiterleiten fwd"},
47 {.name = "archive", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mail"),
48 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Archive the selected messages"),
49 .contexts = kList, .registryId = "mail.archive", .keywords = "ablegen archivieren",
50 .featured = CommandContext::MainMail},
51 {.name = "delete", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mail"),
52 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Move the selected messages to the trash"),
53 .contexts = kList, .registryId = "mail.delete",
54 .values = {{.word = "forever", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog",
55 "Delete for good (no trash, no undo)"),
56 .keywords = "endgültig unwiderruflich"}},
57 .keywords = "löschen papierkorb trash entfernen",
58 .destructive = true},
59 {.name = "junk", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mail"),
60 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mark the selected messages as junk"),
61 .contexts = kList, .registryId = "mail.junk", .keywords = "spam werbung", .destructive = true},
62 {.name = "mark", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mail"),
63 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mark the selected messages"), .contexts = kMail,
64 .argument = CommandArgument::Values, .argumentRequired = true,
65 .syntax = "<read∣unread∣important∣unimportant> …", .example = "mark read important",
66 .values = {{.word = "read", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mark as read"), .keywords = "gelesen",
67 .group = "seen"},
68 {.word = "unread", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mark as unread"), .keywords = "ungelesen neu",
69 .group = "seen"},
70 {.word = "important", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mark as important"),
71 .keywords = "wichtig stern star flag markiert", .group = "flagged"},
72 {.word = "unimportant", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Remove the important mark"),
73 .keywords = "unwichtig unstar unflag", .group = "flagged"}},
74 .keywords = "markieren kennzeichnen flag", .several = true},
75 {.name = "label", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mail"),
76 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Add a label"), .contexts = kMail,
77 .argument = CommandArgument::Label, .argumentRequired = true, .syntax = "<label> …", .example = "label $label1 Work",
78 .keywords = "markierung schlagwort etikett tag", .several = true, .featured = CommandContext::MainMail},
79 {.name = "unlabel", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mail"),
80 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Remove a label"), .contexts = kMail,
81 .argument = CommandArgument::Label, .argumentRequired = true, .syntax = "<label …∣all>", .example = "unlabel all",
82 .values = {{.word = "all", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Remove all labels"), .keywords = "alle",
83 .registryId = "label.clear", .alone = true}},
84 .keywords = "markierung entfernen untag", .several = true},
85 {.name = "move", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mail"),
86 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Move the selected messages to a folder"),
87 .contexts = kList, .registryId = "mail.manual_move", .argument = CommandArgument::Folder,
88 .syntax = "[folder]", .example = "move Projekte/MailJD", .keywords = "verschieben einsortieren einordnen",
89 .featured = CommandContext::MainMail},
90 {.name = "open", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mail"),
91 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Open the message in a tab"),
92 .contexts = kList, .registryId = "tab.open_mail", .keywords = "öffnen tab reiter"},
93 {.name = "undo", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mail"),
94 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Undo the last mail action"), .contexts = kMail,
95 .registryId = "undo.normal", .keywords = "rückgängig zurück"},
96 {.name = "reading", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Mail"),
97 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Choose how the message is shown"),
98 .contexts = kMail, .argument = CommandArgument::Values, .argumentRequired = true, .syntax = "<text∣light∣html>",
99 .example = "reading text",
100 .values = {{.word = "text", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Plain text"), .keywords = "klartext plain"},
101 {.word = "light", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Simple HTML without remote content"),
102 .keywords = "leicht einfach"},
103 {.word = "html", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Full HTML"), .keywords = "voll full original"}},
104 .keywords = "lesemodus darstellung ansicht"},
105
2/4
✓ Branch 21 → 22 taken 300 times.
✓ Branch 21 → 23 taken 25 times.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
325 };
106 25 }
107
108 // Create from text (singular nouns).
109 25 QList<NamedCommandSpec> createCommands() {
110 return {
111 {.name = "compose", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Create"),
112 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Write a new message"), .contexts = kViews,
113 .registryId = "compose.new", .argument = CommandArgument::Address, .syntax = "[address]",
114 .example = "compose mara@example.org", .keywords = "neu schreiben verfassen nachricht new mail",
115 .featured = CommandContext::Contact},
116 // Sprint 108 S108.9.5 (D-27): a template into the composer last focused.
117 {.name = "template", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Create"),
118 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Insert a template into the message"),
119 .contexts = kMail | CommandContext::Compose, .argument = CommandArgument::Text, .syntax = "[name]",
120 .example = "template Offer", .keywords = "vorlage textbaustein baustein",
121 .featured = CommandContext::Compose},
122 {.name = "task", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Create"),
123 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "New task from one line"), .contexts = kViews,
124 .registryId = "task.add", .argument = CommandArgument::TaskLine, .syntax = "[title /list @due !priority #tag]",
125 .example = "task Notes /MailJD @tomorrow !high #work", .keywords = "aufgabe todo erledigen",
126 .featured = CommandContext::MainMail | CommandContext::Task},
127 {.name = "event", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Create"),
128 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "New appointment from one line"),
129 .contexts = kViews, .argument = CommandArgument::EventLine, .syntax = "[title date time @place /calendar]",
130 .example = "event Dentist tomorrow 14:00 @Main Street", .keywords = "termin appointment treffen",
131 .featured = CommandContext::Calendar},
132 {.name = "rule", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Create"),
133 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "New server rule from one line"),
134 .contexts = kMailAndRules, .argument = CommandArgument::RuleLine, .syntax = "[conditions → actions]",
135 .example = "rule from:@github.com → Dev/CI read", .keywords = "regel filter sortieren sieve",
136 .featured = CommandContext::Rules},
137 {.name = "contact", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Create"),
138 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "New contact from one line"),
139 .contexts = kViews, .argument = CommandArgument::ContactLine, .syntax = "[name <address>]",
140 .example = "contact Mara Klein <mara@example.org>", .keywords = "kontakt person adresse"},
141
2/4
✓ Branch 10 → 11 taken 150 times.
✓ Branch 10 → 12 taken 25 times.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
175 };
142 25 }
143
144 // Places and views.
145 25 QList<NamedCommandSpec> placeCommands() {
146 return {
147 {.name = "mail", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Go to"),
148 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Show the mail"), .contexts = kAnywhere,
149 .registryId = "app.mail", .keywords = "posteingang inbox nachrichten", .featured = CommandContext::Rules},
150 {.name = "go", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Go to"),
151 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Go to a folder"),
152 .contexts = kList, .registryId = "folder.switch", .argument = CommandArgument::Folder,
153 .syntax = "[folder]", .example = "go Projekte/MailJD", .keywords = "ordner wechseln gehe zu öffnen"},
154 {.name = "search", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Go to"),
155 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Search all mail"),
156 .contexts = kList, .registryId = "search.open", .argument = CommandArgument::Query,
157 .syntax = "[query]", .example = "search from:mara is:unread", .keywords = "suchen finden"},
158 {.name = "filter", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "View"),
159 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Filter the message list"),
160 .contexts = kList, .argument = CommandArgument::Values, .argumentRequired = true,
161 .syntax = "<unread∣important∣attachments∣off∣words> …", .example = "filter unread invoice",
162 .values = {{.word = "unread", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Only unread messages"),
163 .keywords = "ungelesen neu",
164 .registryId = "view.filter_unread", .stateKey = "filter.unread"},
165 {.word = "important", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Only important messages"),
166 .keywords = "wichtig markiert stern",
167 .registryId = "view.filter_starred", .stateKey = "filter.important"},
168 {.word = "attachments", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Only messages with attachments"),
169 .keywords = "anhang anhänge dateien", .registryId = "view.filter_attachments",
170 .stateKey = "filter.attachments"},
171 {.word = "off", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Show all messages"),
172 .keywords = "aus zurücksetzen alle clear",
173 .registryId = "view.filter_clear", .alone = true}},
174 .keywords = "filtern einschränken", .freeText = true, .several = true},
175 {.name = "calendar", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Go to"),
176 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Show the calendar"), .contexts = kAnywhere,
177 .registryId = "app.calendar", .argument = CommandArgument::Date,
178 .syntax = "[day∣week∣month∣year∣agenda∣today∣<date>]", .example = "calendar week",
179 .values = {{.word = "day", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Day view"), .keywords = "tag tagesansicht",
180 .registryId = "calendar.view_day"},
181 {.word = "week", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Week view"), .keywords = "woche wochenansicht",
182 .registryId = "calendar.view_week", .featured = CommandContext::Calendar},
183 {.word = "month", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Month view"), .keywords = "monat monatsansicht",
184 .registryId = "calendar.view_month", .featured = CommandContext::Calendar},
185 {.word = "year", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Year view"), .keywords = "jahr jahresansicht",
186 .registryId = "calendar.view_year"},
187 {.word = "agenda", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Agenda view"), .keywords = "liste termine",
188 .registryId = "calendar.view_agenda"},
189 {.word = "today", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Go to today"), .keywords = "heute jetzt",
190 .registryId = "calendar.today", .featured = CommandContext::Calendar}},
191 .keywords = "kalender termine", .featured = CommandContext::Calendar},
192 {.name = "tasks", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Go to"),
193 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Show the tasks"), .contexts = kAnywhere,
194 .registryId = "app.tasks", .argument = CommandArgument::Values, .syntax = "[today∣week∣planned∣completed∣all]",
195 .example = "tasks today",
196 .values = {{.word = "today", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Tasks due today"), .keywords = "heute",
197 .featured = CommandContext::Task},
198 {.word = "week", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Tasks due this week"), .keywords = "woche"},
199 {.word = "planned", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Tasks with a due date"),
200 .keywords = "geplant"},
201 {.word = "completed", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Completed tasks"),
202 .keywords = "erledigt fertig done"},
203 {.word = "all", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "All open tasks"), .keywords = "alle meine mine"}},
204 .keywords = "aufgaben todo", .featured = CommandContext::Task},
205 {.name = "contacts", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Go to"),
206 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Show the contacts"), .contexts = kAnywhere,
207 .registryId = "app.contacts", .keywords = "kontakte adressbuch personen"},
208 {.name = "rules", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Go to"),
209 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Show the server rules"),
210 .contexts = kMailAndRules, .registryId = "app.rules", .argument = CommandArgument::Text, .syntax = "[text]",
211 .example = "rules github", .keywords = "regeln sieve filter sortierung"},
212 // Sprint 107 S107.4.5: the conflict list and the resolver.
213 {.name = "conflicts", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Go to"),
214 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Show the open conflicts"), .contexts = kAnywhere,
215 .registryId = "conflict.list", .keywords = "konflikte konflikt synchronisation sync"},
216 {.name = "resolve", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Go to"),
217 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Resolve the first open conflict of this area"),
218 .contexts = kAnywhere, .registryId = "conflict.resolve", .keywords = "lösen auflösen konflikt zusammenführen merge"},
219
2/4
✓ Branch 17 → 18 taken 250 times.
✓ Branch 17 → 19 taken 25 times.
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 24 not taken.
275 };
220 25 }
221
222 // Objects.
223 25 QList<NamedCommandSpec> objectCommands() {
224 return {
225 {.name = "folder", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Folders"),
226 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Change the current folder"),
227 .contexts = CommandContext::MainMail, .argument = CommandArgument::Values, .argumentRequired = true,
228 .syntax = "<new∣rename∣delete∣move∣properties∣subscriptions>", .example = "folder rename Projekte",
229 .values = {{.word = "new", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "New subfolder"),
230 .keywords = "anlegen erstellen neu unterordner mkdir create",
231 .then = CommandArgument::Text, .thenRequired = true, .thenSyntax = "<name>"},
232 {.word = "rename", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Rename the folder"), .keywords = "umbenennen",
233 .then = CommandArgument::Text, .thenRequired = true, .thenSyntax = "<name>"},
234 {.word = "delete", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Delete the folder (asks first)"),
235 .keywords = "löschen entfernen rmdir",
236 .registryId = "folder.delete"},
237 {.word = "move", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Move the folder under another folder"),
238 .keywords = "verschieben",
239 .registryId = "folder.move", .then = CommandArgument::Folder, .thenSyntax = "[parent]"},
240 {.word = "properties", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Folder properties"),
241 .keywords = "eigenschaften details"},
242 {.word = "subscriptions", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Choose the subscribed folders"),
243 .keywords = "abonnements abonnieren"}},
244 .keywords = "ordner verzeichnis"},
245
2/4
✓ Branch 6 → 7 taken 25 times.
✓ Branch 6 → 8 taken 25 times.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 13 not taken.
50 };
246 25 }
247
248 // Toggles [on|off].
249 25 QList<NamedCommandSpec> toggleCommands() {
250 return {
251 {.name = "threads", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "View"),
252 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Group the list into conversations"),
253 .contexts = CommandContext::MainMail, .registryId = "view.thread", .argument = CommandArgument::Values,
254 25 .syntax = "[on∣off]", .example = "threads on", .values = {on(), off()},
255 .keywords = "konversationen unterhaltungen gruppieren thread", .stateKey = "threads"},
256 {.name = "timeline", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "View"),
257 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Show the timeline of the search"),
258 .contexts = CommandContext::Search, .registryId = "search.toggle_timeline", .argument = CommandArgument::Values,
259 25 .syntax = "[on∣off]", .example = "timeline off", .values = {on(), off()},
260 .keywords = "zeitachse zeitleiste histogramm verlauf", .stateKey = "timeline"},
261 {.name = "agenda", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "View"),
262 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Show the agenda beside the mail"),
263 .contexts = kMail, .registryId = "view.agenda_panel", .argument = CommandArgument::Values, .syntax = "[on∣off]",
264 25 .example = "agenda off", .values = {on(), off()}, .keywords = "termine neben seitenleiste",
265 .stateKey = "agenda"},
266 {.name = "detach", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "View"),
267 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Write messages in their own window"),
268 .contexts = kViews, .registryId = "compose.detach", .argument = CommandArgument::Values, .syntax = "[on∣off]",
269 25 .example = "detach on", .values = {on(), off()}, .keywords = "eigenes fenster abdocken", .stateKey = "detach"},
270 {.name = "vacation", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Rules"),
271 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Out-of-office reply"), .contexts = kViews,
272 25 .argument = CommandArgument::Values, .syntax = "[off]", .example = "vacation off", .values = {off()},
273 .keywords = "abwesenheit abwesend urlaub out of office autoreply", .stateKey = "vacation",
274 .featured = CommandContext::Rules},
275
2/4
✓ Branch 23 → 24 taken 125 times.
✓ Branch 23 → 25 taken 25 times.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
175 };
276 150 }
277
278 // Application.
279 25 QList<NamedCommandSpec> applicationCommands() {
280 return {
281 {.name = "import", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Application"),
282 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Import a calendar file"),
283 .contexts = kViews, .keywords = "importieren kalenderdatei ics"},
284 {.name = "help", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Application"),
285 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Keyboard shortcuts"), .contexts = kAnywhere,
286 .registryId = "help.shortcuts", .keywords = "hilfe tastenkürzel tasten shortcuts"},
287 {.name = "quit", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Application"),
288 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Quit MailJD"), .contexts = kAnywhere,
289 .registryId = "app.quit", .keywords = "beenden schließen exit", .destructive = true},
290
2/4
✓ Branch 7 → 8 taken 75 times.
✓ Branch 7 → 9 taken 25 times.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
100 };
291 25 }
292
293 // Sprint 100 §9.1 (D-29): settings, accounts, sync and the situational
294 // toggles. Only settings that are switched situationally get a command;
295 // everything else is one `settings <place>` away.
296 25 QList<NamedCommandSpec> settingsCommands() {
297 return {
298 {.name = "settings", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Settings"),
299 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Open the settings"),
300 .contexts = kAnywhere, .registryId = "app.settings", .argument = CommandArgument::Place, .syntax = "[place]",
301 .example = "settings privacy/remote-images",
302 .keywords = "einstellungen optionen konfiguration preferences options", .featured = CommandContext::Settings},
303 {.name = "account", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Settings"),
304 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Add, change or switch an account"),
305 .contexts = kAnywhere, .argument = CommandArgument::Values, .argumentRequired = true,
306 .syntax = "<new∣password∣remove∣switch> [account]", .example = "account password",
307 .values = {{.word = "new", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Add an account (guided setup)"),
308 .keywords = "neu einrichten hinzufügen anlegen add setup", .featured = CommandContext::Settings},
309 {.word = "password", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Change the password of an account"),
310 .keywords = "passwort kennwort ändern change", .then = CommandArgument::Account, .thenSyntax = "[account]"},
311 {.word = "remove", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Remove an account (asks first)"),
312 .keywords = "entfernen löschen delete", .then = CommandArgument::Account, .thenSyntax = "[account]"},
313 {.word = "switch", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Switch to another account"),
314 .keywords = "wechseln umschalten aktivieren change", .then = CommandArgument::Account,
315 .thenRequired = true, .thenSyntax = "<account>"}},
316 .keywords = "konto konten postfach mailbox account"},
317 {.name = "sync", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Settings"),
318 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Synchronize now"), .contexts = kAnywhere,
319 .argument = CommandArgument::Values, .syntax = "[calendars∣contacts∣settings] …", .example = "sync calendars contacts",
320 .values = {{.word = "calendars", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Calendars and task lists"),
321 .keywords = "kalender termine aufgaben"},
322 {.word = "contacts", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Address books"),
323 .keywords = "kontakte adressbücher"},
324 {.word = "settings", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Settings between installations"),
325 .keywords = "einstellungen geräte installationen"}},
326 .keywords = "abgleichen synchronisieren aktualisieren refresh", .several = true, .featured = CommandContext::Settings},
327 {.name = "push", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Settings"),
328 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Instant updates of calendars and contacts"),
329 .contexts = kAnywhere, .argument = CommandArgument::Values, .syntax = "[on∣off]", .example = "push off",
330 25 .values = {on(), off()}, .keywords = "sofort benachrichtigung server instant", .stateKey = "push"},
331 {.name = "theme", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Settings"),
332 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Light or dark appearance"), .contexts = kAnywhere,
333 .argument = CommandArgument::Values, .argumentRequired = true, .syntax = "<light∣dark∣system>",
334 .example = "theme dark",
335 .values = {{.word = "light", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Light"), .keywords = "hell"},
336 {.word = "dark", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Dark"), .keywords = "dunkel nacht"},
337 {.word = "system", .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Like the system"),
338 .keywords = "automatisch wie system auto"}},
339 .keywords = "design farbschema hell dunkel dark mode erscheinungsbild", .featured = CommandContext::Settings},
340 {.name = "suggestions", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Settings"),
341 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Folder suggestions for all messages"),
342 // S106-359: the toggle acts on the folder list only (registry: MainMail).
343 .contexts = CommandContext::MainMail, .registryId = "suggestion.toggle_all", .argument = CommandArgument::Values, .syntax = "[on∣off]",
344 25 .example = "suggestions off", .values = {on(), off()}, .keywords = "vorschläge ordnervorschläge vorhersage",
345 .stateKey = "suggestions"},
346 {.name = "notifications", .category = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Settings"),
347 .description = QT_TRANSLATE_NOOP("NamedCommandCatalog", "Notifications about new mail and reminders"),
348 .contexts = kAnywhere, .argument = CommandArgument::Values, .syntax = "[on∣off]", .example = "notifications off",
349 25 .values = {on(), off()}, .keywords = "benachrichtigungen mitteilungen nicht stören do not disturb",
350 .stateKey = "notifications", .featured = CommandContext::Settings},
351
2/4
✓ Branch 23 → 24 taken 175 times.
✓ Branch 23 → 25 taken 25 times.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
225 };
352 125 }
353 } // namespace
354
355 5551 const QList<NamedCommandSpec> &NamedCommandCatalog::specs() {
356 static const QList<NamedCommandSpec> commands =
357
11/11
✓ Branch 7 → 8 taken 25 times.
✓ Branch 8 → 9 taken 25 times.
✓ Branch 9 → 10 taken 25 times.
✓ Branch 10 → 11 taken 25 times.
✓ Branch 11 → 12 taken 25 times.
✓ Branch 12 → 13 taken 25 times.
✓ Branch 13 → 14 taken 25 times.
✓ Branch 14 → 15 taken 25 times.
✓ Branch 15 → 16 taken 25 times.
✓ Branch 16 → 17 taken 25 times.
✓ Branch 17 → 18 taken 25 times.
50 selectionCommands() + createCommands() + placeCommands() + objectCommands() + toggleCommands() + settingsCommands() +
358
7/10
✓ Branch 2 → 3 taken 25 times.
✓ Branch 2 → 37 taken 5526 times.
✓ Branch 4 → 5 taken 25 times.
✗ Branch 4 → 37 not taken.
✓ Branch 5 → 6 taken 25 times.
✓ Branch 6 → 7 taken 25 times.
✓ Branch 18 → 19 taken 25 times.
✓ Branch 19 → 20 taken 25 times.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 83 not taken.
5601 applicationCommands() + peopleCommands();
359 5551 return commands;
360 }
361
362 6615 const NamedCommandValue *NamedCommandSpec::value(const QString &word) const {
363
2/2
✓ Branch 11 → 4 taken 12122 times.
✓ Branch 11 → 12 taken 2808 times.
14930 for (const auto &candidate : values)
364
2/2
✓ Branch 7 → 8 taken 3807 times.
✓ Branch 7 → 9 taken 8315 times.
12122 if (word.compare(QLatin1String(candidate.word), Qt::CaseInsensitive) == 0) return &candidate;
365 2808 return nullptr;
366 }
367
368 namespace {
369 // Where the text after the first `count` words of `text` starts.
370 2695 qsizetype afterWords(const QString &text, int count) {
371 2695 qsizetype at = 0;
372
2/2
✓ Branch 28 → 3 taken 1668 times.
✓ Branch 28 → 29 taken 2695 times.
4363 for (int word = 0; word < count; ++word) {
373
5/6
✓ Branch 6 → 7 taken 1761 times.
✗ Branch 6 → 13 not taken.
✓ Branch 11 → 12 taken 93 times.
✓ Branch 11 → 13 taken 1668 times.
✓ Branch 14 → 4 taken 93 times.
✓ Branch 14 → 15 taken 1668 times.
1761 while (at < text.size() && text.at(at) == QLatin1Char(' ')) ++at;
374
6/6
✓ Branch 18 → 19 taken 11588 times.
✓ Branch 18 → 25 taken 1481 times.
✓ Branch 23 → 24 taken 11401 times.
✓ Branch 23 → 25 taken 187 times.
✓ Branch 26 → 16 taken 11401 times.
✓ Branch 26 → 27 taken 1668 times.
13069 while (at < text.size() && text.at(at) != QLatin1Char(' ')) ++at;
375 }
376 2695 return at;
377 }
378 } // namespace
379
380 6884 QList<const NamedCommandValue *> ParsedNamedCommand::values() const {
381 6884 QList<const NamedCommandValue *> result;
382
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 6884 times.
6884 if (!spec) return result;
383
5/5
✓ Branch 4 → 5 taken 6884 times.
✓ Branch 5 → 6 taken 6884 times.
✓ Branch 6 → 7 taken 6884 times.
✓ Branch 17 → 8 taken 5669 times.
✓ Branch 17 → 18 taken 4122 times.
9791 for (const QString &word : words()) {
384
1/1
✓ Branch 9 → 10 taken 5669 times.
5669 const auto *value = spec->value(word);
385
2/2
✓ Branch 10 → 11 taken 2007 times.
✓ Branch 10 → 12 taken 3662 times.
5669 if (!value) break;
386
1/1
✓ Branch 12 → 13 taken 3662 times.
3662 result << value;
387
2/2
✓ Branch 13 → 14 taken 755 times.
✓ Branch 13 → 15 taken 2907 times.
3662 if (!spec->several) break;
388 6884 }
389 6884 return result;
390 ✗ }
391
392 772 const NamedCommandValue *ParsedNamedCommand::value() const {
393
1/1
✓ Branch 2 → 3 taken 772 times.
772 const auto chosen = values();
394
2/2
✓ Branch 4 → 5 taken 202 times.
✓ Branch 4 → 6 taken 570 times.
1544 return chosen.isEmpty() ? nullptr : chosen.first();
395 772 }
396
397 2695 QString ParsedNamedCommand::rest() const {
398
3/3
✓ Branch 2 → 3 taken 2695 times.
✓ Branch 5 → 6 taken 2695 times.
✓ Branch 6 → 7 taken 2695 times.
2695 return argument.mid(afterWords(argument, int(values().size()))).trimmed();
399 }
400
401
1/1
✓ Branch 5 → 6 taken 8452 times.
16904 QStringList ParsedNamedCommand::words() const { return argument.split(QLatin1Char(' '), Qt::SkipEmptyParts); }
402
403 1352 QString ParsedNamedCommand::registryId() const {
404
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1352 times.
1352 if (!spec) return {};
405
1/1
✓ Branch 4 → 5 taken 1352 times.
1352 const auto chosen = values();
406
9/11
✓ Branch 6 → 7 taken 325 times.
✓ Branch 6 → 12 taken 1027 times.
✓ Branch 7 → 8 taken 226 times.
✓ Branch 7 → 11 taken 99 times.
✓ Branch 8 → 9 taken 226 times.
✓ Branch 10 → 11 taken 222 times.
✓ Branch 10 → 12 taken 4 times.
✓ Branch 13 → 14 taken 226 times.
✓ Branch 13 → 15 taken 1126 times.
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 35 not taken.
1352 const bool onlyValue = chosen.size() == 1 && (!spec->several || rest().isEmpty());
407
7/7
✓ Branch 15 → 16 taken 321 times.
✓ Branch 15 → 19 taken 1031 times.
✓ Branch 17 → 18 taken 131 times.
✓ Branch 17 → 19 taken 190 times.
✓ Branch 20 → 21 taken 131 times.
✓ Branch 20 → 25 taken 1221 times.
✓ Branch 23 → 24 taken 131 times.
1352 if (onlyValue && chosen.first()->registryId) return QString::fromLatin1(chosen.first()->registryId);
408
3/3
✓ Branch 25 → 26 taken 611 times.
✓ Branch 25 → 28 taken 610 times.
✓ Branch 27 → 29 taken 611 times.
1221 return spec->registryId ? QString::fromLatin1(spec->registryId) : QString{};
409 1352 }
410
411 606 bool NamedCommandCatalog::combinable(const QList<const NamedCommandValue *> &values, bool more) {
412
2/2
✓ Branch 31 → 3 taken 692 times.
✓ Branch 31 → 32 taken 579 times.
1271 for (qsizetype i = 0; i < values.size(); ++i) {
413
8/8
✓ Branch 4 → 5 taken 53 times.
✓ Branch 4 → 9 taken 639 times.
✓ Branch 6 → 7 taken 44 times.
✓ Branch 6 → 8 taken 9 times.
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 9 taken 42 times.
✓ Branch 10 → 11 taken 11 times.
✓ Branch 10 → 12 taken 681 times.
692 if (values.at(i)->alone && (values.size() > 1 || more)) return false;
414
2/2
✓ Branch 28 → 13 taken 82 times.
✓ Branch 28 → 29 taken 665 times.
747 for (qsizetype j = 0; j < i; ++j) {
415 82 const char *group = values.at(i)->group;
416
9/10
✓ Branch 16 → 17 taken 72 times.
✓ Branch 16 → 23 taken 10 times.
✓ Branch 17 → 18 taken 44 times.
✓ Branch 17 → 24 taken 28 times.
✓ Branch 19 → 20 taken 44 times.
✗ Branch 19 → 24 not taken.
✓ Branch 22 → 23 taken 6 times.
✓ Branch 22 → 24 taken 38 times.
✓ Branch 25 → 26 taken 16 times.
✓ Branch 25 → 27 taken 66 times.
82 if (values.at(j) == values.at(i) || (group && values.at(j)->group && qstrcmp(group, values.at(j)->group) == 0))
417 16 return false;
418 }
419 }
420 579 return true;
421 }
422
423 4054 const NamedCommandSpec *NamedCommandCatalog::find(const QString &name) {
424
3/3
✓ Branch 2 → 3 taken 4054 times.
✓ Branch 11 → 5 taken 104099 times.
✓ Branch 11 → 12 taken 1021 times.
105120 for (const auto &command : specs())
425
2/2
✓ Branch 7 → 8 taken 3033 times.
✓ Branch 7 → 9 taken 101066 times.
104099 if (command.name.compare(name, Qt::CaseInsensitive) == 0) return &command;
426 1021 return nullptr;
427 }
428
429 3316 ParsedNamedCommand NamedCommandCatalog::parse(const QString &input) {
430
13/13
✓ Branch 3 → 4 taken 3311 times.
✓ Branch 3 → 13 taken 5 times.
✓ Branch 5 → 6 taken 3311 times.
✓ Branch 6 → 7 taken 3310 times.
✓ Branch 6 → 13 taken 1 time.
✓ Branch 8 → 9 taken 3310 times.
✓ Branch 9 → 10 taken 3309 times.
✓ Branch 9 → 13 taken 1 time.
✓ Branch 11 → 12 taken 3309 times.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 3308 times.
✓ Branch 15 → 16 taken 8 times.
✓ Branch 15 → 17 taken 3308 times.
3316 if (input.size() > 1024 || input.contains('\n') || input.contains('\r') || input.contains(QChar(0))) return {};
431
1/1
✓ Branch 17 → 18 taken 3308 times.
3308 const QString text = input.trimmed();
432 3308 const int space = text.indexOf(QLatin1Char(' '));
433
4/4
✓ Branch 21 → 22 taken 1680 times.
✓ Branch 21 → 23 taken 1628 times.
✓ Branch 23 → 24 taken 1628 times.
✓ Branch 24 → 25 taken 3308 times.
3308 const auto *command = find(space < 0 ? text : text.left(space));
434
2/2
✓ Branch 26 → 27 taken 998 times.
✓ Branch 26 → 28 taken 2310 times.
3308 if (!command) return {};
435
6/8
✓ Branch 28 → 29 taken 682 times.
✓ Branch 28 → 30 taken 1628 times.
✓ Branch 30 → 31 taken 1628 times.
✓ Branch 31 → 32 taken 1628 times.
✓ Branch 32 → 33 taken 1628 times.
✓ Branch 32 → 34 taken 682 times.
✗ Branch 99 → 100 not taken.
✗ Branch 99 → 101 not taken.
2310 ParsedNamedCommand parsed{command, space < 0 ? QString{} : text.mid(space + 1).trimmed()};
436
2/2
✓ Branch 35 → 36 taken 682 times.
✓ Branch 35 → 37 taken 1628 times.
2310 if (parsed.argument.isEmpty()) return parsed;
437
2/2
✓ Branch 37 → 38 taken 14 times.
✓ Branch 37 → 39 taken 1614 times.
1628 if (command->argument == CommandArgument::None) {
438 14 parsed.argumentValid = false;
439
4/4
✓ Branch 39 → 40 taken 837 times.
✓ Branch 39 → 52 taken 777 times.
✓ Branch 40 → 41 taken 175 times.
✓ Branch 40 → 52 taken 662 times.
1614 } else if (command->argument == CommandArgument::Values && !command->several) {
440 // Only fixed values, optionally followed by what the value takes
441 // ("folder rename X").
442
1/1
✓ Branch 41 → 42 taken 175 times.
175 const auto *value = parsed.value();
443
1/1
✓ Branch 42 → 43 taken 175 times.
175 const QString rest = parsed.rest();
444
4/4
✓ Branch 43 → 44 taken 159 times.
✓ Branch 43 → 49 taken 16 times.
✓ Branch 45 → 46 taken 122 times.
✓ Branch 45 → 47 taken 37 times.
175 parsed.argumentValid = value ? (rest.isEmpty() ? !value->thenRequired : value->then != CommandArgument::None)
445 16 : command->freeText;
446
2/2
✓ Branch 52 → 53 taken 662 times.
✓ Branch 52 → 69 taken 777 times.
1614 } else if (command->argument == CommandArgument::Values) {
447 // D-15a: several values that go together, then words where the command
448 // takes them ("filter unread important invoice").
449
1/1
✓ Branch 53 → 54 taken 662 times.
662 const auto values = parsed.values();
450
1/1
✓ Branch 54 → 55 taken 662 times.
662 const bool more = !parsed.rest().isEmpty();
451
9/9
✓ Branch 58 → 59 taken 89 times.
✓ Branch 58 → 60 taken 573 times.
✓ Branch 60 → 61 taken 573 times.
✓ Branch 61 → 62 taken 562 times.
✓ Branch 61 → 65 taken 11 times.
✓ Branch 62 → 63 taken 17 times.
✓ Branch 62 → 64 taken 545 times.
✓ Branch 63 → 64 taken 10 times.
✓ Branch 63 → 65 taken 7 times.
662 parsed.argumentValid = values.isEmpty() ? command->freeText : combinable(values, more) && (!more || command->freeText);
452
2/2
✓ Branch 69 → 70 taken 165 times.
✓ Branch 69 → 88 taken 612 times.
1439 } else if (command->several) {
453 // D-15a: several labels; "all" only by itself ("unlabel all").
454
1/1
✓ Branch 70 → 71 taken 165 times.
165 const QStringList words = parsed.words();
455
2/2
✓ Branch 85 → 73 taken 193 times.
✓ Branch 85 → 86 taken 165 times.
358 for (const QString &word : words)
456
8/9
✓ Branch 74 → 75 taken 193 times.
✓ Branch 75 → 76 taken 11 times.
✓ Branch 75 → 80 taken 182 times.
✓ Branch 76 → 77 taken 11 times.
✗ Branch 76 → 80 not taken.
✓ Branch 78 → 79 taken 2 times.
✓ Branch 78 → 80 taken 9 times.
✓ Branch 81 → 82 taken 2 times.
✓ Branch 81 → 83 taken 191 times.
193 if (const auto *value = command->value(word); value && value->alone && words.size() > 1) parsed.argumentValid = false;
457 165 }
458 1628 return parsed;
459 3308 }
460
461 7 QStringList NamedCommandCatalog::names(CommandContext context) {
462 7 QStringList result;
463
3/3
✓ Branch 2 → 3 taken 7 times.
✓ Branch 11 → 5 taken 364 times.
✓ Branch 11 → 12 taken 7 times.
371 for (const auto &command : specs())
464
3/3
✓ Branch 7 → 8 taken 211 times.
✓ Branch 7 → 9 taken 153 times.
✓ Branch 8 → 9 taken 211 times.
364 if (command.contexts.testFlag(context)) result.append(command.name);
465 7 return result;
466 ✗ }
467
468 14969 QString NamedCommandCatalog::description(const NamedCommandSpec &spec) {
469 14969 return QCoreApplication::translate("NamedCommandCatalog", spec.description);
470 }
471
472 1198 QString NamedCommandCatalog::description(const NamedCommandValue &value) {
473 1198 return QCoreApplication::translate("NamedCommandCatalog", value.description);
474 }
475
476 ✗ QString NamedCommandCatalog::category(const NamedCommandSpec &spec) {
477 ✗ return QCoreApplication::translate("NamedCommandCatalog", spec.category);
478 }
479
480 477 bool NamedCommandCatalog::storable(const ParsedNamedCommand &parsed) {
481
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 475 times.
477 if (!parsed.complete()) return false;
482 170 const auto storedKind = [](CommandArgument kind) {
483
5/6
✓ Branch 3 → 4 taken 170 times.
✗ Branch 3 → 8 not taken.
✓ Branch 4 → 5 taken 96 times.
✓ Branch 4 → 8 taken 74 times.
✓ Branch 5 → 6 taken 46 times.
✓ Branch 5 → 8 taken 50 times.
170 return kind == CommandArgument::None || kind == CommandArgument::Values || kind == CommandArgument::Folder ||
484
5/6
✓ Branch 2 → 3 taken 170 times.
✗ Branch 2 → 8 not taken.
✓ Branch 6 → 7 taken 45 times.
✓ Branch 6 → 8 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 44 times.
340 kind == CommandArgument::Label || kind == CommandArgument::Date || kind == CommandArgument::Place;
485 };
486
2/2
✓ Branch 6 → 7 taken 83 times.
✓ Branch 6 → 8 taken 392 times.
475 if (parsed.argument.isEmpty()) return true;
487 // F100-74: the words of "filter <words>" are a live-filter text (never stored).
488
12/14
✓ Branch 8 → 9 taken 46 times.
✓ Branch 8 → 15 taken 346 times.
✓ Branch 9 → 10 taken 46 times.
✓ Branch 10 → 11 taken 42 times.
✓ Branch 10 → 14 taken 4 times.
✓ Branch 11 → 12 taken 42 times.
✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 15 taken 40 times.
✓ Branch 16 → 17 taken 42 times.
✓ Branch 16 → 18 taken 350 times.
✓ Branch 18 → 19 taken 6 times.
✓ Branch 18 → 20 taken 386 times.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
392 if (parsed.spec->freeText && (!parsed.value() || !parsed.rest().isEmpty())) return false;
489
9/12
✓ Branch 20 → 21 taken 386 times.
✓ Branch 21 → 22 taken 223 times.
✓ Branch 21 → 33 taken 163 times.
✓ Branch 22 → 23 taken 223 times.
✓ Branch 24 → 25 taken 7 times.
✓ Branch 24 → 27 taken 216 times.
✓ Branch 26 → 27 taken 2 times.
✓ Branch 26 → 28 taken 5 times.
✓ Branch 30 → 31 taken 223 times.
✗ Branch 30 → 32 not taken.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 42 not taken.
386 if (const auto *value = parsed.value()) return parsed.rest().isEmpty() || storedKind(value->then);
490 163 return storedKind(parsed.spec->argument);
491 }
492