KCompletion

kcompletion.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1999, 2000, 2001 Carsten Pfeiffer <pfeiffer@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kcompletion.h"
9#include "kcompletion_p.h"
10#include <kcompletion_debug.h>
11
12#include <QCollator>
13
14void KCompletionPrivate::addWeightedItem(const QString &item)
15{
16 Q_Q(KCompletion);
17 if (order != KCompletion::Weighted) {
18 q->addItem(item, 0);
19 return;
20 }
21
22 int len = item.length();
23 uint weight = 0;
24
25 // find out the weighting of this item (appended to the string as ":num")
26 int index = item.lastIndexOf(QLatin1Char(':'));
27 if (index > 0) {
28 bool ok;
29 weight = QStringView(item).mid(index + 1).toUInt(&ok);
30 if (!ok) {
31 weight = 0;
32 }
33
34 len = index; // only insert until the ':'
35 }
36
37 q->addItem(item.left(len), weight);
38 return;
39}
40
41// tries to complete "string" from the tree-root
42QString KCompletionPrivate::findCompletion(const QString &string)
43{
45 const KCompTreeNode *node = m_treeRoot.get();
46
47 // start at the tree-root and try to find the search-string
48 for (const auto ch : string) {
49 node = node->find(ch);
50
51 if (node) {
52 completion += ch;
53 } else {
54 return QString(); // no completion
55 }
56 }
57
58 // Now we have the last node of the to be completed string.
59 // Follow it as long as it has exactly one child (= longest possible
60 // completion)
61
62 while (node->childrenCount() == 1) {
63 node = node->firstChild();
64 if (!node->isNull()) {
65 completion += *node;
66 }
67 }
68 // if multiple matches and auto-completion mode
69 // -> find the first complete match
70 if (node && node->childrenCount() > 1) {
71 hasMultipleMatches = true;
72
73 if (completionMode == KCompletion::CompletionAuto) {
74 rotationIndex = 1;
75 if (order != KCompletion::Weighted) {
76 while ((node = node->firstChild())) {
77 if (!node->isNull()) {
78 completion += *node;
79 } else {
80 break;
81 }
82 }
83 } else {
84 // don't just find the "first" match, but the one with the
85 // highest priority
86
87 const KCompTreeNode *temp_node = nullptr;
88 while (1) {
89 int count = node->childrenCount();
90 temp_node = node->firstChild();
91 uint weight = temp_node->weight();
92 const KCompTreeNode *hit = temp_node;
93 for (int i = 1; i < count; i++) {
94 temp_node = node->childAt(i);
95 if (temp_node->weight() > weight) {
96 hit = temp_node;
97 weight = hit->weight();
98 }
99 }
100 // 0x0 has the highest priority -> we have the best match
101 if (hit->isNull()) {
102 break;
103 }
104
105 node = hit;
106 completion += *node;
107 }
108 }
109 }
110 }
111
112 return completion;
113}
114
115void KCompletionPrivate::defaultSort(QStringList &stringList)
116{
117 QCollator c;
119 std::stable_sort(stringList.begin(), stringList.end(), c);
120}
121
123 : d_ptr(new KCompletionPrivate(this))
124{
126}
127
131
133{
135 d->order = order;
136 d->matches.setSorting(order);
137}
138
139KCompletion::CompOrder KCompletion::order() const
140{
141 Q_D(const KCompletion);
142 return d->order;
143}
144
145void KCompletion::setIgnoreCase(bool ignoreCase)
146{
148 d->ignoreCase = ignoreCase;
149}
150
151bool KCompletion::ignoreCase() const
152{
153 Q_D(const KCompletion);
154 return d->ignoreCase;
155}
156
158{
159 clear();
160 insertItems(itemList);
161}
162
164{
166 for (const auto &str : items) {
167 if (d->order == Weighted) {
168 d->addWeightedItem(str);
169 } else {
170 addItem(str, 0);
171 }
172 }
173}
174
175QStringList KCompletion::items() const
176{
177 Q_D(const KCompletion);
178 KCompletionMatchesWrapper list(d->sorterFunction); // unsorted
179 list.extractStringsFromNode(d->m_treeRoot.get(), QString(), d->order == Weighted);
180 return list.list();
181}
182
184{
185 Q_D(const KCompletion);
186 return (d->m_treeRoot->childrenCount() == 0);
187}
188
192
196
200
202{
204 d->matches.clear();
205 d->rotationIndex = 0;
206 d->lastString.clear();
207
208 addItem(item, 0);
209}
210
211void KCompletion::addItem(const QString &item, uint weight)
212{
214 if (item.isEmpty()) {
215 return;
216 }
217
218 KCompTreeNode *node = d->m_treeRoot.get();
219 int len = item.length();
220
221 bool sorted = (d->order == Sorted);
222 bool weighted = ((d->order == Weighted) && weight > 1);
223
224 // knowing the weight of an item, we simply add this weight to all of its
225 // nodes.
226
227 for (int i = 0; i < len; i++) {
228 node = node->insert(item.at(i), sorted);
229 if (weighted) {
230 node->confirm(weight - 1); // node->insert() sets weighting to 1
231 }
232 }
233
234 // add 0x0-item as delimiter with evtl. weight
235 node = node->insert(QChar(0x0), true);
236 if (weighted) {
237 node->confirm(weight - 1);
238 }
239 // qDebug("*** added: %s (%i)", item.toLatin1().constData(), node->weight());
240}
241
243{
245 d->matches.clear();
246 d->rotationIndex = 0;
247 d->lastString.clear();
248
249 d->m_treeRoot->remove(item);
250}
251
253{
255 d->matches.clear();
256 d->rotationIndex = 0;
257 d->lastString.clear();
258
259 d->m_treeRoot.reset(new KCompTreeNode);
260}
261
263{
265 if (d->completionMode == CompletionNone) {
266 return QString();
267 }
268
269 // qDebug() << "KCompletion: completing: " << string;
270
271 d->matches.clear();
272 d->rotationIndex = 0;
273 d->hasMultipleMatches = false;
274 d->lastMatch = d->currentMatch;
275
276 // in Shell-completion-mode, emit all matches when we get the same
277 // complete-string twice
278 if (d->completionMode == CompletionShell && string == d->lastString) {
279 // Don't use d->matches since calling postProcessMatches()
280 // on d->matches here would interfere with call to
281 // postProcessMatch() during rotation
282
283 d->matches.findAllCompletions(d->m_treeRoot.get(), string, d->ignoreCase, d->hasMultipleMatches);
284 QStringList l = d->matches.list();
286 Q_EMIT matches(l);
287
288 return QString();
289 }
290
291 QString completion;
292 // in case-insensitive popup mode, we search all completions at once
293 if (d->completionMode == CompletionPopup || d->completionMode == CompletionPopupAuto) {
294 d->matches.findAllCompletions(d->m_treeRoot.get(), string, d->ignoreCase, d->hasMultipleMatches);
295 if (!d->matches.isEmpty()) {
296 completion = d->matches.first();
297 }
298 } else {
299 completion = d->findCompletion(string);
300 }
301
302 if (d->hasMultipleMatches) {
304 }
305
306 d->lastString = string;
307 d->currentMatch = completion;
308
309 postProcessMatch(&completion);
310
311 if (!string.isEmpty()) { // only emit match when string is not empty
312 // qDebug() << "KCompletion: Match: " << completion;
313 Q_EMIT match(completion);
314 }
315
316 return completion;
317}
318
320{
321 Q_D(const KCompletion);
322 // get all items in the tree, eventually in sorted order
323 KCompletionMatchesWrapper allItems(d->sorterFunction, d->order);
324 allItems.extractStringsFromNode(d->m_treeRoot.get(), QString(), false);
325
326 QStringList list = allItems.list();
327
328 // subStringMatches is invoked manually, via a shortcut
329 if (list.isEmpty()) {
330 return list;
331 }
332
333 if (!string.isEmpty()) { // If it's empty, nothing to compare
334 auto it = std::remove_if(list.begin(), list.end(), [&string](const QString &item) {
335 return !item.contains(string, Qt::CaseInsensitive); // always case insensitive
336 });
337 list.erase(it, list.end());
338 }
339
340 postProcessMatches(&list);
341 return list;
342}
343
345{
347 d->completionMode = mode;
348}
349
351{
352 Q_D(const KCompletion);
353 return d->completionMode;
354}
355
356void KCompletion::setShouldAutoSuggest(const bool shouldAutoSuggest)
357{
359 d->shouldAutoSuggest = shouldAutoSuggest;
360}
361
363{
364 Q_D(const KCompletion);
365 return d->shouldAutoSuggest;
366}
367
369{
371 d->sorterFunction = sortFunc ? sortFunc : KCompletionPrivate::defaultSort;
372}
373
375{
377 // Don't use d->matches since calling postProcessMatches()
378 // on d->matches here would interfere with call to
379 // postProcessMatch() during rotation
380 KCompletionMatchesWrapper matches(d->sorterFunction, d->order);
381 bool dummy;
382 matches.findAllCompletions(d->m_treeRoot.get(), d->lastString, d->ignoreCase, dummy);
383 QStringList l = matches.list();
385 return l;
386}
387
389{
391 // Don't use d->matches since calling postProcessMatches()
392 // on d->matches here would interfere with call to
393 // postProcessMatch() during rotation
394 KCompletionMatchesWrapper matches(d->sorterFunction, d->order);
395 bool dummy;
396 matches.findAllCompletions(d->m_treeRoot.get(), d->lastString, d->ignoreCase, dummy);
398 postProcessMatches(&ret);
399 return ret;
400}
401
403{
405 KCompletionMatchesWrapper matches(d->sorterFunction, d->order);
406 bool dummy;
407 matches.findAllCompletions(d->m_treeRoot.get(), string, d->ignoreCase, dummy);
408 QStringList l = matches.list();
410 return l;
411}
412
414{
416 KCompletionMatchesWrapper matches(d->sorterFunction, d->order);
417 bool dummy;
418 matches.findAllCompletions(d->m_treeRoot.get(), string, d->ignoreCase, dummy);
420 postProcessMatches(&ret);
421 return ret;
422}
423
425{
427 d->beep = enable;
428}
429
431{
432 Q_D(const KCompletion);
433 return d->beep;
434}
435
437{
438 Q_D(const KCompletion);
439 return d->hasMultipleMatches;
440}
441
442/////////////////////////////////////////////////////
443///////////////// tree operations ///////////////////
444
446{
448 QString completion;
449 d->lastMatch = d->currentMatch;
450
451 if (d->matches.isEmpty()) {
452 d->matches.findAllCompletions(d->m_treeRoot.get(), d->lastString, d->ignoreCase, d->hasMultipleMatches);
453 if (!d->matches.isEmpty()) {
454 completion = d->matches.first();
455 }
456 d->currentMatch = completion;
457 d->rotationIndex = 0;
458 postProcessMatch(&completion);
459 Q_EMIT match(completion);
460 return completion;
461 }
462
463 QStringList matches = d->matches.list();
464 d->lastMatch = matches[d->rotationIndex++];
465
466 if (d->rotationIndex == matches.count()) {
467 d->rotationIndex = 0;
468 }
469
470 completion = matches[d->rotationIndex];
471 d->currentMatch = completion;
472 postProcessMatch(&completion);
473 Q_EMIT match(completion);
474 return completion;
475}
476
478{
479 Q_D(const KCompletion);
480 return d->lastMatch;
481}
482
484{
486 QString completion;
487 d->lastMatch = d->currentMatch;
488
489 if (d->matches.isEmpty()) {
490 d->matches.findAllCompletions(d->m_treeRoot.get(), d->lastString, d->ignoreCase, d->hasMultipleMatches);
491 if (!d->matches.isEmpty()) {
492 completion = d->matches.last();
493 }
494 d->currentMatch = completion;
495 d->rotationIndex = 0;
496 postProcessMatch(&completion);
497 Q_EMIT match(completion);
498 return completion;
499 }
500
501 QStringList matches = d->matches.list();
502 d->lastMatch = matches[d->rotationIndex];
503
504 if (d->rotationIndex == 0) {
505 d->rotationIndex = matches.count();
506 }
507
508 d->rotationIndex--;
509
510 completion = matches[d->rotationIndex];
511 d->currentMatch = completion;
512 postProcessMatch(&completion);
513 Q_EMIT match(completion);
514 return completion;
515}
516
517QSharedPointer<KZoneAllocator> KCompTreeNode::m_alloc(new KZoneAllocator(8 * 1024));
518
519#include "moc_kcompletion.cpp"
This structure is returned by KCompletion::allWeightedMatches().
A generic class for completing QStrings.
QString nextMatch()
Returns the next item from the list of matching items.
void insertItems(const QStringList &items)
Inserts items into the list of possible completions.
void removeItem(const QString &item)
Removes an item from the list of available completions.
void multipleMatches()
This signal is emitted when calling makeCompletion() and more than one matching item is found.
virtual QString makeCompletion(const QString &string)
Attempts to find an item in the list of available completions that begins with string.
bool soundsEnabled() const
Tells you whether KCompletion will emit sounds on certain occasions.
virtual void postProcessMatches(QStringList *matchList) const
This method is called before a list of all available completions is emitted via matches().
bool isEmpty() const
Returns true if the completion object contains no entries.
void matches(const QStringList &matchlist)
This signal is emitted by makeCompletion() in shell-completion mode when the same string is passed to...
QStringList allMatches()
Returns a list of all items matching the last completed string.
KCompletion()
Constructor, nothing special here :)
virtual void setOrder(CompOrder order)
KCompletion offers three different ways in which it offers its items:
virtual void setCompletionMode(CompletionMode mode)
Sets the completion mode.
virtual void setSoundsEnabled(bool enable)
Enables/disables emitting a sound when.
CompOrder
Constants that represent the order in which KCompletion performs completion lookups.
@ Insertion
Use order of insertion.
@ Weighted
Use weighted order.
@ Sorted
Use alphabetically sorted order or custom sorter logic.
QStringList substringCompletion(const QString &string) const
Returns a list of all completion items that contain the given string.
CompletionMode
This enum describes the completion mode used for by the KCompletion class.
@ CompletionNone
No completion is used.
@ CompletionPopupAuto
Lists all possible matches in a popup list box to choose from, and automatically fills the result whe...
@ CompletionAuto
Text is automatically filled in whenever possible.
@ CompletionShell
Completes text much in the same way as a typical *nix shell would.
@ CompletionPopup
Lists all possible matches in a popup list box to choose from.
virtual void setIgnoreCase(bool ignoreCase)
Setting this to true makes KCompletion behave case insensitively.
QString previousMatch()
Returns the next item from the list of matching items.
std::function< void(QStringList &)> SorterFunction
The sorter function signature.
virtual void postProcessMatch(QString *match) const
This method is called after a completion is found and before the matching string is emitted.
virtual void clear()
Removes all inserted items.
void match(const QString &item)
This signal is emitted when a match is found.
CompletionMode completionMode() const
Returns the current completion mode.
void setSorterFunction(SorterFunction sortFunc)
Sets a custom function to be used to sort the matches.
void setShouldAutoSuggest(bool shouldAutosuggest)
Deriving classes may set this property and control whether the auto-suggestion should be displayed fo...
void addItem(const QString &item)
Adds an item to the list of available completions.
bool shouldAutoSuggest() const
Informs the caller if they should display the auto-suggestion for the last completion operation perfo...
bool hasMultipleMatches() const
Returns true when more than one match is found.
virtual const QString & lastMatch() const
Returns the last match.
KCompletionMatches allWeightedMatches()
Returns a list of all items matching the last completed string.
~KCompletion() override
Destructor, nothing special here, either.
virtual void setItems(const QStringList &itemList)
Sets the list of items available for completion.
const QList< QKeySequence > & completion()
void setCaseSensitivity(Qt::CaseSensitivity cs)
iterator begin()
iterator end()
iterator erase(const_iterator begin, const_iterator end)
bool isEmpty() const const
Q_EMITQ_EMIT
const QChar at(qsizetype position) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
qsizetype length() const const
QStringView mid(qsizetype start, qsizetype length) const const
uint toUInt(bool *ok, int base) const const
CaseSensitive
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:12:50 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.