KIO

kurlcompletion.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 David Smith <dsmith@algonet.se>
4 SPDX-FileCopyrightText: 2004 Scott Wheeler <wheeler@kde.org>
5
6 This class was inspired by a previous KUrlCompletion by
7 SPDX-FileContributor: Henner Zeller <zeller@think.de>
8
9 SPDX-License-Identifier: LGPL-2.0-or-later
10*/
11
12#include "kurlcompletion.h"
13#include "../utils_p.h"
14#include <assert.h>
15#include <limits.h>
16#include <stdlib.h>
17
18#include <QCollator>
19#include <QDebug>
20#include <QDir>
21#include <QDirIterator>
22#include <QFile>
23#include <QMimeDatabase>
24#include <QMutex>
25#include <QProcessEnvironment>
26#include <QRegularExpression>
27#include <QThread>
28#include <QUrl>
29#include <qplatformdefs.h> // QT_LSTAT, QT_STAT, QT_STATBUF
30
31#include <KConfig>
32#include <KConfigGroup>
33#include <KSharedConfig>
34#include <KUser>
35
36#include <kio/listjob.h>
37#include <kio_widgets_debug.h>
38#include <kioglobal_p.h>
39#include <kprotocolmanager.h>
40#include <kurlauthorized.h>
41
42#include <time.h>
43
44#ifdef Q_OS_WIN
45#include <qt_windows.h>
46#else
47#include <pwd.h>
48#include <sys/param.h>
49#endif
50
51static bool expandTilde(QString &);
52static bool expandEnv(QString &);
53
54static QString unescape(const QString &text);
55
56// Permission mask for files that are executable by
57// user, group or other
58static constexpr mode_t s_modeExe = S_IXUSR | S_IXGRP | S_IXOTH;
59
60// Constants for types of completion
61enum ComplType { CTNone = 0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo };
62
63class CompletionThread;
64
65// Ensure that we don't end up with "//".
66static void addPathToUrl(QUrl &url, const QString &relPath)
67{
68 url.setPath(Utils::concatPaths(url.path(), relPath));
69}
70
71static QBasicAtomicInt s_waitDuration = Q_BASIC_ATOMIC_INITIALIZER(-1);
72
73static int initialWaitDuration()
74{
75 if (s_waitDuration.loadRelaxed() == -1) {
76 const QByteArray envVar = qgetenv("KURLCOMPLETION_WAIT");
77 if (envVar.isEmpty()) {
78 s_waitDuration = 200; // default: 200 ms
79 } else {
80 s_waitDuration = envVar.toInt();
81 }
82 }
83 return s_waitDuration;
84}
85
86// For local paths we use our custom comparer function that ignores the trailing slash character
87static void sortLocalPaths(QStringList &list)
88{
89 QCollator c;
91 std::sort(list.begin(), list.end(), [c](const QString &a, const QString &b) {
92 return c.compare(a.endsWith(QStringLiteral("/")) ? a.chopped(1) : a, b.endsWith(QStringLiteral("/")) ? b.chopped(1) : b) < 0;
93 });
94}
95
96///////////////////////////////////////////////////////
97///////////////////////////////////////////////////////
98// KUrlCompletionPrivate
99//
100class KUrlCompletionPrivate
101{
102public:
103 explicit KUrlCompletionPrivate(KUrlCompletion *qq, KUrlCompletion::Mode m)
104 : q(qq)
105 , cwd(QUrl::fromLocalFile(QDir::homePath()))
106 , mode(m)
107 {
108 // Read settings
109 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("URLCompletion"));
110 url_auto_completion = cg.readEntry("alwaysAutoComplete", true);
111 popup_append_slash = cg.readEntry("popupAppendSlash", true);
112 onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false);
113
114 q->setIgnoreCase(true);
115 }
116
117 ~KUrlCompletionPrivate();
118
119 void slotEntries(KIO::Job *, const KIO::UDSEntryList &);
120 void slotIOFinished(KJob *);
121 void slotCompletionThreadDone(QThread *thread, const QStringList &matches);
122
123 class MyURL;
124 bool userCompletion(const MyURL &url, QString *match);
125 bool envCompletion(const MyURL &url, QString *match);
126 bool exeCompletion(const MyURL &url, QString *match);
127 bool fileCompletion(const MyURL &url, QString *match);
128 bool urlCompletion(const MyURL &url, QString *match);
129
130 bool isAutoCompletion();
131
132 // List the next dir in m_dirs
133 QString listDirectories(const QStringList &, const QString &, bool only_exe = false, bool only_dir = false, bool no_hidden = false, bool stat_files = true);
134
135 void listUrls(const QList<QUrl> &urls, const QString &filter = QString(), bool only_exe = false, bool no_hidden = false);
136
137 void addMatches(const QStringList &);
138 QString finished();
139
140 void init();
141
142 void setListedUrl(ComplType compl_type, const QString &dir = QString(), const QString &filter = QString(), bool no_hidden = false);
143
144 bool isListedUrl(ComplType compl_type, const QString &dir = QString(), const QString &filter = QString(), bool no_hidden = false);
145
146 KUrlCompletion *const q;
147 QList<QUrl> list_urls;
148
149 bool onlyLocalProto = false;
150
151 // urlCompletion() in Auto/Popup mode?
152 bool url_auto_completion = true;
153
154 // Append '/' to directories in Popup mode?
155 // Doing that stat's all files and is slower
156 bool popup_append_slash = true;
157
158 // Keep track of currently listed files to avoid reading them again
159 bool last_no_hidden = false;
160 QString last_path_listed;
161 QString last_file_listed;
162 QString last_prepend;
163 ComplType last_compl_type = CTNone;
164
165 QUrl cwd; // "current directory" = base dir for completion
166
167 KUrlCompletion::Mode mode = KUrlCompletion::FileCompletion;
168 bool replace_env = true;
169 bool replace_home = true;
170 bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path
171
172 KIO::ListJob *list_job = nullptr; // kio job to list directories
173
174 QString prepend; // text to prepend to listed items
175 QString compl_text; // text to pass on to KCompletion
176
177 // Filters for files read with kio
178 bool list_urls_only_exe; // true = only list executables
179 bool list_urls_no_hidden;
180 QString list_urls_filter; // filter for listed files
181
182 CompletionThread *userListThread = nullptr;
183 CompletionThread *dirListThread = nullptr;
184
185 QStringList mimeTypeFilters;
186};
187
188class CompletionThread : public QThread
189{
191protected:
192 CompletionThread(KUrlCompletionPrivate *receiver)
193 : QThread()
194 , m_prepend(receiver->prepend)
195 , m_complete_url(receiver->complete_url)
196 , m_terminationRequested(false)
197 {
198 }
199
200public:
201 void requestTermination()
202 {
203 if (!isFinished()) {
204 qCDebug(KIO_WIDGETS) << "stopping thread" << this;
205 }
206 m_terminationRequested.storeRelaxed(true);
207 wait();
208 }
209
210 QStringList matches() const
211 {
212 QMutexLocker locker(&m_mutex);
213 return m_matches;
214 }
215
217 void completionThreadDone(QThread *thread, const QStringList &matches);
218
219protected:
220 void addMatch(const QString &match)
221 {
222 QMutexLocker locker(&m_mutex);
223 m_matches.append(match);
224 }
225 bool terminationRequested() const
226 {
227 return m_terminationRequested.loadRelaxed();
228 }
229 void done()
230 {
231 if (!terminationRequested()) {
232 qCDebug(KIO_WIDGETS) << "done, emitting signal with" << m_matches.count() << "matches";
233 Q_EMIT completionThreadDone(this, m_matches);
234 }
235 }
236
237 const QString m_prepend;
238 const bool m_complete_url; // if true completing a URL (i.e. 'm_prepend' is a URL), otherwise a path
239
240private:
241 mutable QMutex m_mutex; // protects m_matches
242 QStringList m_matches; // written by secondary thread, read by the matches() method
243 QAtomicInt m_terminationRequested; // used as a bool
244};
245
246/**
247 * A simple thread that fetches a list of tilde-completions and returns this
248 * to the caller via the completionThreadDone signal.
249 */
250
251class UserListThread : public CompletionThread
252{
254public:
255 UserListThread(KUrlCompletionPrivate *receiver)
256 : CompletionThread(receiver)
257 {
258 }
259
260protected:
261 void run() override
262 {
263#ifndef Q_OS_ANDROID
264 const QChar tilde = QLatin1Char('~');
265
266 // we don't need to handle prepend here, right? ~user is always at pos 0
267 assert(m_prepend.isEmpty());
268#ifndef Q_OS_WIN
269 struct passwd *pw;
270 ::setpwent();
271 while ((pw = ::getpwent()) && !terminationRequested()) {
272 addMatch(tilde + QString::fromLocal8Bit(pw->pw_name));
273 }
274 ::endpwent();
275#else
276 // TODO: add KUser::allUserNames() with a std::function<bool()> shouldTerminate parameter
277 // currently terminationRequested is ignored on Windows
278 const QStringList allUsers = KUser::allUserNames();
279 for (const QString &s : allUsers) {
280 addMatch(tilde + s);
281 }
282#endif
283 addMatch(QString(tilde));
284#endif
285 done();
286 }
287};
288
289class DirectoryListThread : public CompletionThread
290{
292public:
293 DirectoryListThread(KUrlCompletionPrivate *receiver,
294 const QStringList &dirList,
295 const QString &filter,
296 const QStringList &mimeTypeFilters,
297 bool onlyExe,
298 bool onlyDir,
299 bool noHidden,
300 bool appendSlashToDir)
301 : CompletionThread(receiver)
302 , m_dirList(dirList)
303 , m_filter(filter)
304 , m_mimeTypeFilters(mimeTypeFilters)
305 , m_onlyExe(onlyExe)
306 , m_onlyDir(onlyDir)
307 , m_noHidden(noHidden)
308 , m_appendSlashToDir(appendSlashToDir)
309 {
310 }
311
312 void run() override;
313
314private:
315 QStringList m_dirList;
316 QString m_filter;
317 QStringList m_mimeTypeFilters;
318 bool m_onlyExe;
319 bool m_onlyDir;
320 bool m_noHidden;
321 bool m_appendSlashToDir;
322};
323
324void DirectoryListThread::run()
325{
326 // qDebug() << "Entered DirectoryListThread::run(), m_filter=" << m_filter << ", m_onlyExe=" << m_onlyExe << ", m_onlyDir=" << m_onlyDir << ",
327 // m_appendSlashToDir=" << m_appendSlashToDir << ", m_dirList.size()=" << m_dirList.size();
328
329 QDir::Filters iterator_filter = (m_noHidden ? QDir::Filter(0) : QDir::Hidden) | QDir::Readable | QDir::NoDotAndDotDot;
330 if (m_onlyExe) {
331 iterator_filter |= (QDir::Dirs | QDir::Files | QDir::Executable);
332 } else if (m_onlyDir) {
333 iterator_filter |= QDir::Dirs;
334 } else {
335 iterator_filter |= (QDir::Dirs | QDir::Files);
336 }
337
338 QMimeDatabase mimeTypes;
339
340 for (const QString &dir : std::as_const(m_dirList)) {
341 if (terminationRequested()) {
342 break;
343 }
344
345 // qDebug() << "Scanning directory" << dir;
346
347 QDirIterator current_dir_iterator(dir, iterator_filter);
348
349 while (current_dir_iterator.hasNext() && !terminationRequested()) {
350 current_dir_iterator.next();
351
352 QFileInfo item_info = current_dir_iterator.fileInfo();
353 QString item_name = item_info.fileName();
354
355 // qDebug() << "Found" << file_name;
356
357 if (!m_filter.isEmpty() && !item_name.startsWith(m_filter)) {
358 continue;
359 }
360
361 if (!m_mimeTypeFilters.isEmpty() && !item_info.isDir()) {
362 auto mimeType = mimeTypes.mimeTypeForFile(item_info);
363 if (!m_mimeTypeFilters.contains(mimeType.name())) {
364 continue;
365 }
366 }
367
368 // Add '/' to directories
369 if (m_appendSlashToDir && item_info.isDir()) {
370 Utils::appendSlash(item_name);
371 }
372
373 if (m_complete_url) {
374 QUrl url(m_prepend);
375 addPathToUrl(url, item_name);
376 addMatch(url.toDisplayString());
377 } else {
378 item_name.prepend(m_prepend);
379 addMatch(item_name);
380 }
381 }
382 }
383
384 done();
385}
386
387KUrlCompletionPrivate::~KUrlCompletionPrivate()
388{
389}
390
391///////////////////////////////////////////////////////
392///////////////////////////////////////////////////////
393// MyURL - wrapper for QUrl with some different functionality
394//
395
396class KUrlCompletionPrivate::MyURL
397{
398public:
399 MyURL(const QString &url, const QUrl &cwd);
400 MyURL(const MyURL &url);
401 ~MyURL();
402
403 QUrl kurl() const
404 {
405 return m_kurl;
406 }
407
408 bool isLocalFile() const
409 {
410 return m_kurl.isLocalFile();
411 }
412 QString scheme() const
413 {
414 return m_kurl.scheme();
415 }
416 // The directory with a trailing '/'
417 QString dir() const
418 {
419 return m_kurl.adjusted(QUrl::RemoveFilename).path();
420 }
421 QString file() const
422 {
423 return m_kurl.fileName();
424 }
425
426 // The initial, unparsed, url, as a string.
427 QString url() const
428 {
429 return m_url;
430 }
431
432 // Is the initial string a URL, or just a path (whether absolute or relative)
433 bool isURL() const
434 {
435 return m_isURL;
436 }
437
438 void filter(bool replace_user_dir, bool replace_env);
439
440private:
441 void init(const QString &url, const QUrl &cwd);
442
443 QUrl m_kurl;
444 QString m_url;
445 bool m_isURL;
446};
447
448KUrlCompletionPrivate::MyURL::MyURL(const QString &_url, const QUrl &cwd)
449{
450 init(_url, cwd);
451}
452
453KUrlCompletionPrivate::MyURL::MyURL(const MyURL &_url)
454 : m_kurl(_url.m_kurl)
455{
456 m_url = _url.m_url;
457 m_isURL = _url.m_isURL;
458}
459
460void KUrlCompletionPrivate::MyURL::init(const QString &_url, const QUrl &cwd)
461{
462 // Save the original text
463 m_url = _url;
464
465 // Non-const copy
466 QString url_copy = _url;
467
468 // Special shortcuts for "man:" and "info:"
469 if (url_copy.startsWith(QLatin1Char('#'))) {
470 if (url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#')) {
471 url_copy.replace(0, 2, QStringLiteral("info:"));
472 } else {
473 url_copy.replace(0, 1, QStringLiteral("man:"));
474 }
475 }
476
477 // Look for a protocol in 'url'
478 const QRegularExpression protocol_regex(QStringLiteral("^(?![A-Za-z]:)[^/\\s\\\\]*:"));
479
480 // Assume "file:" or whatever is given by 'cwd' if there is
481 // no protocol. (QUrl does this only for absolute paths)
482 if (protocol_regex.match(url_copy).hasMatch()) {
483 m_kurl = QUrl(url_copy);
484 m_isURL = true;
485 } else { // relative path or ~ or $something
486 m_isURL = false;
487 if (Utils::isAbsoluteLocalPath(url_copy) || url_copy.startsWith(QLatin1Char('~')) || url_copy.startsWith(QLatin1Char('$'))) {
488 m_kurl = QUrl::fromLocalFile(url_copy);
489 } else {
490 // Relative path
491 if (cwd.isEmpty()) {
492 m_kurl = QUrl(url_copy);
493 } else {
494 m_kurl = cwd;
495 m_kurl.setPath(Utils::concatPaths(m_kurl.path(), url_copy));
496 }
497 }
498 }
499}
500
501KUrlCompletionPrivate::MyURL::~MyURL()
502{
503}
504
505void KUrlCompletionPrivate::MyURL::filter(bool replace_user_dir, bool replace_env)
506{
507 QString d = dir() + file();
508 if (replace_user_dir) {
509 expandTilde(d);
510 }
511 if (replace_env) {
512 expandEnv(d);
513 }
514 m_kurl.setPath(d);
515}
516
517///////////////////////////////////////////////////////
518///////////////////////////////////////////////////////
519// KUrlCompletion
520//
521
523 : KUrlCompletion(FileCompletion)
524{
525}
526
528 : KCompletion()
529 , d(new KUrlCompletionPrivate(this, _mode))
530{
531}
532
537
539{
540 d->cwd = dir;
541}
542
544{
545 return d->cwd;
546}
547
549{
550 return d->mode;
551}
552
554{
555 d->mode = _mode;
556}
557
559{
560 return d->replace_env;
561}
562
564{
565 d->replace_env = replace;
566}
567
569{
570 return d->replace_home;
571}
572
574{
575 d->replace_home = replace;
576}
577
578/*
579 * makeCompletion()
580 *
581 * Entry point for file name completion
582 */
584{
585 qCDebug(KIO_WIDGETS) << text << "d->cwd=" << d->cwd;
586
587 KUrlCompletionPrivate::MyURL url(text, d->cwd);
588
589 d->compl_text = text;
590
591 // Set d->prepend to the original URL, with the filename [and ref/query] stripped.
592 // This is what gets prepended to the directory-listing matches.
593 if (url.isURL()) {
594 QUrl directoryUrl(url.kurl());
595 directoryUrl.setQuery(QString());
596 directoryUrl.setFragment(QString());
597 directoryUrl.setPath(url.dir());
598 d->prepend = directoryUrl.toString();
599 } else {
600 d->prepend = text.left(text.length() - url.file().length());
601 }
602
603 d->complete_url = url.isURL();
604
605 // We use our custom sorter function if we are completing local paths
606 setSorterFunction(!d->complete_url ? sortLocalPaths : nullptr);
607
608 // If we typed an exact path to a directory, we block autosuggestion
609 // or else it would autosuggest the first child dir.
610 setShouldAutoSuggest(d->complete_url || (url.dir() != text));
611
612 QString aMatch;
613
614 // Environment variables
615 //
616 if (d->replace_env && d->envCompletion(url, &aMatch)) {
617 return aMatch;
618 }
619
620 // User directories
621 //
622 if (d->replace_home && d->userCompletion(url, &aMatch)) {
623 return aMatch;
624 }
625
626 // Replace user directories and variables
627 url.filter(d->replace_home, d->replace_env);
628
629 // qDebug() << "Filtered: proto=" << url.scheme()
630 // << ", dir=" << url.dir()
631 // << ", file=" << url.file()
632 // << ", kurl url=" << *url.kurl();
633
634 if (d->mode == ExeCompletion) {
635 // Executables
636 //
637 if (d->exeCompletion(url, &aMatch)) {
638 return aMatch;
639 }
640
641 // KRun can run "man:" and "info:" etc. so why not treat them
642 // as executables...
643
644 if (d->urlCompletion(url, &aMatch)) {
645 return aMatch;
646 }
647 } else {
648 // Local files, directories
649 //
650 if (d->fileCompletion(url, &aMatch)) {
651 return aMatch;
652 }
653
654 // All other...
655 //
656 if (d->urlCompletion(url, &aMatch)) {
657 return aMatch;
658 }
659 }
660
661 d->setListedUrl(CTNone);
662 stop();
663
664 return QString();
665}
666
667/*
668 * finished
669 *
670 * Go on and call KCompletion.
671 * Called when all matches have been added
672 */
673QString KUrlCompletionPrivate::finished()
674{
675 if (last_compl_type == CTInfo) {
676 return q->KCompletion::makeCompletion(compl_text.toLower());
677 } else {
678 return q->KCompletion::makeCompletion(compl_text);
679 }
680}
681
682/*
683 * isRunning
684 *
685 * Return true if either a KIO job or a thread is running
686 */
688{
689 return d->list_job || (d->dirListThread && !d->dirListThread->isFinished()) || (d->userListThread && !d->userListThread->isFinished());
690}
691
692/*
693 * stop
694 *
695 * Stop and delete a running KIO job or the DirLister
696 */
698{
699 if (d->list_job) {
700 d->list_job->kill();
701 d->list_job = nullptr;
702 }
703
704 if (d->dirListThread) {
705 d->dirListThread->requestTermination();
706 delete d->dirListThread;
707 d->dirListThread = nullptr;
708 }
709
710 if (d->userListThread) {
711 d->userListThread->requestTermination();
712 delete d->userListThread;
713 d->userListThread = nullptr;
714 }
715}
716
717/*
718 * Keep track of the last listed directory
719 */
720void KUrlCompletionPrivate::setListedUrl(ComplType complType, const QString &directory, const QString &filter, bool no_hidden)
721{
722 last_compl_type = complType;
723 last_path_listed = directory;
724 last_file_listed = filter;
725 last_no_hidden = no_hidden;
726 last_prepend = prepend;
727}
728
729bool KUrlCompletionPrivate::isListedUrl(ComplType complType, const QString &directory, const QString &filter, bool no_hidden)
730{
731 /* clang-format off */
732 return last_compl_type == complType
733 && (last_path_listed == directory || (directory.isEmpty() && last_path_listed.isEmpty()))
734 && (filter.startsWith(last_file_listed) || (filter.isEmpty() && last_file_listed.isEmpty()))
735 && last_no_hidden == no_hidden
736 && last_prepend == prepend; // e.g. relative path vs absolute
737 /* clang-format on */
738}
739
740/*
741 * isAutoCompletion
742 *
743 * Returns true if completion mode is Auto or Popup
744 */
745bool KUrlCompletionPrivate::isAutoCompletion()
746{
747 /* clang-format off */
752 /* clang-format on */
753}
754//////////////////////////////////////////////////
755//////////////////////////////////////////////////
756// User directories
757//
758
759bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
760{
761 if (url.scheme() != QLatin1String("file") || !url.dir().isEmpty() || !url.file().startsWith(QLatin1Char('~')) || !prepend.isEmpty()) {
762 return false;
763 }
764
765 if (!isListedUrl(CTUser)) {
766 q->stop();
767 q->clear();
768 setListedUrl(CTUser);
769
770 Q_ASSERT(!userListThread); // caller called stop()
771 userListThread = new UserListThread(this);
772 QObject::connect(userListThread, &CompletionThread::completionThreadDone, q, [this](QThread *thread, const QStringList &matches) {
773 slotCompletionThreadDone(thread, matches);
774 });
775 userListThread->start();
776
777 // If the thread finishes quickly make sure that the results
778 // are added to the first matching case.
779
780 userListThread->wait(initialWaitDuration());
781 const QStringList l = userListThread->matches();
782 addMatches(l);
783 }
784 *pMatch = finished();
785 return true;
786}
787
788/////////////////////////////////////////////////////
789/////////////////////////////////////////////////////
790// Environment variables
791//
792
793bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
794{
795 if (url.file().isEmpty() || url.file().at(0) != QLatin1Char('$')) {
796 return false;
797 }
798
799 if (!isListedUrl(CTEnv)) {
800 q->stop();
801 q->clear();
802
804 const QStringList keys = env.keys();
805
806 QStringList l;
807 l.reserve(keys.size());
808 for (const QString &key : keys) {
809 l.append(prepend + QLatin1Char('$') + key);
810 }
811
812 addMatches(l);
813 }
814
815 setListedUrl(CTEnv);
816
817 *pMatch = finished();
818 return true;
819}
820
821//////////////////////////////////////////////////
822//////////////////////////////////////////////////
823// Executables
824//
825
826bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
827{
828 if (!url.isLocalFile()) {
829 return false;
830 }
831
832 QString directory = unescape(url.dir()); // remove escapes
833
834 // Find directories to search for completions, either
835 //
836 // 1. complete path given in url
837 // 2. current directory (d->cwd)
838 // 3. $PATH
839 // 4. no directory at all
840
841 QStringList dirList;
842
843 if (!url.file().isEmpty()) {
844 // $PATH
846
847 QStringList::Iterator it = dirList.begin();
848
849 for (; it != dirList.end(); ++it) {
850 it->append(QLatin1Char('/'));
851 }
852 } else if (Utils::isAbsoluteLocalPath(directory)) {
853 // complete path in url
854 dirList.append(directory);
855 } else if (!directory.isEmpty() && !cwd.isEmpty()) {
856 // current directory
857 dirList.append(cwd.toLocalFile() + QLatin1Char('/') + directory);
858 }
859
860 // No hidden files unless the user types "."
861 bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.');
862
863 // List files if needed
864 //
865 if (!isListedUrl(CTExe, directory, url.file(), no_hidden_files)) {
866 q->stop();
867 q->clear();
868
869 setListedUrl(CTExe, directory, url.file(), no_hidden_files);
870
871 *pMatch = listDirectories(dirList, url.file(), true, false, no_hidden_files);
872 } else {
873 *pMatch = finished();
874 }
875
876 return true;
877}
878
879//////////////////////////////////////////////////
880//////////////////////////////////////////////////
881// Local files
882//
883
884bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
885{
886 if (!url.isLocalFile()) {
887 return false;
888 }
889
890 QString directory = unescape(url.dir());
891
892 if (url.url() == QLatin1String("..")) {
893 *pMatch = QStringLiteral("..");
894 return true;
895 }
896
897 // qDebug() << "fileCompletion" << url << "dir=" << dir;
898
899 // Find directories to search for completions, either
900 //
901 // 1. complete path given in url
902 // 2. current directory (d->cwd)
903 // 3. no directory at all
904
905 QStringList dirList;
906
907 if (Utils::isAbsoluteLocalPath(directory)) {
908 // complete path in url
909 dirList.append(directory);
910 } else if (!cwd.isEmpty()) {
911 // current directory
912 QString dirToAdd = cwd.toLocalFile();
913 if (!directory.isEmpty()) {
914 Utils::appendSlash(dirToAdd);
915 dirToAdd += directory;
916 }
917 dirList.append(dirToAdd);
918 }
919
920 // No hidden files unless the user types "."
921 bool no_hidden_files = !url.file().startsWith(QLatin1Char('.'));
922
923 // List files if needed
924 //
925 if (!isListedUrl(CTFile, directory, QString(), no_hidden_files)) {
926 q->stop();
927 q->clear();
928
929 setListedUrl(CTFile, directory, QString(), no_hidden_files);
930
931 // Append '/' to directories in Popup mode?
932 bool append_slash =
934
935 bool only_dir = (mode == KUrlCompletion::DirCompletion);
936
937 *pMatch = listDirectories(dirList, QString(), false, only_dir, no_hidden_files, append_slash);
938 } else {
939 *pMatch = finished();
940 }
941
942 return true;
943}
944
945//////////////////////////////////////////////////
946//////////////////////////////////////////////////
947// URLs not handled elsewhere...
948//
949
950static bool isLocalProtocol(const QString &protocol)
951{
952 return (KProtocolInfo::protocolClass(protocol) == QLatin1String(":local"));
953}
954
955bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
956{
957 // qDebug() << *url.kurl();
958 if (onlyLocalProto && isLocalProtocol(url.scheme())) {
959 return false;
960 }
961
962 // Use d->cwd as base url in case url is not absolute
963 QUrl url_dir = url.kurl();
964 if (url_dir.isRelative() && !cwd.isEmpty()) {
965 // Create an URL with the directory to be listed
966 url_dir = cwd.resolved(url_dir);
967 }
968
969 // url is malformed
970 if (!url_dir.isValid() || url.scheme().isEmpty()) {
971 return false;
972 }
973
974 // non local urls
975 if (!isLocalProtocol(url.scheme())) {
976 // url does not specify host
977 if (url_dir.host().isEmpty()) {
978 return false;
979 }
980
981 // url does not specify a valid directory
983 return false;
984 }
985
986 // automatic completion is disabled
987 if (isAutoCompletion() && !url_auto_completion) {
988 return false;
989 }
990 }
991
992 // url handler doesn't support listing
993 if (!KProtocolManager::supportsListing(url_dir)) {
994 return false;
995 }
996
997 // Remove escapes
998 const QString directory = unescape(url_dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path());
999 url_dir.setPath(directory);
1000
1001 // List files if needed
1002 //
1003 if (!isListedUrl(CTUrl, directory, url.file())) {
1004 q->stop();
1005 q->clear();
1006
1007 setListedUrl(CTUrl, directory, QString());
1008
1009 QList<QUrl> url_list;
1010 url_list.append(url_dir);
1011
1012 listUrls(url_list, QString(), false);
1013
1014 pMatch->clear();
1015 } else if (!q->isRunning()) {
1016 *pMatch = finished();
1017 } else {
1018 pMatch->clear();
1019 }
1020
1021 return true;
1022}
1023
1024//////////////////////////////////////////////////
1025//////////////////////////////////////////////////
1026// Directory and URL listing
1027//
1028
1029/*
1030 * addMatches
1031 *
1032 * Called to add matches to KCompletion
1033 */
1034void KUrlCompletionPrivate::addMatches(const QStringList &matchList)
1035{
1036 q->insertItems(matchList);
1037}
1038
1039/*
1040 * listDirectories
1041 *
1042 * List files starting with 'filter' in the given directories,
1043 * either using DirLister or listURLs()
1044 *
1045 * In either case, addMatches() is called with the listed
1046 * files, and eventually finished() when the listing is done
1047 *
1048 * Returns the match if available, or QString() if
1049 * DirLister timed out or using kio
1050 */
1051QString KUrlCompletionPrivate::listDirectories(const QStringList &dirList,
1052 const QString &filter,
1053 bool only_exe,
1054 bool only_dir,
1055 bool no_hidden,
1056 bool append_slash_to_dir)
1057{
1058 assert(!q->isRunning());
1059
1060 if (qEnvironmentVariableIsEmpty("KURLCOMPLETION_LOCAL_KIO")) {
1061 qCDebug(KIO_WIDGETS) << "Listing directories:" << dirList << "with filter=" << filter << "using thread";
1062
1063 // Don't use KIO
1064
1065 QStringList dirs;
1066
1068 for (QStringList::ConstIterator it = dirList.constBegin(); it != end; ++it) {
1069 QUrl url = QUrl::fromLocalFile(*it);
1070 if (KUrlAuthorized::authorizeUrlAction(QStringLiteral("list"), QUrl(), url)) {
1071 dirs.append(*it);
1072 }
1073 }
1074
1075 Q_ASSERT(!dirListThread); // caller called stop()
1076 dirListThread = new DirectoryListThread(this, dirs, filter, mimeTypeFilters, only_exe, only_dir, no_hidden, append_slash_to_dir);
1077 QObject::connect(dirListThread, &CompletionThread::completionThreadDone, q, [this](QThread *thread, const QStringList &matches) {
1078 slotCompletionThreadDone(thread, matches);
1079 });
1080 dirListThread->start();
1081 dirListThread->wait(initialWaitDuration());
1082 qCDebug(KIO_WIDGETS) << "Adding initial matches:" << dirListThread->matches();
1083 addMatches(dirListThread->matches());
1084
1085 return finished();
1086 }
1087
1088 // Use KIO
1089 // qDebug() << "Listing (listDirectories):" << dirList << "with KIO";
1090
1091 QList<QUrl> url_list;
1092
1095
1096 url_list.reserve(dirList.size());
1097 for (; it != end; ++it) {
1098 url_list.append(QUrl(*it));
1099 }
1100
1101 listUrls(url_list, filter, only_exe, no_hidden);
1102 // Will call addMatches() and finished()
1103
1104 return QString();
1105}
1106
1107/*
1108 * listURLs
1109 *
1110 * Use KIO to list the given urls
1111 *
1112 * addMatches() is called with the listed files
1113 * finished() is called when the listing is done
1114 */
1115void KUrlCompletionPrivate::listUrls(const QList<QUrl> &urls, const QString &filter, bool only_exe, bool no_hidden)
1116{
1117 assert(list_urls.isEmpty());
1118 assert(list_job == nullptr);
1119
1120 list_urls = urls;
1121 list_urls_filter = filter;
1122 list_urls_only_exe = only_exe;
1123 list_urls_no_hidden = no_hidden;
1124
1125 // qDebug() << "Listing URLs:" << *urls[0] << ",...";
1126
1127 // Start it off by calling slotIOFinished
1128 //
1129 // This will start a new list job as long as there
1130 // are urls in d->list_urls
1131 //
1132 slotIOFinished(nullptr);
1133}
1134
1135/*
1136 * slotEntries
1137 *
1138 * Receive files listed by KIO and call addMatches()
1139 */
1140void KUrlCompletionPrivate::slotEntries(KIO::Job *, const KIO::UDSEntryList &entries)
1141{
1142 QStringList matchList;
1143
1144 const QString filter = list_urls_filter;
1145 const int filter_len = filter.length();
1146
1147 // Iterate over all files
1148 for (const auto &entry : entries) {
1149 const QString udsUrl = entry.stringValue(KIO::UDSEntry::UDS_URL);
1150
1151 QString entry_name;
1152 if (!udsUrl.isEmpty()) {
1153 // qDebug() << "url:" << url;
1154 entry_name = QUrl(udsUrl).fileName();
1155 } else {
1156 entry_name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
1157 }
1158
1159 // This can happen with kdeconnect://deviceId as a completion for kdeconnect:/,
1160 // there's no fileName [and the UDS_NAME is unrelated, can't use that].
1161 // This code doesn't support completing hostnames anyway (see addPathToUrl below).
1162 if (entry_name.isEmpty()) {
1163 continue;
1164 }
1165
1166 if (entry_name.at(0) == QLatin1Char('.')
1167 && (list_urls_no_hidden || entry_name.length() == 1 || (entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.')))) {
1168 continue;
1169 }
1170
1171 const bool isDir = entry.isDir();
1172
1173 if (mode == KUrlCompletion::DirCompletion && !isDir) {
1174 continue;
1175 }
1176
1177 if (filter_len != 0 && QStringView(entry_name).left(filter_len) != filter) {
1178 continue;
1179 }
1180
1181 if (!mimeTypeFilters.isEmpty() && !isDir && !mimeTypeFilters.contains(entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE))) {
1182 continue;
1183 }
1184
1185 QString toAppend = entry_name;
1186
1187 if (isDir) {
1188 toAppend.append(QLatin1Char('/'));
1189 }
1190
1191 if (!list_urls_only_exe || (entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & s_modeExe) // true if executable
1192 ) {
1193 if (complete_url) {
1194 QUrl url(prepend);
1195 addPathToUrl(url, toAppend);
1196 matchList.append(url.toDisplayString());
1197 } else {
1198 matchList.append(prepend + toAppend);
1199 }
1200 }
1201 }
1202
1203 addMatches(matchList);
1204}
1205
1206/*
1207 * slotIOFinished
1208 *
1209 * Called when a KIO job is finished.
1210 *
1211 * Start a new list job if there are still urls in
1212 * list_urls, otherwise call finished()
1213 */
1214void KUrlCompletionPrivate::slotIOFinished(KJob *job)
1215{
1216 assert(job == list_job);
1217 Q_UNUSED(job)
1218
1219 if (list_urls.isEmpty()) {
1220 list_job = nullptr;
1221
1222 finished(); // will call KCompletion::makeCompletion()
1223
1224 } else {
1225 QUrl kurl(list_urls.takeFirst());
1226
1227 // list_urls.removeAll( kurl );
1228
1229 // qDebug() << "Start KIO::listDir" << kurl;
1230
1231 list_job = KIO::listDir(kurl, KIO::HideProgressInfo);
1232 list_job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true"));
1233
1234 assert(list_job);
1235
1236 q->connect(list_job, &KJob::result, q, [this](KJob *job) {
1237 slotIOFinished(job);
1238 });
1239
1240 q->connect(list_job, &KIO::ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &list) {
1241 slotEntries(job, list);
1242 });
1243 }
1244}
1245
1246///////////////////////////////////////////////////
1247///////////////////////////////////////////////////
1248
1249/*
1250 * postProcessMatch, postProcessMatches
1251 *
1252 * Called by KCompletion before emitting match() and matches()
1253 *
1254 * Append '/' to directories for file completion. This is
1255 * done here to avoid stat()'ing a lot of files
1256 */
1257void KUrlCompletion::postProcessMatch(QString *pMatch) const
1258{
1259 // qDebug() << *pMatch;
1260
1261 if (!pMatch->isEmpty() && pMatch->startsWith(QLatin1String("file:"))) {
1262 // Add '/' to directories in file completion mode
1263 // unless it has already been done
1264 if (d->last_compl_type == CTFile && pMatch->at(pMatch->length() - 1) != QLatin1Char('/')) {
1265 QString copy = QUrl(*pMatch).toLocalFile();
1266 expandTilde(copy);
1267 expandEnv(copy);
1268 if (!Utils::isAbsoluteLocalPath(copy)) {
1269 copy.prepend(d->cwd.toLocalFile() + QLatin1Char('/'));
1270 }
1271
1272 // qDebug() << "stat'ing" << copy;
1273
1274 QByteArray file = QFile::encodeName(copy);
1275
1276 QT_STATBUF sbuff;
1277 if (QT_STAT(file.constData(), &sbuff) == 0) {
1278 if (Utils::isDirMask(sbuff.st_mode)) {
1279 pMatch->append(QLatin1Char('/'));
1280 }
1281 } else {
1282 // qDebug() << "Could not stat file" << copy;
1283 }
1284 }
1285 }
1286}
1287
1288void KUrlCompletion::postProcessMatches(QStringList * /*matches*/) const
1289{
1290 // Maybe '/' should be added to directories here as in
1291 // postProcessMatch() but it would slow things down
1292 // when there are a lot of matches...
1293}
1294
1295void KUrlCompletion::postProcessMatches(KCompletionMatches * /*matches*/) const
1296{
1297 // Maybe '/' should be added to directories here as in
1298 // postProcessMatch() but it would slow things down
1299 // when there are a lot of matches...
1300}
1301
1302void KUrlCompletionPrivate::slotCompletionThreadDone(QThread *thread, const QStringList &matches)
1303{
1304 if (thread != userListThread && thread != dirListThread) {
1305 qCDebug(KIO_WIDGETS) << "got" << matches.count() << "outdated matches";
1306 return;
1307 }
1308
1309 qCDebug(KIO_WIDGETS) << "got" << matches.count() << "matches at end of thread";
1310 q->setItems(matches);
1311
1312 if (userListThread == thread) {
1313 thread->wait();
1314 delete thread;
1315 userListThread = nullptr;
1316 } else if (dirListThread == thread) {
1317 thread->wait();
1318 delete thread;
1319 dirListThread = nullptr;
1320 }
1321 finished(); // will call KCompletion::makeCompletion()
1322}
1323
1324// static
1325QString KUrlCompletion::replacedPath(const QString &text, bool replaceHome, bool replaceEnv)
1326{
1327 if (text.isEmpty()) {
1328 return text;
1329 }
1330
1331 KUrlCompletionPrivate::MyURL url(text, QUrl()); // no need to replace something of our current cwd
1332 if (!url.kurl().isLocalFile()) {
1333 return text;
1334 }
1335
1336 url.filter(replaceHome, replaceEnv);
1337 return url.dir() + url.file();
1338}
1339
1341{
1342 return replacedPath(text, d->replace_home, d->replace_env);
1343}
1344
1346{
1347 d->mimeTypeFilters = mimeTypeFilters;
1348}
1349
1351{
1352 return d->mimeTypeFilters;
1353}
1354
1355/////////////////////////////////////////////////////////
1356/////////////////////////////////////////////////////////
1357// Static functions
1358
1359/*
1360 * expandEnv
1361 *
1362 * Expand environment variables in text. Escaped '$' are ignored.
1363 * Return true if expansion was made.
1364 */
1365static bool expandEnv(QString &text)
1366{
1367 // Find all environment variables beginning with '$'
1368 //
1369 int pos = 0;
1370
1371 bool expanded = false;
1372
1373 while ((pos = text.indexOf(QLatin1Char('$'), pos)) != -1) {
1374 // Skip escaped '$'
1375 //
1376 if (pos > 0 && text.at(pos - 1) == QLatin1Char('\\')) {
1377 pos++;
1378 }
1379 // Variable found => expand
1380 //
1381 else {
1382 // Find the end of the variable = next '/' or ' '
1383 //
1384 int pos2 = text.indexOf(QLatin1Char(' '), pos + 1);
1385 int pos_tmp = text.indexOf(QLatin1Char('/'), pos + 1);
1386
1387 if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) {
1388 pos2 = pos_tmp;
1389 }
1390
1391 if (pos2 == -1) {
1392 pos2 = text.length();
1393 }
1394
1395 // Replace if the variable is terminated by '/' or ' '
1396 // and defined
1397 //
1398 if (pos2 >= 0) {
1399 const int len = pos2 - pos;
1400 const QStringView key = QStringView(text).mid(pos + 1, len - 1);
1401 const QString value = QString::fromLocal8Bit(qgetenv(key.toLocal8Bit().constData()));
1402
1403 if (!value.isEmpty()) {
1404 expanded = true;
1405 text.replace(pos, len, value);
1406 pos = pos + value.length();
1407 } else {
1408 pos = pos2;
1409 }
1410 }
1411 }
1412 }
1413
1414 return expanded;
1415}
1416
1417/*
1418 * expandTilde
1419 *
1420 * Replace "~user" with the users home directory
1421 * Return true if expansion was made.
1422 */
1423static bool expandTilde(QString &text)
1424{
1425 if (text.isEmpty() || (text.at(0) != QLatin1Char('~'))) {
1426 return false;
1427 }
1428
1429 bool expanded = false;
1430
1431 // Find the end of the user name = next '/' or ' '
1432 //
1433 int pos2 = text.indexOf(QLatin1Char(' '), 1);
1434 int pos_tmp = text.indexOf(QLatin1Char('/'), 1);
1435
1436 if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) {
1437 pos2 = pos_tmp;
1438 }
1439
1440 if (pos2 == -1) {
1441 pos2 = text.length();
1442 }
1443
1444 // Replace ~user if the user name is terminated by '/' or ' '
1445 //
1446 if (pos2 >= 0) {
1447 QString userName = text.mid(1, pos2 - 1);
1448 QString dir;
1449
1450 // A single ~ is replaced with $HOME
1451 //
1452 if (userName.isEmpty()) {
1453 dir = QDir::homePath();
1454 }
1455 // ~user is replaced with the dir from passwd
1456 //
1457 else {
1458 KUser user(userName);
1459 dir = user.homeDir();
1460 }
1461
1462 if (!dir.isEmpty()) {
1463 expanded = true;
1464 text.replace(0, pos2, dir);
1465 }
1466 }
1467
1468 return expanded;
1469}
1470
1471/*
1472 * unescape
1473 *
1474 * Remove escapes and return the result in a new string
1475 *
1476 */
1477static QString unescape(const QString &text)
1478{
1479 QString result;
1480 result.reserve(text.size());
1481
1482 for (const QChar ch : text) {
1483 if (ch != QLatin1Char('\\')) {
1484 result.append(ch);
1485 }
1486 }
1487
1488 return result;
1489}
1490
1491#include "kurlcompletion.moc"
1492#include "moc_kurlcompletion.cpp"
void insertItems(const QStringList &items)
virtual void setIgnoreCase(bool ignoreCase)
virtual void clear()
CompletionMode completionMode() const
void setSorterFunction(SorterFunction sortFunc)
void setShouldAutoSuggest(bool shouldAutosuggest)
virtual void setItems(const QStringList &itemList)
The base class for all jobs.
Definition job_base.h:45
void addMetaData(const QString &key, const QString &value)
Add key/value pair to the meta data that is sent to the worker.
Definition job.cpp:221
A ListJob is allows you to get the get the content of a directory.
Definition listjob.h:28
void entries(KIO::Job *job, const KIO::UDSEntryList &list)
This signal emits the entry found by the job while listing.
@ UDS_URL
An alternative URL (If different from the caption).
Definition udsentry.h:251
@ UDS_MIME_TYPE
A MIME type; the KIO worker should set it if it's known.
Definition udsentry.h:253
@ UDS_NAME
Filename - as displayed in directory listings etc.
Definition udsentry.h:224
@ UDS_ACCESS
Access permissions (part of the mode returned by stat)
Definition udsentry.h:232
void result(KJob *job)
static QString protocolClass(const QString &protocol)
Returns the protocol class for the specified protocol.
static bool supportsListing(const QUrl &url)
Returns whether the protocol can list files/objects.
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
This class does completion of URLs including user directories (~user) and environment variables.
QString makeCompletion(const QString &text) override
Finds completions to the given text.
virtual void setMode(Mode mode)
Changes the completion mode: exe or file completion.
virtual bool replaceEnv() const
Checks whether environment variables are completed and whether they are replaced internally while fin...
virtual void stop()
Stops asynchronous completion.
void setMimeTypeFilters(const QStringList &mimeTypes)
Sets the MIME type filters for the file dialog.
virtual QUrl dir() const
Returns the current directory, as it was given in setDir.
virtual bool replaceHome() const
Returns whether ~username is completed and whether ~username is replaced internally with the user's h...
virtual void setReplaceHome(bool replace)
Enables/disables completion of ~username and replacement (internally) of ~username with the user's ho...
KUrlCompletion()
Constructs a KUrlCompletion object in FileCompletion mode.
virtual void setReplaceEnv(bool replace)
Enables/disables completion and replacement (internally) of environment variables in URLs.
virtual void setDir(const QUrl &dir)
Sets the current directory (used as base for completion).
~KUrlCompletion() override
Destructs the KUrlCompletion object.
Mode
Determines how completion is done.
virtual Mode mode() const
Returns the completion mode: exe or file completion (default FileCompletion).
virtual bool isRunning() const
Check whether asynchronous completion is in progress.
QStringList mimeTypeFilters() const
Returns the MIME type filters for the file dialog.
QString replacedPath(const QString &text) const
Replaces username and/or environment variables, depending on the current settings and returns the fil...
static QStringList allUserNames(uint maxCount=KCOREADDONS_UINT_MAX)
KCALUTILS_EXPORT QString mimeType()
KIOCORE_EXPORT ListJob * listDir(const QUrl &url, JobFlags flags=DefaultFlags, ListJob::ListFlags listFlags=ListJob::ListFlag::IncludeHidden)
List the contents of url, which is assumed to be a directory.
Definition listjob.cpp:239
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
Definition job_base.h:251
KIOCORE_EXPORT QString dir(const QString &fileClass)
Returns the most recently used directory associated with this file-class.
KIOCORE_EXPORT QStringList list(const QString &fileClass)
Returns a list of directories associated with this file-class.
const QList< QKeySequence > & end()
const QList< QKeySequence > & copy()
bool authorizeUrlAction(const QString &action, const QUrl &baseURL, const QUrl &destURL)
Returns whether a certain URL related action is authorized.
T loadRelaxed() const const
void storeRelaxed(T newValue)
const char * constData() const const
bool isEmpty() const const
int toInt(bool *ok, int base) const const
void setCaseSensitivity(Qt::CaseSensitivity cs)
typedef Filters
QString homePath()
QChar listSeparator()
QByteArray encodeName(const QString &fileName)
QString fileName() const const
bool isDir() const const
typedef ConstIterator
typedef Iterator
void append(QList< T > &&value)
iterator begin()
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
iterator end()
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
value_type takeFirst()
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const const
Q_EMITQ_EMIT
Q_OBJECTQ_OBJECT
Q_SIGNALSQ_SIGNALS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QThread * thread() const const
QStringList keys() const const
QProcessEnvironment systemEnvironment()
QString & append(QChar ch)
const QChar at(qsizetype position) const const
void clear()
QString fromLocal8Bit(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString & prepend(QChar ch)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
void reserve(qsizetype size)
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toLower() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QStringView mid(qsizetype start, qsizetype length) const const
QByteArray toLocal8Bit() const const
CaseSensitive
SkipEmptyParts
QTextStream & left(QTextStream &stream)
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
bool isFinished() const const
void start(Priority priority)
bool wait(QDeadlineTimer deadline)
RemoveFilename
QUrl adjusted(FormattingOptions options) const const
QString fileName(ComponentFormattingOptions options) const const
QUrl fromLocalFile(const QString &localFile)
QString host(ComponentFormattingOptions options) const const
bool isEmpty() const const
bool isLocalFile() const const
bool isRelative() const const
bool isValid() const const
QString path(ComponentFormattingOptions options) const const
QUrl resolved(const QUrl &relative) const const
QString scheme() const const
void setFragment(const QString &fragment, ParsingMode mode)
void setPath(const QString &path, ParsingMode mode)
void setQuery(const QString &query, ParsingMode mode)
QString toDisplayString(FormattingOptions options) const const
QString toLocalFile() const const
QString toString(FormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:52 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.