1 /* -*- indent-tabs-mode: t; tab-width: 4; c-basic-offset:4 -*-
3 This file is part of the KDE libraries
4 Copyright (C) 2000 David Smith <dsmith@algonet.se>
5 Copyright (C) 2004 Scott Wheeler <wheeler@kde.org>
7 This class was inspired by a previous KUrlCompletion by
8 Henner Zeller <zeller@think.de>
10 This library is free software; you can redistribute it and/or
11 modify it under the terms of the GNU Library General Public
12 License as published by the Free Software Foundation; either
13 version 2 of the License, or (at your option) any later version.
15 This library is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 Library General Public License for more details.
20 You should have received a copy of the GNU Library General Public License
21 along with this library; see the file COPYING.LIB. If not, write to
22 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 Boston, MA 02110-1301, USA.
26 #include "kurlcompletion.h"
34 #include <QtCore/QMutableStringListIterator>
35 #include <QtCore/QRegExp>
36 #include <QtCore/QTimer>
37 #include <QtCore/QDir>
38 #include <QtCore/QDirIterator>
39 #include <QtCore/QFile>
40 #include <QtCore/QTextIStream>
41 #include <QtCore/QThread>
42 #include <QtGui/QActionEvent>
44 #include <kapplication.h>
45 #include <kauthorized.h>
49 #include <kprotocolmanager.h>
54 #include <sys/types.h>
60 #include <sys/param.h>
61 #include <kconfiggroup.h>
64 #include <kkernel_win.h>
67 static bool expandTilde(QString
&);
68 static bool expandEnv(QString
&);
70 static QString
unescape(const QString
&text
);
72 // Permission mask for files that are executable by
73 // user, group or other
74 #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH)
76 // Constants for types of completion
77 enum ComplType
{CTNone
=0, CTEnv
, CTUser
, CTMan
, CTExe
, CTFile
, CTUrl
, CTInfo
};
79 class CompletionThread
;
81 ///////////////////////////////////////////////////////
82 ///////////////////////////////////////////////////////
83 // KUrlCompletionPrivate
85 class KUrlCompletionPrivate
88 KUrlCompletionPrivate(KUrlCompletion
*parent
)
90 url_auto_completion(true),
96 ~KUrlCompletionPrivate();
98 void _k_slotEntries( KIO::Job
*, const KIO::UDSEntryList
& );
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();
110 // List the next dir in m_dirs
111 QString
listDirectories(const QStringList
&,
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
,
119 const QString
&filter
= QString(),
120 bool only_exe
= false,
121 bool no_hidden
= false );
123 void addMatches( const QStringList
& );
128 void setListedUrl(int compl_type
/* enum ComplType */,
129 const QString
& dir
= QString(),
130 const QString
& filter
= QString(),
131 bool no_hidden
= false );
133 bool isListedUrl( int compl_type
/* enum ComplType */,
134 const QString
& dir
= QString(),
135 const QString
& filter
= QString(),
136 bool no_hidden
= false );
139 QList
<KUrl
*> list_urls
;
143 // urlCompletion() in Auto/Popup mode?
144 bool url_auto_completion
;
146 // Append '/' to directories in Popup mode?
147 // Doing that stat's all files and is slower
148 bool popup_append_slash
;
150 // Keep track of currently listed files to avoid reading them again
151 QString last_path_listed
;
152 QString last_file_listed
;
153 QString last_prepend
;
157 QString cwd
; // "current directory" = base dir for completion
159 KUrlCompletion::Mode mode
; // ExeCompletion, FileCompletion, DirCompletion
162 bool complete_url
; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path
164 KIO::ListJob
*list_job
; // kio job to list directories
166 QString prepend
; // text to prepend to listed items
167 QString compl_text
; // text to pass on to KCompletion
169 // Filters for files read with kio
170 bool list_urls_only_exe
; // true = only list executables
171 bool list_urls_no_hidden
;
172 QString list_urls_filter
; // filter for listed files
174 CompletionThread
*userListThread
;
175 CompletionThread
*dirListThread
;
179 * A custom event type that is used to return a list of completion
180 * matches from an asynchronous lookup.
183 class CompletionMatchEvent
: public QEvent
186 CompletionMatchEvent( CompletionThread
*thread
) :
187 QEvent( uniqueType() ),
188 m_completionThread( thread
)
191 CompletionThread
*completionThread() const { return m_completionThread
; }
192 static Type
uniqueType() { return Type(User
+ 61080); }
195 CompletionThread
*m_completionThread
;
198 class CompletionThread
: public QThread
201 CompletionThread( KUrlCompletionPrivate
*receiver
) :
203 m_prepend( receiver
->prepend
),
204 m_complete_url( receiver
->complete_url
),
205 m_receiver( receiver
),
206 m_terminationRequested( false )
210 void requestTermination() { m_terminationRequested
= true; }
211 QStringList
matches() const { return m_matches
; }
214 void addMatch( const QString
&match
) { m_matches
.append( match
); }
215 bool terminationRequested() const { return m_terminationRequested
; }
218 if ( !m_terminationRequested
)
219 qApp
->postEvent( m_receiver
->q
, new CompletionMatchEvent( this ) );
224 const QString m_prepend
;
225 const bool m_complete_url
; // if true completing a URL (i.e. 'm_prepend' is a URL), otherwise a path
228 KUrlCompletionPrivate
*m_receiver
;
229 QStringList m_matches
;
230 bool m_terminationRequested
;
234 * A simple thread that fetches a list of tilde-completions and returns this
235 * to the caller via a CompletionMatchEvent.
238 class UserListThread
: public CompletionThread
241 UserListThread( KUrlCompletionPrivate
*receiver
) :
242 CompletionThread( receiver
)
248 static const QChar tilde
= '~';
250 // we don't need to handle prepend here, right? ~user is always at pos 0
251 assert(m_prepend
.isEmpty());
253 while ( ( pw
= ::getpwent() ) && !terminationRequested() )
254 addMatch( tilde
+ QString::fromLocal8Bit( pw
->pw_name
) );
258 addMatch( QString( tilde
) );
264 class DirectoryListThread
: public CompletionThread
267 DirectoryListThread( KUrlCompletionPrivate
*receiver
,
268 const QStringList
&dirList
,
269 const QString
&filter
,
273 bool appendSlashToDir
) :
274 CompletionThread( receiver
),
275 m_dirList( dirList
),
277 m_onlyExe( onlyExe
),
278 m_onlyDir( onlyDir
),
279 m_noHidden( noHidden
),
280 m_appendSlashToDir( appendSlashToDir
)
286 QStringList m_dirList
;
291 bool m_appendSlashToDir
;
294 void DirectoryListThread::run()
296 // Thread safety notes:
298 // There very possibly may be thread safety issues here, but I've done a check
299 // of all of the things that would seem to be problematic. Here are a few
300 // things that I have checked to be safe here (some used indirectly):
302 // QDir::currentPath(), QDir::setCurrent(), QFile::decodeName(), QFile::encodeName()
303 // QString::fromLocal8Bit(), QString::toLocal8Bit(), QTextCodec::codecForLocale()
305 // Also see (for POSIX functions):
306 // http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html
308 // kDebug() << "Entered DirectoryListThread::run(), m_filter=" << m_filter << ", m_onlyExe=" << m_onlyExe << ", m_onlyDir=" << m_onlyDir << ", m_appendSlashToDir=" << m_appendSlashToDir << ", m_dirList.size()=" << m_dirList.size();
310 QStringList::ConstIterator end
= m_dirList
.constEnd();
311 for ( QStringList::ConstIterator it
= m_dirList
.constBegin();
312 it
!= end
&& !terminationRequested();
315 // kDebug() << "Scanning directory" << *it;
317 // A trick from KIO that helps performance by a little bit:
318 // chdir to the directory so we won't have to deal with full paths
321 QString path
= QDir::currentPath();
322 QDir::setCurrent( *it
);
324 QDir::Filters iterator_filter
= (m_noHidden
? QDir::Filter(0) : QDir::Hidden
) | QDir::Readable
| QDir::NoDotAndDotDot
;
327 iterator_filter
|= (QDir::Dirs
| QDir::Files
| QDir::Executable
);
328 else if ( m_onlyDir
)
329 iterator_filter
|= QDir::Dirs
;
331 iterator_filter
|= (QDir::Dirs
| QDir::Files
);
333 QDirIterator
current_dir_iterator( *it
, iterator_filter
);
335 while (current_dir_iterator
.hasNext()) {
336 current_dir_iterator
.next();
338 QFileInfo file_info
= current_dir_iterator
.fileInfo();
339 const QString file_name
= file_info
.fileName();
341 //kDebug() << "Found" << file_name;
343 if ( m_filter
.isEmpty() || file_name
.startsWith( m_filter
) ) {
345 QString toAppend
= m_complete_url
? QUrl::toPercentEncoding(file_name
) : file_name
;
346 // Add '/' to directories
347 if ( m_appendSlashToDir
&& file_info
.isDir() )
348 toAppend
.append( QLatin1Char( '/' ) );
350 addMatch( m_prepend
+ toAppend
);
354 // chdir to the original directory
355 QDir::setCurrent( path
);
361 KUrlCompletionPrivate::~KUrlCompletionPrivate()
363 if ( userListThread
)
364 userListThread
->requestTermination();
366 dirListThread
->requestTermination();
369 ///////////////////////////////////////////////////////
370 ///////////////////////////////////////////////////////
371 // MyURL - wrapper for KUrl with some different functionality
374 class KUrlCompletionPrivate::MyURL
377 MyURL(const QString
&url
, const QString
&cwd
);
378 MyURL(const MyURL
&url
);
381 KUrl
*kurl() const { return m_kurl
; }
383 QString
protocol() const { return m_kurl
->protocol(); }
384 // The directory with a trailing '/'
385 QString
dir() const { return m_kurl
->directory(KUrl::AppendTrailingSlash
|KUrl::ObeyTrailingSlash
); }
386 QString
file() const { return m_kurl
->fileName(KUrl::ObeyTrailingSlash
); }
388 // The initial, unparsed, url, as a string.
389 QString
url() const { return m_url
; }
391 // Is the initial string a URL, or just a path (whether absolute or relative)
392 bool isURL() const { return m_isURL
; }
394 void filter( bool replace_user_dir
, bool replace_env
);
397 void init(const QString
&url
, const QString
&cwd
);
404 KUrlCompletionPrivate::MyURL::MyURL(const QString
&_url
, const QString
&cwd
)
409 KUrlCompletionPrivate::MyURL::MyURL(const MyURL
&_url
)
411 m_kurl
= new KUrl( *(_url
.m_kurl
) );
413 m_isURL
= _url
.m_isURL
;
416 void KUrlCompletionPrivate::MyURL::init(const QString
&_url
, const QString
&cwd
)
418 // Save the original text
422 QString url_copy
= _url
;
424 // Special shortcuts for "man:" and "info:"
425 if ( url_copy
.startsWith( QLatin1Char('#') ) ) {
426 if ( url_copy
.length() > 1 && url_copy
.at(1) == QLatin1Char('#') )
427 url_copy
.replace( 0, 2, QLatin1String("info:") );
429 url_copy
.replace( 0, 1, QLatin1String("man:") );
432 // Look for a protocol in 'url'
433 QRegExp protocol_regex
= QRegExp( "^(?![A-Za-z]:)[^/\\s\\\\]*:" );
435 // Assume "file:" or whatever is given by 'cwd' if there is
436 // no protocol. (KUrl does this only for absolute paths)
437 if ( protocol_regex
.indexIn( url_copy
) == 0 )
439 m_kurl
= new KUrl( url_copy
);
442 else // relative path or ~ or $something
445 if ( !QDir::isRelativePath(url_copy
) ||
446 url_copy
.startsWith( QLatin1Char('~') ) ||
447 url_copy
.startsWith( QLatin1Char('$') ))
450 m_kurl
->setPath( url_copy
);
454 if ( cwd
.isEmpty() ) {
455 m_kurl
= new KUrl( url_copy
);
457 m_kurl
= new KUrl( cwd
);
458 m_kurl
->addPath( url_copy
);
464 KUrlCompletionPrivate::MyURL::~MyURL()
469 void KUrlCompletionPrivate::MyURL::filter( bool replace_user_dir
, bool replace_env
)
471 QString d
= dir() + file();
472 if ( replace_user_dir
) expandTilde( d
);
473 if ( replace_env
) expandEnv( d
);
474 m_kurl
->setPath( d
);
477 ///////////////////////////////////////////////////////
478 ///////////////////////////////////////////////////////
482 KUrlCompletion::KUrlCompletion() : KCompletion(),d(new KUrlCompletionPrivate(this))
488 KUrlCompletion::KUrlCompletion( Mode _mode
) : KCompletion(),d(new KUrlCompletionPrivate(this))
494 KUrlCompletion::~KUrlCompletion()
501 void KUrlCompletionPrivate::init()
503 cwd
= QDir::homePath();
507 last_no_hidden
= false;
510 mode
= KUrlCompletion::FileCompletion
;
513 KConfigGroup
cg( KGlobal::config(), "URLCompletion" );
515 url_auto_completion
= cg
.readEntry("alwaysAutoComplete", true);
516 popup_append_slash
= cg
.readEntry("popupAppendSlash", true);
517 onlyLocalProto
= cg
.readEntry("LocalProtocolsOnly", false);
519 q
->setIgnoreCase(true);
522 void KUrlCompletion::setDir(const QString
&_dir
)
527 QString
KUrlCompletion::dir() const
532 KUrlCompletion::Mode
KUrlCompletion::mode() const
537 void KUrlCompletion::setMode( Mode _mode
)
542 bool KUrlCompletion::replaceEnv() const
544 return d
->replace_env
;
547 void KUrlCompletion::setReplaceEnv( bool replace
)
549 d
->replace_env
= replace
;
552 bool KUrlCompletion::replaceHome() const
554 return d
->replace_home
;
557 void KUrlCompletion::setReplaceHome( bool replace
)
559 d
->replace_home
= replace
;
565 * Entry point for file name completion
567 QString
KUrlCompletion::makeCompletion(const QString
&text
)
569 //kDebug() << text << "d->cwd=" << d->cwd;
571 KUrlCompletionPrivate::MyURL
url(text
, d
->cwd
);
573 d
->compl_text
= text
;
575 // Set d->prepend to the original URL, with the filename [and ref/query] stripped.
576 // This is what gets prepended to the directory-listing matches.
577 int toRemove
= url
.file().length() - url
.kurl()->query().length();
578 if ( url
.kurl()->hasRef() )
579 toRemove
+= url
.kurl()->ref().length() + 1;
580 d
->prepend
= text
.left( text
.length() - toRemove
);
581 d
->complete_url
= url
.isURL();
585 // Environment variables
587 if ( d
->replace_env
&& d
->envCompletion( url
, &aMatch
) )
592 if ( d
->replace_home
&& d
->userCompletion( url
, &aMatch
) )
595 // Replace user directories and variables
596 url
.filter( d
->replace_home
, d
->replace_env
);
598 //kDebug() << "Filtered: proto=" << url.protocol()
599 // << ", dir=" << url.dir()
600 // << ", file=" << url.file()
601 // << ", kurl url=" << *url.kurl();
603 if ( d
->mode
== ExeCompletion
) {
606 if ( d
->exeCompletion( url
, &aMatch
) )
609 // KRun can run "man:" and "info:" etc. so why not treat them
612 if ( d
->urlCompletion( url
, &aMatch
) )
616 // Local files, directories
618 if ( d
->fileCompletion( url
, &aMatch
) )
623 if ( d
->urlCompletion( url
, &aMatch
) )
627 d
->setListedUrl( CTNone
);
636 * Go on and call KCompletion.
637 * Called when all matches have been added
639 QString
KUrlCompletionPrivate::finished()
641 if ( last_compl_type
== CTInfo
)
642 return q
->KCompletion::makeCompletion( compl_text
.toLower() );
644 return q
->KCompletion::makeCompletion( compl_text
);
650 * Return true if either a KIO job or the DirLister
653 bool KUrlCompletion::isRunning() const
655 return d
->list_job
|| (d
->dirListThread
&& !d
->dirListThread
->isFinished());
661 * Stop and delete a running KIO job or the DirLister
663 void KUrlCompletion::stop()
670 while ( !d
->list_urls
.isEmpty() ) {
671 delete d
->list_urls
.takeFirst();
674 if ( d
->dirListThread
) {
675 d
->dirListThread
->requestTermination();
676 d
->dirListThread
= 0;
681 * Keep track of the last listed directory
683 void KUrlCompletionPrivate::setListedUrl( int complType
,
684 const QString
& directory
,
685 const QString
& filter
,
688 last_compl_type
= complType
;
689 last_path_listed
= directory
;
690 last_file_listed
= filter
;
691 last_no_hidden
= (int)no_hidden
;
692 last_prepend
= prepend
;
695 bool KUrlCompletionPrivate::isListedUrl( int complType
,
696 const QString
& directory
,
697 const QString
& filter
,
700 return last_compl_type
== complType
701 && ( last_path_listed
== directory
702 || (directory
.isEmpty() && last_path_listed
.isEmpty()) )
703 && ( filter
.startsWith(last_file_listed
)
704 || (filter
.isEmpty() && last_file_listed
.isEmpty()) )
705 && last_no_hidden
== (int)no_hidden
706 && last_prepend
== prepend
; // e.g. relative path vs absolute
712 * Returns true if completion mode is Auto or Popup
714 bool KUrlCompletionPrivate::isAutoCompletion()
716 return q
->completionMode() == KGlobalSettings::CompletionAuto
717 || q
->completionMode() == KGlobalSettings::CompletionPopup
718 || q
->completionMode() == KGlobalSettings::CompletionMan
719 || q
->completionMode() == KGlobalSettings::CompletionPopupAuto
;
721 //////////////////////////////////////////////////
722 //////////////////////////////////////////////////
726 bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL
&url
, QString
*pMatch
)
728 if ( url
.protocol() != QLatin1String("file")
729 || !url
.dir().isEmpty()
730 || !url
.file().startsWith( QLatin1Char('~') ) )
733 if ( !isListedUrl( CTUser
) ) {
737 if ( !userListThread
) {
738 userListThread
= new UserListThread( this );
739 userListThread
->start();
741 // If the thread finishes quickly make sure that the results
742 // are added to the first matching case.
744 userListThread
->wait( 200 );
745 const QStringList l
= userListThread
->matches();
749 *pMatch
= finished();
753 /////////////////////////////////////////////////////
754 /////////////////////////////////////////////////////
755 // Environment variables
759 extern char **environ
; // Array of environment variables
762 bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL
&url
, QString
*pMatch
)
764 if ( url
.file().isEmpty() || url
.file().at(0) != QLatin1Char('$') )
767 if ( !isListedUrl( CTEnv
) ) {
771 char **env
= environ
;
773 QString dollar
= QLatin1String("$");
778 QString s
= QString::fromLocal8Bit( *env
);
780 int pos
= s
.indexOf(QLatin1Char('='));
786 l
.append( prepend
+ dollar
+ s
.left(pos
) );
794 setListedUrl( CTEnv
);
796 *pMatch
= finished();
800 //////////////////////////////////////////////////
801 //////////////////////////////////////////////////
805 bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL
&url
, QString
*pMatch
)
807 if ( url
.protocol() != QLatin1String("file") )
810 QString directory
= unescape( url
.dir() ); // remove escapes
812 // Find directories to search for completions, either
814 // 1. complete path given in url
815 // 2. current directory (d->cwd)
817 // 4. no directory at all
821 if ( !QDir::isRelativePath(directory
) ) {
822 // complete path in url
823 dirList
.append( directory
);
825 else if ( !directory
.isEmpty() && !cwd
.isEmpty() ) {
827 dirList
.append( cwd
+ QLatin1Char('/') + directory
);
829 else if ( !url
.file().isEmpty() ) {
831 dirList
= QString::fromLocal8Bit(qgetenv("PATH")).split(
832 KPATH_SEPARATOR
,QString::SkipEmptyParts
);
834 QStringList::Iterator it
= dirList
.begin();
836 for ( ; it
!= dirList
.end(); ++it
)
837 it
->append(QLatin1Char('/'));
840 // No hidden files unless the user types "."
841 bool no_hidden_files
= url
.file().isEmpty() || url
.file().at(0) != QLatin1Char('.');
843 // List files if needed
845 if ( !isListedUrl( CTExe
, directory
, url
.file(), no_hidden_files
) )
850 setListedUrl( CTExe
, directory
, url
.file(), no_hidden_files
);
852 *pMatch
= listDirectories( dirList
, url
.file(), true, false, no_hidden_files
);
854 else if ( !q
->isRunning() ) {
855 *pMatch
= finished();
859 setListedUrl( CTExe
, directory
, url
.file(), no_hidden_files
);
866 //////////////////////////////////////////////////
867 //////////////////////////////////////////////////
871 bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL
&url
, QString
*pMatch
)
873 if ( url
.protocol() != QLatin1String("file") )
876 QString directory
= unescape( url
.dir() );
878 if (url
.url().length() && url
.url().at(0) == QLatin1Char('.'))
880 if (url
.url().length() == 1)
882 *pMatch
= ( q
->completionMode() == KGlobalSettings::CompletionMan
)?
887 else if (url
.url().length() == 2 && url
.url().at(1) == QLatin1Char('.'))
889 *pMatch
= QLatin1String("..");
894 //kDebug() << "fileCompletion" << url << "dir=" << dir;
896 // Find directories to search for completions, either
898 // 1. complete path given in url
899 // 2. current directory (d->cwd)
900 // 3. no directory at all
904 if ( !QDir::isRelativePath(directory
) ) {
905 // complete path in url
906 dirList
.append( directory
);
908 else if ( !cwd
.isEmpty() ) {
910 QString dirToAdd
= cwd
;
911 if ( !directory
.isEmpty() ) {
912 if ( !cwd
.endsWith('/') )
913 dirToAdd
.append( QLatin1Char('/') );
914 dirToAdd
.append( directory
);
916 dirList
.append( dirToAdd
);
919 // No hidden files unless the user types "."
920 bool no_hidden_files
= !url
.file().startsWith( QLatin1Char('.') );
922 // List files if needed
924 if ( !isListedUrl( CTFile
, directory
, QString(), no_hidden_files
) )
929 setListedUrl( CTFile
, directory
, QString(), no_hidden_files
);
931 // Append '/' to directories in Popup mode?
932 bool append_slash
= ( popup_append_slash
933 && (q
->completionMode() == KGlobalSettings::CompletionPopup
||
934 q
->completionMode() == KGlobalSettings::CompletionPopupAuto
) );
936 bool only_dir
= ( mode
== KUrlCompletion::DirCompletion
);
938 *pMatch
= listDirectories( dirList
, QString(), false, only_dir
, no_hidden_files
,
941 else if ( !q
->isRunning() ) {
942 *pMatch
= finished();
951 //////////////////////////////////////////////////
952 //////////////////////////////////////////////////
953 // URLs not handled elsewhere...
956 bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL
&url
, QString
*pMatch
)
958 //kDebug() << *url.kurl();
959 if (onlyLocalProto
&& KProtocolInfo::protocolClass(url
.protocol()) != QLatin1String(":local"))
962 // Use d->cwd as base url in case url is not absolute
965 // Create an URL with the directory to be listed
966 KUrl
url_dir( url_cwd
, url
.kurl()->url() );
968 // Don't try url completion if
970 // 2. protocol that doesn't have listDir()
971 // 3. there is no directory (e.g. "ftp://ftp.kd" shouldn't do anything)
972 // 4. auto or popup completion mode depending on settings
974 bool man_or_info
= ( url_dir
.protocol() == QLatin1String("man")
975 || url_dir
.protocol() == QLatin1String("info") );
977 if ( !url_dir
.isValid()
978 || !KProtocolManager::supportsListing( url_dir
)
980 && ( url_dir
.directory(KUrl::AppendTrailingSlash
|KUrl::ObeyTrailingSlash
).isEmpty()
981 || ( isAutoCompletion()
982 && !url_auto_completion
) ) ) ) {
986 url_dir
.setFileName(QString()); // not really nesseccary, but clear the filename anyway...
989 QString directory
= unescape( url_dir
.directory(KUrl::AppendTrailingSlash
|KUrl::ObeyTrailingSlash
) );
991 url_dir
.setPath( directory
);
993 // List files if needed
995 if ( !isListedUrl( CTUrl
, url_dir
.prettyUrl(), url
.file() ) )
1000 setListedUrl( CTUrl
, url_dir
.prettyUrl(), QString() );
1002 QList
<KUrl
*> url_list
;
1003 url_list
.append( new KUrl( url_dir
) );
1005 listUrls( url_list
, QString(), false );
1009 else if ( !q
->isRunning() ) {
1010 *pMatch
= finished();
1019 //////////////////////////////////////////////////
1020 //////////////////////////////////////////////////
1021 // Directory and URL listing
1027 * Called to add matches to KCompletion
1029 void KUrlCompletionPrivate::addMatches( const QStringList
&matchList
)
1031 q
->insertItems(matchList
);
1037 * List files starting with 'filter' in the given directories,
1038 * either using DirLister or listURLs()
1040 * In either case, addMatches() is called with the listed
1041 * files, and eventually finished() when the listing is done
1043 * Returns the match if available, or QString() if
1044 * DirLister timed out or using kio
1046 QString
KUrlCompletionPrivate::listDirectories(
1047 const QStringList
&dirList
,
1048 const QString
&filter
,
1052 bool append_slash_to_dir
)
1054 assert( !q
->isRunning() );
1056 if ( qgetenv("KURLCOMPLETION_LOCAL_KIO").isEmpty() ) {
1058 //kDebug() << "Listing (listDirectories):" << dirList << "filter=" << filter << "without KIO";
1062 if ( dirListThread
)
1063 dirListThread
->requestTermination();
1067 QStringList::ConstIterator end
= dirList
.constEnd();
1068 for ( QStringList::ConstIterator it
= dirList
.constBegin();
1074 if ( KAuthorized::authorizeUrlAction( QLatin1String("list"), KUrl(), url
) )
1078 dirListThread
= new DirectoryListThread( this, dirs
, filter
, only_exe
, only_dir
,
1079 no_hidden
, append_slash_to_dir
);
1080 dirListThread
->start();
1081 dirListThread
->wait( 200 );
1082 addMatches( dirListThread
->matches() );
1088 //kDebug() << "Listing (listDirectories):" << dirList << "with KIO";
1090 QList
<KUrl
*> url_list
;
1092 QStringList::ConstIterator it
= dirList
.constBegin();
1093 QStringList::ConstIterator end
= dirList
.constEnd();
1095 for ( ; it
!= end
; ++it
) {
1096 url_list
.append( new KUrl( *it
) );
1099 listUrls( url_list
, filter
, only_exe
, no_hidden
);
1100 // Will call addMatches() and finished()
1108 * Use KIO to list the given urls
1110 * addMatches() is called with the listed files
1111 * finished() is called when the listing is done
1113 void KUrlCompletionPrivate::listUrls(
1114 const QList
<KUrl
*> &urls
,
1115 const QString
&filter
,
1119 assert( list_urls
.isEmpty() );
1120 assert( list_job
== 0L );
1123 list_urls_filter
= filter
;
1124 list_urls_only_exe
= only_exe
;
1125 list_urls_no_hidden
= no_hidden
;
1127 //kDebug() << "Listing URLs:" << *urls[0] << ",...";
1129 // Start it off by calling _k_slotIOFinished
1131 // This will start a new list job as long as there
1132 // are urls in d->list_urls
1134 _k_slotIOFinished(0);
1140 * Receive files listed by KIO and call addMatches()
1142 void KUrlCompletionPrivate::_k_slotEntries(KIO::Job
*, const KIO::UDSEntryList
& entries
)
1144 QStringList matchList
;
1146 KIO::UDSEntryList::ConstIterator it
= entries
.constBegin();
1147 const KIO::UDSEntryList::ConstIterator end
= entries
.constEnd();
1149 QString filter
= list_urls_filter
;
1151 int filter_len
= filter
.length();
1153 // Iterate over all files
1155 for (; it
!= end
; ++it
) {
1156 const KIO::UDSEntry
& entry
= *it
;
1157 const QString url
= entry
.stringValue( KIO::UDSEntry::UDS_URL
);
1160 if (!url
.isEmpty()) {
1161 // kDebug() << "url:" << url;
1162 entry_name
= KUrl(url
).fileName();
1164 entry_name
= entry
.stringValue( KIO::UDSEntry::UDS_NAME
);
1167 // kDebug() << "name:" << name;
1169 if ( (!entry_name
.isEmpty() && entry_name
.at(0) == QLatin1Char('.')) &&
1170 ( list_urls_no_hidden
||
1171 entry_name
.length() == 1 ||
1172 ( entry_name
.length() == 2 && entry_name
.at(1) == QLatin1Char('.') ) ) )
1175 const bool isDir
= entry
.isDir();
1177 if ( mode
== KUrlCompletion::DirCompletion
&& !isDir
)
1180 if ( filter_len
== 0 || entry_name
.left(filter_len
) == filter
) {
1182 QString toAppend
= complete_url
? QUrl::toPercentEncoding(entry_name
) : entry_name
;
1185 toAppend
.append( QLatin1Char( '/' ) );
1187 if ( !list_urls_only_exe
||
1188 (entry
.numberValue( KIO::UDSEntry::UDS_ACCESS
) & MODE_EXE
) // true if executable
1190 matchList
.append( prepend
+ toAppend
);
1195 addMatches( matchList
);
1201 * Called when a KIO job is finished.
1203 * Start a new list job if there are still urls in
1204 * list_urls, otherwise call finished()
1206 void KUrlCompletionPrivate::_k_slotIOFinished( KJob
* job
)
1208 assert( job
== list_job
);
1210 if ( list_urls
.isEmpty() ) {
1214 finished(); // will call KCompletion::makeCompletion()
1219 KUrl
*kurl
= list_urls
.takeFirst();
1221 // list_urls.removeAll( kurl );
1223 // kDebug() << "Start KIO::listDir" << *kurl;
1225 list_job
= KIO::listDir( *kurl
, KIO::HideProgressInfo
);
1226 list_job
->addMetaData("no-auth-prompt", "true");
1230 q
->connect( list_job
,
1231 SIGNAL(result(KJob
*)),
1232 SLOT(_k_slotIOFinished(KJob
*)) );
1234 q
->connect( list_job
,
1235 SIGNAL( entries( KIO::Job
*, const KIO::UDSEntryList
&)),
1236 SLOT( _k_slotEntries( KIO::Job
*, const KIO::UDSEntryList
&)) );
1242 ///////////////////////////////////////////////////
1243 ///////////////////////////////////////////////////
1246 * postProcessMatch, postProcessMatches
1248 * Called by KCompletion before emitting match() and matches()
1250 * Append '/' to directories for file completion. This is
1251 * done here to avoid stat()'ing a lot of files
1253 void KUrlCompletion::postProcessMatch( QString
*pMatch
) const
1255 // kDebug() << *pMatch;
1257 if ( !pMatch
->isEmpty() ) {
1259 // Add '/' to directories in file completion mode
1260 // unless it has already been done
1261 if ( d
->last_compl_type
== CTFile
1262 && pMatch
->at( pMatch
->length()-1 ) != QLatin1Char('/') )
1266 if ( pMatch
->startsWith( QLatin1String("file:") ) )
1267 copy
= KUrl(*pMatch
).toLocalFile();
1271 expandTilde( copy
);
1274 DWORD dwAttr
= GetFileAttributesW( (LPCWSTR
) copy
.utf16() );
1275 if ( dwAttr
== INVALID_FILE_ATTRIBUTES
) {
1276 kDebug() << "Could not get file attribs ( "
1281 if ( ( dwAttr
& FILE_ATTRIBUTE_DIRECTORY
) == FILE_ATTRIBUTE_DIRECTORY
)
1282 pMatch
->append( QLatin1Char( '/' ) );
1284 if ( QDir::isRelativePath(copy
) )
1285 copy
.prepend( d
->cwd
+ QLatin1Char('/') );
1287 // kDebug() << "stat'ing" << copy;
1289 KDE_struct_stat sbuff
;
1291 QByteArray file
= QFile::encodeName( copy
);
1293 if ( KDE_stat( file
.data(), &sbuff
) == 0 ) {
1294 if ( S_ISDIR ( sbuff
.st_mode
) )
1295 pMatch
->append( QLatin1Char( '/' ) );
1298 kDebug() << "Could not stat file" << copy
;
1305 void KUrlCompletion::postProcessMatches( QStringList
* /*matches*/ ) const
1307 // Maybe '/' should be added to directories here as in
1308 // postProcessMatch() but it would slow things down
1309 // when there are a lot of matches...
1312 void KUrlCompletion::postProcessMatches( KCompletionMatches
* /*matches*/ ) const
1314 // Maybe '/' should be added to directories here as in
1315 // postProcessMatch() but it would slow things down
1316 // when there are a lot of matches...
1319 void KUrlCompletion::customEvent(QEvent
*e
)
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() );
1333 d
->setListedUrl( CTUser
);
1335 if ( d
->userListThread
== matchEvent
->completionThread() )
1336 d
->userListThread
= 0;
1338 if ( d
->dirListThread
== matchEvent
->completionThread() )
1339 d
->dirListThread
= 0;
1341 delete matchEvent
->completionThread();
1346 QString
KUrlCompletion::replacedPath( const QString
& text
, bool replaceHome
, bool replaceEnv
)
1348 if ( text
.isEmpty() )
1351 KUrlCompletionPrivate::MyURL
url( text
, QString() ); // no need to replace something of our current cwd
1352 if ( !url
.kurl()->isLocalFile() )
1355 url
.filter( replaceHome
, replaceEnv
);
1356 return url
.dir() + url
.file();
1360 QString
KUrlCompletion::replacedPath( const QString
& text
) const
1362 return replacedPath( text
, d
->replace_home
, d
->replace_env
);
1365 /////////////////////////////////////////////////////////
1366 /////////////////////////////////////////////////////////
1372 * Expand environment variables in text. Escaped '$' are ignored.
1373 * Return true if expansion was made.
1375 static bool expandEnv( QString
&text
)
1377 // Find all environment variables beginning with '$'
1381 bool expanded
= false;
1383 while ( (pos
= text
.indexOf(QLatin1Char('$'), pos
)) != -1 ) {
1387 if ( pos
> 0 && text
.at(pos
-1) == QLatin1Char('\\') ) {
1390 // Variable found => expand
1393 // Find the end of the variable = next '/' or ' '
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();
1404 // Replace if the variable is terminated by '/' or ' '
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();
1431 * Replace "~user" with the users home directory
1432 * Return true if expansion was made.
1434 static bool expandTilde(QString
&text
)
1436 if ( text
.isEmpty() || ( text
.at(0) != QLatin1Char('~') ))
1439 bool expanded
= false;
1441 // Find the end of the user name = next '/' or ' '
1443 int pos2
= text
.indexOf( QLatin1Char(' '), 1 );
1444 int pos_tmp
= text
.indexOf( QLatin1Char('/'), 1 );
1446 if ( pos2
== -1 || (pos_tmp
!= -1 && pos_tmp
< pos2
) )
1450 pos2
= text
.length();
1452 // Replace ~user if the user name is terminated by '/' or ' '
1456 QString user
= text
.mid( 1, pos2
-1 );
1459 // A single ~ is replaced with $HOME
1461 if ( user
.isEmpty() ) {
1462 dir
= QDir::homePath();
1464 // ~user is replaced with the dir from passwd
1467 struct passwd
*pw
= ::getpwnam( user
.toLocal8Bit() );
1470 dir
= QFile::decodeName( pw
->pw_dir
);
1475 if ( !dir
.isEmpty() ) {
1477 text
.replace(0, pos2
, dir
);
1487 * Remove escapes and return the result in a new string
1490 static QString
unescape(const QString
&text
)
1494 for (int pos
= 0; pos
< text
.length(); pos
++)
1495 if ( text
.at(pos
) != QLatin1Char('\\') )
1496 result
.insert( result
.length(), text
.at(pos
) );
1501 #include "kurlcompletion.moc"