34 #include <QtCore/QCoreApplication>
35 #include <QtCore/QMutableStringListIterator>
36 #include <QtCore/QRegExp>
37 #include <QtCore/QTimer>
38 #include <QtCore/QDir>
39 #include <QtCore/QDirIterator>
40 #include <QtCore/QFile>
41 #include <QtCore/QTextIStream>
42 #include <QtCore/QThread>
43 #include <QtGui/QActionEvent>
55 #include <sys/types.h>
61 #include <sys/param.h>
75 #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH)
80 class CompletionThread;
86 class KUrlCompletionPrivate
91 url_auto_completion(true),
96 ~KUrlCompletionPrivate();
99 void _k_slotIOFinished(
KJob*);
102 bool userCompletion(
const MyURL& url,
QString* match);
103 bool envCompletion(
const MyURL& url,
QString* match);
104 bool exeCompletion(
const MyURL& url,
QString* match);
105 bool fileCompletion(
const MyURL& url,
QString* match);
106 bool urlCompletion(
const MyURL& url,
QString* match);
108 bool isAutoCompletion();
113 bool only_exe =
false,
114 bool only_dir =
false,
115 bool no_hidden =
false,
116 bool stat_files =
true);
118 void listUrls(
const QList<KUrl> &urls,
120 bool only_exe =
false,
121 bool no_hidden =
false);
128 void setListedUrl(
int compl_type ,
131 bool no_hidden =
false);
133 bool isListedUrl(
int compl_type ,
136 bool no_hidden =
false);
139 QList<KUrl> list_urls;
144 bool url_auto_completion;
148 bool popup_append_slash;
170 bool list_urls_only_exe;
171 bool list_urls_no_hidden;
174 CompletionThread* userListThread;
175 CompletionThread* dirListThread;
183 class CompletionMatchEvent :
public QEvent
186 CompletionMatchEvent(CompletionThread* thread) :
187 QEvent(uniqueType()),
188 m_completionThread(thread)
191 CompletionThread* completionThread()
const {
192 return m_completionThread;
194 static Type uniqueType() {
195 return Type(User + 61080);
199 CompletionThread* m_completionThread;
202 class CompletionThread :
public QThread
205 CompletionThread(KUrlCompletionPrivate* receiver) :
207 m_prepend(receiver->prepend),
208 m_complete_url(receiver->complete_url),
209 m_receiver(receiver),
210 m_terminationRequested(false)
214 void requestTermination() {
215 m_terminationRequested =
true;
222 void addMatch(
const QString& match) {
223 m_matches.append(match);
225 bool terminationRequested()
const {
226 return m_terminationRequested;
229 if (!m_terminationRequested)
230 qApp->postEvent(m_receiver->q,
new CompletionMatchEvent(
this));
236 const bool m_complete_url;
239 KUrlCompletionPrivate* m_receiver;
241 bool m_terminationRequested;
249 class UserListThread :
public CompletionThread
252 UserListThread(KUrlCompletionPrivate* receiver) :
253 CompletionThread(receiver)
258 static const QChar tilde =
'~';
261 assert(m_prepend.isEmpty());
263 while ((pw = ::getpwent()) && !terminationRequested())
264 addMatch(tilde + QString::fromLocal8Bit(pw->pw_name));
274 class DirectoryListThread :
public CompletionThread
277 DirectoryListThread(KUrlCompletionPrivate* receiver,
283 bool appendSlashToDir) :
284 CompletionThread(receiver),
289 m_noHidden(noHidden),
290 m_appendSlashToDir(appendSlashToDir)
301 bool m_appendSlashToDir;
320 QStringList::ConstIterator
end = m_dirList.constEnd();
321 for (QStringList::ConstIterator it = m_dirList.constBegin();
322 it != end && !terminationRequested();
330 QString path = QDir::currentPath();
331 QDir::setCurrent(*it);
333 QDir::Filters iterator_filter = (m_noHidden ? QDir::Filter(0) : QDir::Hidden) | QDir::Readable | QDir::NoDotAndDotDot;
336 iterator_filter |= (QDir::Dirs | QDir::Files | QDir::Executable);
338 iterator_filter |= QDir::Dirs;
340 iterator_filter |= (QDir::Dirs | QDir::Files);
342 QDirIterator current_dir_iterator(*it, iterator_filter);
344 while (current_dir_iterator.hasNext()) {
345 current_dir_iterator.next();
347 QFileInfo file_info = current_dir_iterator.fileInfo();
348 const QString file_name = file_info.fileName();
352 if (m_filter.isEmpty() || file_name.startsWith(m_filter)) {
354 QString toAppend = m_complete_url ? QUrl::toPercentEncoding(file_name) : file_name;
356 if (m_appendSlashToDir && file_info.isDir())
357 toAppend.append(QLatin1Char(
'/'));
359 addMatch(m_prepend + toAppend);
364 QDir::setCurrent(path);
370 KUrlCompletionPrivate::~KUrlCompletionPrivate()
373 userListThread->requestTermination();
375 dirListThread->requestTermination();
383 class KUrlCompletionPrivate::MyURL
387 MyURL(
const MyURL& url);
395 return m_kurl.protocol();
415 void filter(
bool replace_user_dir,
bool replace_env);
425 KUrlCompletionPrivate::MyURL::MyURL(
const QString& _url,
const QString& cwd)
430 KUrlCompletionPrivate::MyURL::MyURL(
const MyURL& _url)
431 : m_kurl(_url.m_kurl)
434 m_isURL = _url.m_isURL;
437 void KUrlCompletionPrivate::MyURL::init(
const QString& _url,
const QString& cwd)
446 if (url_copy.startsWith(QLatin1Char(
'#'))) {
447 if (url_copy.length() > 1 && url_copy.at(1) == QLatin1Char(
'#'))
448 url_copy.replace(0, 2, QLatin1String(
"info:"));
450 url_copy.replace(0, 1, QLatin1String(
"man:"));
454 QRegExp protocol_regex = QRegExp(
"^(?![A-Za-z]:)[^/\\s\\\\]*:");
458 if (protocol_regex.indexIn(url_copy) == 0) {
459 m_kurl =
KUrl(url_copy);
463 if (!QDir::isRelativePath(url_copy) ||
464 url_copy.startsWith(QLatin1Char(
'~')) ||
465 url_copy.startsWith(QLatin1Char(
'$'))) {
467 m_kurl.setPath(url_copy);
470 m_kurl =
KUrl(url_copy);
473 m_kurl.addPath(url_copy);
479 KUrlCompletionPrivate::MyURL::~MyURL()
483 void KUrlCompletionPrivate::MyURL::filter(
bool replace_user_dir,
bool replace_env)
504 d(new KUrlCompletionPrivate(this))
517 void KUrlCompletionPrivate::init()
519 cwd = QDir::homePath();
523 last_no_hidden =
false;
531 url_auto_completion = cg.readEntry(
"alwaysAutoComplete",
true);
532 popup_append_slash = cg.readEntry(
"popupAppendSlash",
true);
533 onlyLocalProto = cg.readEntry(
"LocalProtocolsOnly",
false);
535 q->setIgnoreCase(
true);
560 return d->replace_env;
565 d->replace_env = replace;
570 return d->replace_home;
575 d->replace_home = replace;
587 KUrlCompletionPrivate::MyURL url(text, d->cwd);
589 d->compl_text = text;
593 int toRemove = url.file().length() - url.kurl().query().length();
594 if (url.kurl().hasRef())
595 toRemove += url.kurl().ref().length() + 1;
596 d->prepend = text.left(text.length() - toRemove);
597 d->complete_url = url.isURL();
603 if (d->replace_env && d->envCompletion(url, &aMatch))
608 if (d->replace_home && d->userCompletion(url, &aMatch))
612 url.filter(d->replace_home, d->replace_env);
622 if (d->exeCompletion(url, &aMatch))
628 if (d->urlCompletion(url, &aMatch))
633 if (d->fileCompletion(url, &aMatch))
638 if (d->urlCompletion(url, &aMatch))
654 QString KUrlCompletionPrivate::finished()
656 if (last_compl_type ==
CTInfo)
657 return q->KCompletion::makeCompletion(compl_text.toLower());
659 return q->KCompletion::makeCompletion(compl_text);
670 return d->list_job || (d->dirListThread && !d->dirListThread->isFinished());
685 if (d->dirListThread) {
686 d->dirListThread->requestTermination();
687 d->dirListThread = 0;
694 void KUrlCompletionPrivate::setListedUrl(
int complType,
699 last_compl_type = complType;
700 last_path_listed = directory;
701 last_file_listed = filter;
702 last_no_hidden = (int) no_hidden;
703 last_prepend = prepend;
706 bool KUrlCompletionPrivate::isListedUrl(
int complType,
711 return last_compl_type == complType
712 && (last_path_listed == directory
713 || (directory.isEmpty() && last_path_listed.isEmpty()))
714 && (filter.startsWith (last_file_listed)
715 || (filter.isEmpty() && last_file_listed.isEmpty()))
716 && last_no_hidden == (int) no_hidden
717 && last_prepend == prepend;
725 bool KUrlCompletionPrivate::isAutoCompletion()
737 bool KUrlCompletionPrivate::userCompletion(
const KUrlCompletionPrivate::MyURL& url,
QString* pMatch)
739 if (url.protocol() != QLatin1String(
"file")
740 || !url.dir().isEmpty()
741 || !url.file().startsWith(QLatin1Char(
'~')))
744 if (!isListedUrl(
CTUser)) {
748 if (!userListThread) {
749 userListThread =
new UserListThread(
this);
750 userListThread->start();
755 userListThread->wait(200);
760 *pMatch = finished();
770 extern char** environ;
773 bool KUrlCompletionPrivate::envCompletion(
const KUrlCompletionPrivate::MyURL& url,
QString* pMatch)
775 if (url.file().isEmpty() || url.file().at(0) != QLatin1Char(
'$'))
778 if (!isListedUrl(
CTEnv)) {
782 char** env = environ;
784 QString dollar = QLatin1String(
"$");
789 QString s = QString::fromLocal8Bit(*env);
791 int pos = s.indexOf(QLatin1Char(
'='));
797 l.append(prepend + dollar + s.left(pos));
807 *pMatch = finished();
816 bool KUrlCompletionPrivate::exeCompletion(
const KUrlCompletionPrivate::MyURL& url,
QString* pMatch)
818 if (url.protocol() != QLatin1String(
"file"))
832 if (!url.file().isEmpty()) {
834 dirList = QString::fromLocal8Bit(qgetenv(
"PATH")).split(
835 KPATH_SEPARATOR, QString::SkipEmptyParts);
837 QStringList::Iterator it = dirList.begin();
839 for (; it != dirList.end(); ++it)
840 it->append(QLatin1Char(
'/'));
841 }
else if (!QDir::isRelativePath(directory)) {
843 dirList.append(directory);
844 }
else if (!directory.isEmpty() && !cwd.isEmpty()) {
846 dirList.append(cwd + QLatin1Char(
'/') + directory);
850 bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char(
'.');
854 if (!isListedUrl(
CTExe, directory, url.file(), no_hidden_files)) {
858 setListedUrl(
CTExe, directory, url.file(), no_hidden_files);
860 *pMatch = listDirectories(dirList, url.file(),
true,
false, no_hidden_files);
861 }
else if (!q->isRunning()) {
862 *pMatch = finished();
865 setListedUrl(
CTExe, directory, url.file(), no_hidden_files);
877 bool KUrlCompletionPrivate::fileCompletion(
const KUrlCompletionPrivate::MyURL& url,
QString* pMatch)
879 if (url.protocol() != QLatin1String(
"file"))
884 if (url.url().length() && url.url().at(0) == QLatin1Char(
'.')) {
885 if (url.url().length() == 1) {
890 }
else if (url.url().length() == 2 && url.url().at(1) == QLatin1Char(
'.')) {
891 *pMatch = QLatin1String(
"..");
906 if (!QDir::isRelativePath(directory)) {
908 dirList.append(directory);
909 }
else if (!cwd.isEmpty()) {
912 if (!directory.isEmpty()) {
913 if (!cwd.endsWith(
'/'))
914 dirToAdd.append(QLatin1Char(
'/'));
915 dirToAdd.append(directory);
917 dirList.append(dirToAdd);
921 bool no_hidden_files = !url.file().startsWith(QLatin1Char(
'.'));
925 if (!isListedUrl(
CTFile, directory,
QString(), no_hidden_files)) {
932 bool append_slash = (popup_append_slash
938 *pMatch = listDirectories(dirList,
QString(),
false, only_dir, no_hidden_files,
940 }
else if (!q->isRunning()) {
941 *pMatch = finished();
959 bool KUrlCompletionPrivate::urlCompletion(
const KUrlCompletionPrivate::MyURL& url,
QString* pMatch)
966 KUrl url_dir = url.kurl();
967 if (url_dir.isRelative() && !cwd.isEmpty()) {
968 const KUrl url_cwd (cwd);
970 url_dir =
KUrl(url_cwd, url_dir.
url());
974 if (!url_dir.isValid())
980 if (url_dir.host().isEmpty())
988 if (isAutoCompletion() && !url_auto_completion)
1011 QList<KUrl> url_list;
1012 url_list.append(url_dir);
1014 listUrls(url_list,
QString(),
false);
1017 }
else if (!q->isRunning()) {
1018 *pMatch = finished();
1036 void KUrlCompletionPrivate::addMatches(
const QStringList& matchList)
1038 q->insertItems(matchList);
1053 QString KUrlCompletionPrivate::listDirectories(
1059 bool append_slash_to_dir)
1061 assert(!q->isRunning());
1063 if (qgetenv(
"KURLCOMPLETION_LOCAL_KIO").isEmpty()) {
1070 dirListThread->requestTermination();
1074 QStringList::ConstIterator end = dirList.constEnd();
1075 for (QStringList::ConstIterator it = dirList.constBegin();
1084 dirListThread =
new DirectoryListThread(
this, dirs, filter, only_exe, only_dir,
1085 no_hidden, append_slash_to_dir);
1086 dirListThread->start();
1087 dirListThread->wait(200);
1088 addMatches(dirListThread->matches());
1096 QList<KUrl> url_list;
1098 QStringList::ConstIterator it = dirList.constBegin();
1099 QStringList::ConstIterator end = dirList.constEnd();
1101 for (; it != end; ++it) {
1102 url_list.append(
KUrl(*it));
1105 listUrls(url_list, filter, only_exe, no_hidden);
1119 void KUrlCompletionPrivate::listUrls(
1120 const QList<KUrl> &urls,
1125 assert(list_urls.isEmpty());
1126 assert(list_job == 0L);
1129 list_urls_filter = filter;
1130 list_urls_only_exe = only_exe;
1131 list_urls_no_hidden = no_hidden;
1140 _k_slotIOFinished(0);
1152 KIO::UDSEntryList::ConstIterator it = entries.constBegin();
1153 const KIO::UDSEntryList::ConstIterator end = entries.constEnd();
1155 QString filter = list_urls_filter;
1157 int filter_len = filter.length();
1161 for (; it != end; ++it) {
1166 if (!url.isEmpty()) {
1175 if ((!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char(
'.')) &&
1176 (list_urls_no_hidden ||
1177 entry_name.length() == 1 ||
1178 (entry_name.length() == 2 && entry_name.at(1) == QLatin1Char(
'.'))))
1181 const bool isDir = entry.
isDir();
1186 if (filter_len == 0 || entry_name.left(filter_len) == filter) {
1188 QString toAppend = complete_url ? QUrl::toPercentEncoding(entry_name) : entry_name;
1191 toAppend.append(QLatin1Char(
'/'));
1193 if (!list_urls_only_exe ||
1196 matchList.append(prepend + toAppend);
1201 addMatches(matchList);
1212 void KUrlCompletionPrivate::_k_slotIOFinished(
KJob* job)
1214 assert(job == list_job); Q_UNUSED(job)
1216 if (list_urls.isEmpty()) {
1224 KUrl kurl(list_urls.takeFirst());
1231 list_job->addMetaData(
"no-auth-prompt",
"true");
1235 q->connect(list_job,
1236 SIGNAL(result(
KJob*)),
1237 SLOT(_k_slotIOFinished(
KJob*)));
1239 q->connect(list_job,
1260 if (!pMatch->isEmpty()) {
1264 if (d->last_compl_type ==
CTFile
1265 && pMatch->at(pMatch->length() - 1) != QLatin1Char(
'/')) {
1268 if (pMatch->startsWith(QLatin1String(
"file:")))
1276 DWORD dwAttr = GetFileAttributesW((LPCWSTR) copy.utf16());
1277 if (dwAttr == INVALID_FILE_ATTRIBUTES) {
1278 kDebug() <<
"Could not get file attribs ( "
1282 }
else if ((dwAttr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
1283 pMatch->append(QLatin1Char(
'/'));
1285 if (QDir::isRelativePath(copy))
1286 copy.prepend(d->cwd + QLatin1Char(
'/'));
1290 KDE_struct_stat sbuff;
1292 QByteArray file = QFile::encodeName(copy);
1294 if (KDE_stat(file.data(), &sbuff) == 0) {
1295 if (S_ISDIR(sbuff.st_mode))
1296 pMatch->append(QLatin1Char(
'/'));
1321 if (e->type() == CompletionMatchEvent::uniqueType()) {
1323 CompletionMatchEvent* matchEvent =
static_cast<CompletionMatchEvent*
>(e);
1325 matchEvent->completionThread()->wait();
1327 if (!d->isListedUrl(
CTUser)) {
1330 d->addMatches(matchEvent->completionThread()->matches());
1335 if (d->userListThread == matchEvent->completionThread())
1336 d->userListThread = 0;
1338 if (d->dirListThread == matchEvent->completionThread())
1339 d->dirListThread = 0;
1341 delete matchEvent->completionThread();
1351 KUrlCompletionPrivate::MyURL url(text,
QString());
1352 if (!url.kurl().isLocalFile())
1355 url.filter(replaceHome, replaceEnv);
1356 return url.dir() + url.file();
1362 return replacedPath(text, d->replace_home, d->replace_env);
1381 bool expanded =
false;
1383 while ((pos = text.indexOf(QLatin1Char(
'$'), pos)) != -1) {
1387 if (pos > 0 && text.at(pos - 1) == QLatin1Char(
'\\')) {
1395 int pos2 = text.indexOf(QLatin1Char(
' '), pos + 1);
1396 int pos_tmp = text.indexOf(QLatin1Char(
'/'), pos + 1);
1398 if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2))
1402 pos2 = text.length();
1408 int len = pos2 - pos;
1409 QString key = text.mid(pos + 1, len - 1);
1411 QString::fromLocal8Bit(qgetenv(key.toLocal8Bit()));
1413 if (!value.isEmpty()) {
1415 text.replace(pos, len, value);
1416 pos = pos + value.length();
1435 if (text.isEmpty() || (text.at(0) != QLatin1Char(
'~')))
1438 bool expanded =
false;
1442 int pos2 = text.indexOf(QLatin1Char(
' '), 1);
1443 int pos_tmp = text.indexOf(QLatin1Char(
'/'), 1);
1445 if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2))
1449 pos2 = text.length();
1455 QString user = text.mid(1, pos2 - 1);
1460 if (user.isEmpty()) {
1461 dir = QDir::homePath();
1466 struct passwd* pw = ::getpwnam(user.toLocal8Bit());
1469 dir = QFile::decodeName(pw->pw_dir);
1474 if (!dir.isEmpty()) {
1476 text.replace(0, pos2, dir);
1493 for (
int pos = 0; pos < text.length(); pos++)
1494 if (text.at(pos) != QLatin1Char(
'\\'))
1495 result.insert(result.length(), text.at(pos));
1500 #include "kurlcompletion.moc"