00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 #include "kurlcompletion.h"
00027
00028 #include <config.h>
00029
00030 #include <stdlib.h>
00031 #include <assert.h>
00032 #include <limits.h>
00033
00034 #include <QtCore/QMutableStringListIterator>
00035 #include <QtCore/QRegExp>
00036 #include <QtCore/QTimer>
00037 #include <QtCore/QDir>
00038 #include <QtCore/QFile>
00039 #include <QtCore/QTextIStream>
00040 #include <QtCore/QThread>
00041 #include <QtGui/QActionEvent>
00042
00043 #include <kapplication.h>
00044 #include <kauthorized.h>
00045 #include <kdebug.h>
00046 #include <kurl.h>
00047 #include <kio/job.h>
00048 #include <kprotocolmanager.h>
00049 #include <kconfig.h>
00050 #include <kglobal.h>
00051 #include <kde_file.h>
00052
00053 #include <sys/types.h>
00054 #include <dirent.h>
00055 #include <unistd.h>
00056 #include <sys/stat.h>
00057 #include <pwd.h>
00058 #include <time.h>
00059 #include <sys/param.h>
00060 #include <kconfiggroup.h>
00061
00062 #ifdef Q_WS_WIN
00063 #include <kkernel_win.h>
00064 #endif
00065
00066 static bool expandTilde(QString &);
00067 static bool expandEnv(QString &);
00068
00069 static QString unescape(const QString &text);
00070
00071
00072
00073 #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH)
00074
00075
00076 enum ComplType {CTNone=0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo};
00077
00078 class CompletionThread;
00079
00082
00083
00084 class KUrlCompletionPrivate
00085 {
00086 public:
00087 KUrlCompletionPrivate(KUrlCompletion *parent)
00088 : q(parent),
00089 url_auto_completion(true),
00090 userListThread(0),
00091 dirListThread(0)
00092 {
00093 }
00094
00095 ~KUrlCompletionPrivate();
00096
00097 void _k_slotEntries( KIO::Job*, const KIO::UDSEntryList& );
00098 void _k_slotIOFinished( KJob* );
00099
00100 class MyURL;
00101 bool userCompletion(const MyURL &url, QString *match);
00102 bool envCompletion(const MyURL &url, QString *match);
00103 bool exeCompletion(const MyURL &url, QString *match);
00104 bool fileCompletion(const MyURL &url, QString *match);
00105 bool urlCompletion(const MyURL &url, QString *match);
00106
00107 bool isAutoCompletion();
00108
00109
00110 QString listDirectories(const QStringList &,
00111 const QString &,
00112 bool only_exe = false,
00113 bool only_dir = false,
00114 bool no_hidden = false,
00115 bool stat_files = true);
00116
00117 void listUrls( const QList<KUrl *> &urls,
00118 const QString &filter = QString(),
00119 bool only_exe = false,
00120 bool no_hidden = false );
00121
00122 void addMatches( const QStringList & );
00123 QString finished();
00124
00125 void init();
00126
00127 void setListedUrl(int compl_type ,
00128 const QString& dir = QString(),
00129 const QString& filter = QString(),
00130 bool no_hidden = false );
00131
00132 bool isListedUrl( int compl_type ,
00133 const QString& dir = QString(),
00134 const QString& filter = QString(),
00135 bool no_hidden = false );
00136
00137 KUrlCompletion *q;
00138 QList<KUrl*> list_urls;
00139
00140 bool onlyLocalProto;
00141
00142
00143 bool url_auto_completion;
00144
00145
00146
00147 bool popup_append_slash;
00148
00149
00150 QString last_path_listed;
00151 QString last_file_listed;
00152 QString last_prepend;
00153 int last_compl_type;
00154 int last_no_hidden;
00155
00156 QString cwd;
00157
00158 KUrlCompletion::Mode mode;
00159 bool replace_env;
00160 bool replace_home;
00161 bool complete_url;
00162
00163 KIO::ListJob *list_job;
00164
00165 QString prepend;
00166 QString compl_text;
00167
00168
00169 bool list_urls_only_exe;
00170 bool list_urls_no_hidden;
00171 QString list_urls_filter;
00172
00173 CompletionThread *userListThread;
00174 CompletionThread *dirListThread;
00175 };
00176
00182 class CompletionMatchEvent : public QEvent
00183 {
00184 public:
00185 CompletionMatchEvent( CompletionThread *thread ) :
00186 QEvent( uniqueType() ),
00187 m_completionThread( thread )
00188 {}
00189
00190 CompletionThread *completionThread() const { return m_completionThread; }
00191 static Type uniqueType() { return Type(User + 61080); }
00192
00193 private:
00194 CompletionThread *m_completionThread;
00195 };
00196
00197 class CompletionThread : public QThread
00198 {
00199 protected:
00200 CompletionThread( KUrlCompletionPrivate *receiver ) :
00201 QThread(),
00202 m_prepend( receiver->prepend ),
00203 m_complete_url( receiver->complete_url ),
00204 m_receiver( receiver ),
00205 m_terminationRequested( false )
00206 {}
00207
00208 public:
00209 void requestTermination() { m_terminationRequested = true; }
00210 QStringList matches() const { return m_matches; }
00211
00212 protected:
00213 void addMatch( const QString &match ) { m_matches.append( match ); }
00214 bool terminationRequested() const { return m_terminationRequested; }
00215 void done()
00216 {
00217 if ( !m_terminationRequested )
00218 qApp->postEvent( m_receiver->q, new CompletionMatchEvent( this ) );
00219 else
00220 deleteLater();
00221 }
00222
00223 const QString m_prepend;
00224 const bool m_complete_url;
00225
00226 private:
00227 KUrlCompletionPrivate *m_receiver;
00228 QStringList m_matches;
00229 bool m_terminationRequested;
00230 };
00231
00237 class UserListThread : public CompletionThread
00238 {
00239 public:
00240 UserListThread( KUrlCompletionPrivate *receiver ) :
00241 CompletionThread( receiver )
00242 {}
00243
00244 protected:
00245 virtual void run()
00246 {
00247 static const QChar tilde = '~';
00248
00249
00250 assert(m_prepend.isEmpty());
00251 struct passwd *pw;
00252 while ( ( pw = ::getpwent() ) && !terminationRequested() )
00253 addMatch( tilde + QString::fromLocal8Bit( pw->pw_name ) );
00254
00255 ::endpwent();
00256
00257 addMatch( QString( tilde ) );
00258
00259 done();
00260 }
00261 };
00262
00263 class DirectoryListThread : public CompletionThread
00264 {
00265 public:
00266 DirectoryListThread( KUrlCompletionPrivate *receiver,
00267 const QStringList &dirList,
00268 const QString &filter,
00269 bool onlyExe,
00270 bool onlyDir,
00271 bool noHidden,
00272 bool appendSlashToDir ) :
00273 CompletionThread( receiver ),
00274 m_dirList( dirList ),
00275 m_filter( filter ),
00276 m_onlyExe( onlyExe ),
00277 m_onlyDir( onlyDir ),
00278 m_noHidden( noHidden ),
00279 m_appendSlashToDir( appendSlashToDir )
00280 {}
00281
00282 virtual void run();
00283
00284 private:
00285 QStringList m_dirList;
00286 QString m_filter;
00287 bool m_onlyExe;
00288 bool m_onlyDir;
00289 bool m_noHidden;
00290 bool m_appendSlashToDir;
00291 };
00292
00293 #ifdef Q_WS_WIN
00294
00295 void DirectoryListThread::run()
00296 {
00297 WIN32_FIND_DATAW find_data;
00298
00299 QString prepend_slash;
00300
00301
00302
00303 if( !m_prepend.isEmpty() ) {
00304 prepend_slash = m_prepend;
00305 if( !prepend_slash.endsWith( QLatin1Char( '/' ) ) )
00306 prepend_slash += QLatin1Char( '/' );
00307 }
00308
00309
00310
00311 Q_FOREACH( const QString &dir_, m_dirList )
00312 {
00313 QString dir = dir_;
00314 if( !dir.endsWith( '/' ) )
00315 dir += QLatin1Char( '/' );
00316 dir += QLatin1String( "*.*" );
00317 HANDLE hFind = FindFirstFileW( ( LPCWSTR ) dir.utf16(), &find_data );
00318 if( hFind == INVALID_HANDLE_VALUE ) {
00319 qDebug() << "Failed to open dir:" << dir;
00320 return;
00321 }
00322 do {
00323
00324 if( ( find_data.cFileName[0] == '.' && m_noHidden ) ||
00325 ( find_data.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN ) == FILE_ATTRIBUTE_HIDDEN )
00326 continue;
00327
00328
00329
00330 if ( find_data.cFileName[0] == '.' && find_data.cFileName[1] == '\0' )
00331 continue;
00332
00333
00334
00335 if ( find_data.cFileName[0] == '.' && find_data.cFileName[1] == '.' && find_data.cFileName[2] == '\0' )
00336 continue;
00337
00338
00339
00340 if ( m_onlyDir && ( find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) == 0 )
00341 continue;
00342
00343 QString file = QString::fromUtf16( ( const ushort * ) find_data.cFileName );
00344
00345 bool appendSlash = false;
00346
00347 if ( m_filter.isEmpty() || file.startsWith( m_filter ) ) {
00348
00349 if ( m_onlyExe || m_onlyDir || m_appendSlashToDir ) {
00350
00351
00352
00353 if ( m_onlyExe && !isExecutable( file ) )
00354 continue;
00355
00356
00357
00358 if ( m_appendSlashToDir && ( find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) == FILE_ATTRIBUTE_DIRECTORY )
00359 appendSlash = true;
00360 }
00361
00362 if( appendSlash ) {
00363 addMatch( prepend_slash + file + QLatin1Char( '/' ) );
00364
00365 } else {
00366 addMatch( prepend_slash + file );
00367
00368 }
00369
00370 }
00371
00372 }
00373
00374 while( FindNextFileW( hFind, &find_data ) );
00375
00376 }
00377
00378 done();
00379 }
00380
00381 #else // Q_WS_WIN
00382
00383 void DirectoryListThread::run()
00384 {
00385
00386
00387
00388
00389
00390
00391
00392
00393
00394
00395
00396
00397 DIR *dir = 0;
00398
00399 for ( QStringList::ConstIterator it = m_dirList.constBegin();
00400 it != m_dirList.constEnd() && !terminationRequested();
00401 ++it )
00402 {
00403
00404
00405 if ( !dir ) {
00406 dir = ::opendir( QFile::encodeName( *it ) );
00407 if ( ! dir ) {
00408 kDebug() << "Failed to open dir:" << *it;
00409 return;
00410 }
00411 }
00412
00413
00414
00415
00416
00417 QString path = QDir::currentPath();
00418 QDir::setCurrent( *it );
00419
00420
00421
00422
00423
00424 #ifndef HAVE_READDIR_R
00425 KDE_struct_dirent *dirEntry = 0;
00426 while ( !terminationRequested() &&
00427 (dirEntry = KDE_readdir( dir)))
00428 #else
00429 struct dirent *dirPosition = (struct dirent *) malloc( sizeof( struct dirent ) + MAXPATHLEN + 1 );
00430 struct dirent *dirEntry = 0;
00431 while ( !terminationRequested() &&
00432 ::readdir_r( dir, dirPosition, &dirEntry ) == 0 && dirEntry )
00433 #endif
00434
00435 {
00436
00437
00438 if ( dirEntry->d_name[0] == '.' && m_noHidden )
00439 continue;
00440
00441
00442
00443 if ( dirEntry->d_name[0] == '.' && dirEntry->d_name[1] == '\0' )
00444 continue;
00445
00446
00447
00448 if ( dirEntry->d_name[0] == '.' && dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0' )
00449 continue;
00450
00451 QString file = QFile::decodeName( dirEntry->d_name );
00452
00453 if ( m_filter.isEmpty() || file.startsWith( m_filter ) ) {
00454
00455 QString toAppend = m_complete_url ? QUrl::toPercentEncoding(file) : file;
00456
00457 if ( m_onlyExe || m_onlyDir || m_appendSlashToDir ) {
00458 KDE_struct_stat sbuff;
00459
00460 if ( KDE_stat( dirEntry->d_name, &sbuff ) == 0 ) {
00461
00462
00463
00464 if ( m_onlyExe && ( sbuff.st_mode & MODE_EXE ) == 0 )
00465 continue;
00466
00467
00468
00469 if ( m_onlyDir && !S_ISDIR( sbuff.st_mode ) )
00470 continue;
00471
00472
00473
00474 if ( m_appendSlashToDir && S_ISDIR( sbuff.st_mode ) )
00475 toAppend.append( QLatin1Char( '/' ) );
00476
00477 }
00478 else {
00479 kDebug() << "Could not stat file" << file;
00480 continue;
00481 }
00482 }
00483
00484 addMatch( m_prepend + toAppend );
00485 }
00486 }
00487
00488
00489
00490 QDir::setCurrent( path );
00491
00492 ::closedir( dir );
00493 dir = 0;
00494 #ifdef HAVE_READDIR_R
00495 free( dirPosition );
00496 #endif
00497 }
00498
00499 done();
00500 }
00501 #endif // !Q_WS_WIN
00502
00503 KUrlCompletionPrivate::~KUrlCompletionPrivate()
00504 {
00505 if ( userListThread )
00506 userListThread->requestTermination();
00507 if ( dirListThread )
00508 dirListThread->requestTermination();
00509 }
00510
00513
00514
00515
00516 class KUrlCompletionPrivate::MyURL
00517 {
00518 public:
00519 MyURL(const QString &url, const QString &cwd);
00520 MyURL(const MyURL &url);
00521 ~MyURL();
00522
00523 KUrl *kurl() const { return m_kurl; }
00524
00525 QString protocol() const { return m_kurl->protocol(); }
00526
00527 QString dir() const { return m_kurl->directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash); }
00528 QString file() const { return m_kurl->fileName(KUrl::ObeyTrailingSlash); }
00529
00530
00531 QString url() const { return m_url; }
00532
00533
00534 bool isURL() const { return m_isURL; }
00535
00536 void filter( bool replace_user_dir, bool replace_env );
00537
00538 private:
00539 void init(const QString &url, const QString &cwd);
00540
00541 KUrl *m_kurl;
00542 QString m_url;
00543 bool m_isURL;
00544 };
00545
00546 KUrlCompletionPrivate::MyURL::MyURL(const QString &_url, const QString &cwd)
00547 {
00548 init(_url, cwd);
00549 }
00550
00551 KUrlCompletionPrivate::MyURL::MyURL(const MyURL &_url)
00552 {
00553 m_kurl = new KUrl( *(_url.m_kurl) );
00554 m_url = _url.m_url;
00555 m_isURL = _url.m_isURL;
00556 }
00557
00558 void KUrlCompletionPrivate::MyURL::init(const QString &_url, const QString &cwd)
00559 {
00560
00561 m_url = _url;
00562
00563
00564 QString url_copy = _url;
00565
00566
00567 if ( url_copy.startsWith( QLatin1Char('#') ) ) {
00568 if ( url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#') )
00569 url_copy.replace( 0, 2, QLatin1String("info:") );
00570 else
00571 url_copy.replace( 0, 1, QLatin1String("man:") );
00572 }
00573
00574
00575 QRegExp protocol_regex = QRegExp( "^(?![A-Za-z]:)[^/\\s\\\\]*:" );
00576
00577
00578
00579 if ( protocol_regex.indexIn( url_copy ) == 0 )
00580 {
00581 m_kurl = new KUrl( url_copy );
00582 m_isURL = true;
00583 }
00584 else
00585 {
00586 m_isURL = false;
00587 if ( !QDir::isRelativePath(url_copy) ||
00588 url_copy.startsWith( QLatin1Char('~') ) ||
00589 url_copy.startsWith( QLatin1Char('$') ))
00590 {
00591 m_kurl = new KUrl;
00592 m_kurl->setPath( url_copy );
00593 }
00594 else
00595 {
00596 if ( cwd.isEmpty() ) {
00597 m_kurl = new KUrl( url_copy );
00598 } else {
00599 m_kurl = new KUrl( cwd );
00600 m_kurl->addPath( url_copy );
00601 }
00602 }
00603 }
00604 }
00605
00606 KUrlCompletionPrivate::MyURL::~MyURL()
00607 {
00608 delete m_kurl;
00609 }
00610
00611 void KUrlCompletionPrivate::MyURL::filter( bool replace_user_dir, bool replace_env )
00612 {
00613 QString d = dir() + file();
00614 if ( replace_user_dir ) expandTilde( d );
00615 if ( replace_env ) expandEnv( d );
00616 m_kurl->setPath( d );
00617 }
00618
00621
00622
00623
00624 KUrlCompletion::KUrlCompletion() : KCompletion(),d(new KUrlCompletionPrivate(this))
00625 {
00626 d->init();
00627 }
00628
00629
00630 KUrlCompletion::KUrlCompletion( Mode _mode ) : KCompletion(),d(new KUrlCompletionPrivate(this))
00631 {
00632 d->init();
00633 setMode ( _mode );
00634 }
00635
00636 KUrlCompletion::~KUrlCompletion()
00637 {
00638 stop();
00639 delete d;
00640 }
00641
00642
00643 void KUrlCompletionPrivate::init()
00644 {
00645
00646 cwd = QDir::homePath();
00647
00648 replace_home = true;
00649 replace_env = true;
00650 last_no_hidden = false;
00651 last_compl_type = 0;
00652 list_job = 0L;
00653 mode = KUrlCompletion::FileCompletion;
00654
00655
00656 KConfigGroup cg( KGlobal::config(), "URLCompletion" );
00657
00658 url_auto_completion = cg.readEntry("alwaysAutoComplete", true);
00659 popup_append_slash = cg.readEntry("popupAppendSlash", true);
00660 onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false);
00661 }
00662
00663 void KUrlCompletion::setDir(const QString &_dir)
00664 {
00665 d->cwd = _dir;
00666 }
00667
00668 QString KUrlCompletion::dir() const
00669 {
00670 return d->cwd;
00671 }
00672
00673 KUrlCompletion::Mode KUrlCompletion::mode() const
00674 {
00675 return d->mode;
00676 }
00677
00678 void KUrlCompletion::setMode( Mode _mode )
00679 {
00680 d->mode = _mode;
00681 }
00682
00683 bool KUrlCompletion::replaceEnv() const
00684 {
00685 return d->replace_env;
00686 }
00687
00688 void KUrlCompletion::setReplaceEnv( bool replace )
00689 {
00690 d->replace_env = replace;
00691 }
00692
00693 bool KUrlCompletion::replaceHome() const
00694 {
00695 return d->replace_home;
00696 }
00697
00698 void KUrlCompletion::setReplaceHome( bool replace )
00699 {
00700 d->replace_home = replace;
00701 }
00702
00703
00704
00705
00706
00707
00708 QString KUrlCompletion::makeCompletion(const QString &text)
00709 {
00710
00711
00712 KUrlCompletionPrivate::MyURL url(text, d->cwd);
00713
00714 d->compl_text = text;
00715
00716
00717
00718 int toRemove = url.file().length() - url.kurl()->query().length();
00719 if ( url.kurl()->hasRef() )
00720 toRemove += url.kurl()->ref().length() + 1;
00721 d->prepend = text.left( text.length() - toRemove );
00722 d->complete_url = url.isURL();
00723
00724 QString aMatch;
00725
00726
00727
00728 if ( d->replace_env && d->envCompletion( url, &aMatch ) )
00729 return aMatch;
00730
00731
00732
00733 if ( d->replace_home && d->userCompletion( url, &aMatch ) )
00734 return aMatch;
00735
00736
00737 url.filter( d->replace_home, d->replace_env );
00738
00739
00740
00741
00742
00743
00744 if ( d->mode == ExeCompletion ) {
00745
00746
00747 if ( d->exeCompletion( url, &aMatch ) )
00748 return aMatch;
00749
00750
00751
00752
00753 if ( d->urlCompletion( url, &aMatch ) )
00754 return aMatch;
00755 }
00756 else {
00757
00758
00759 if ( d->fileCompletion( url, &aMatch ) )
00760 return aMatch;
00761
00762
00763
00764 if ( d->urlCompletion( url, &aMatch ) )
00765 return aMatch;
00766 }
00767
00768 d->setListedUrl( CTNone );
00769 stop();
00770
00771 return QString();
00772 }
00773
00774
00775
00776
00777
00778
00779
00780 QString KUrlCompletionPrivate::finished()
00781 {
00782 if ( last_compl_type == CTInfo )
00783 return q->KCompletion::makeCompletion( compl_text.toLower() );
00784 else
00785 return q->KCompletion::makeCompletion( compl_text );
00786 }
00787
00788
00789
00790
00791
00792
00793
00794 bool KUrlCompletion::isRunning() const
00795 {
00796 return d->list_job || (d->dirListThread && !d->dirListThread->isFinished());
00797 }
00798
00799
00800
00801
00802
00803
00804 void KUrlCompletion::stop()
00805 {
00806 if ( d->list_job ) {
00807 d->list_job->kill();
00808 d->list_job = 0L;
00809 }
00810
00811 while ( !d->list_urls.isEmpty() ) {
00812 delete d->list_urls.takeFirst();
00813 }
00814
00815 if ( d->dirListThread ) {
00816 d->dirListThread->requestTermination();
00817 d->dirListThread = 0;
00818 }
00819 }
00820
00821
00822
00823
00824 void KUrlCompletionPrivate::setListedUrl( int complType,
00825 const QString& directory,
00826 const QString& filter,
00827 bool no_hidden )
00828 {
00829 last_compl_type = complType;
00830 last_path_listed = directory;
00831 last_file_listed = filter;
00832 last_no_hidden = (int)no_hidden;
00833 last_prepend = prepend;
00834 }
00835
00836 bool KUrlCompletionPrivate::isListedUrl( int complType,
00837 const QString& directory,
00838 const QString& filter,
00839 bool no_hidden )
00840 {
00841 return last_compl_type == complType
00842 && ( last_path_listed == directory
00843 || (directory.isEmpty() && last_path_listed.isEmpty()) )
00844 && ( filter.startsWith(last_file_listed)
00845 || (filter.isEmpty() && last_file_listed.isEmpty()) )
00846 && last_no_hidden == (int)no_hidden
00847 && last_prepend == prepend;
00848 }
00849
00850
00851
00852
00853
00854
00855 bool KUrlCompletionPrivate::isAutoCompletion()
00856 {
00857 return q->completionMode() == KGlobalSettings::CompletionAuto
00858 || q->completionMode() == KGlobalSettings::CompletionPopup
00859 || q->completionMode() == KGlobalSettings::CompletionMan
00860 || q->completionMode() == KGlobalSettings::CompletionPopupAuto;
00861 }
00864
00865
00866
00867 bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
00868 {
00869 if ( url.protocol() != QLatin1String("file")
00870 || !url.dir().isEmpty()
00871 || !url.file().startsWith( QLatin1Char('~') ) )
00872 return false;
00873
00874 if ( !isListedUrl( CTUser ) ) {
00875 q->stop();
00876 q->clear();
00877
00878 if ( !userListThread ) {
00879 userListThread = new UserListThread( this );
00880 userListThread->start();
00881
00882
00883
00884
00885 userListThread->wait( 200 );
00886 const QStringList l = userListThread->matches();
00887 addMatches( l );
00888 }
00889 }
00890 *pMatch = finished();
00891 return true;
00892 }
00893
00896
00897
00898
00899 #ifndef Q_OS_WIN
00900 extern char **environ;
00901 #endif
00902
00903 bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
00904 {
00905 if ( url.file().isEmpty() || url.file().at(0) != QLatin1Char('$') )
00906 return false;
00907
00908 if ( !isListedUrl( CTEnv ) ) {
00909 q->stop();
00910 q->clear();
00911
00912 char **env = environ;
00913
00914 QString dollar = QLatin1String("$");
00915
00916 QStringList l;
00917
00918 while ( *env ) {
00919 QString s = QString::fromLocal8Bit( *env );
00920
00921 int pos = s.indexOf(QLatin1Char('='));
00922
00923 if ( pos == -1 )
00924 pos = s.length();
00925
00926 if ( pos > 0 )
00927 l.append( prepend + dollar + s.left(pos) );
00928
00929 env++;
00930 }
00931
00932 addMatches( l );
00933 }
00934
00935 setListedUrl( CTEnv );
00936
00937 *pMatch = finished();
00938 return true;
00939 }
00940
00943
00944
00945
00946 bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
00947 {
00948 if ( url.protocol() != QLatin1String("file") )
00949 return false;
00950
00951 QString directory = unescape( url.dir() );
00952
00953
00954
00955
00956
00957
00958
00959
00960 QStringList dirList;
00961
00962 if ( !QDir::isRelativePath(directory) ) {
00963
00964 dirList.append( directory );
00965 }
00966 else if ( !directory.isEmpty() && !cwd.isEmpty() ) {
00967
00968 dirList.append( cwd + QLatin1Char('/') + directory );
00969 }
00970 else if ( !url.file().isEmpty() ) {
00971
00972 dirList = QString::fromLocal8Bit(qgetenv("PATH")).split(
00973 KPATH_SEPARATOR,QString::SkipEmptyParts);
00974
00975 QStringList::Iterator it = dirList.begin();
00976
00977 for ( ; it != dirList.end(); ++it )
00978 it->append(QLatin1Char('/'));
00979 }
00980
00981
00982 bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.');
00983
00984
00985
00986 if ( !isListedUrl( CTExe, directory, url.file(), no_hidden_files ) )
00987 {
00988 q->stop();
00989 q->clear();
00990
00991 setListedUrl( CTExe, directory, url.file(), no_hidden_files );
00992
00993 *pMatch = listDirectories( dirList, url.file(), true, false, no_hidden_files );
00994 }
00995 else if ( !q->isRunning() ) {
00996 *pMatch = finished();
00997 }
00998 else {
00999 if ( dirListThread )
01000 setListedUrl( CTExe, directory, url.file(), no_hidden_files );
01001 pMatch->clear();
01002 }
01003
01004 return true;
01005 }
01006
01009
01010
01011
01012 bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
01013 {
01014 if ( url.protocol() != QLatin1String("file") )
01015 return false;
01016
01017 QString directory = unescape( url.dir() );
01018
01019 if (url.url().length() && url.url().at(0) == QLatin1Char('.'))
01020 {
01021 if (url.url().length() == 1)
01022 {
01023 *pMatch = ( q->completionMode() == KGlobalSettings::CompletionMan )?
01024 QLatin1String(".") :
01025 QLatin1String("..");
01026 return true;
01027 }
01028 else if (url.url().length() == 2 && url.url().at(1) == QLatin1Char('.'))
01029 {
01030 *pMatch = QLatin1String("..");
01031 return true;
01032 }
01033 }
01034
01035
01036
01037
01038
01039
01040
01041
01042
01043 QStringList dirList;
01044
01045 if ( !QDir::isRelativePath(directory) ) {
01046
01047 dirList.append( directory );
01048 }
01049 else if ( !cwd.isEmpty() ) {
01050
01051 QString dirToAdd = cwd;
01052 if ( !directory.isEmpty() ) {
01053 if ( !cwd.endsWith('/') )
01054 dirToAdd.append( QLatin1Char('/') );
01055 dirToAdd.append( directory );
01056 }
01057 dirList.append( dirToAdd );
01058 }
01059
01060
01061 bool no_hidden_files = !url.file().startsWith( QLatin1Char('.') );
01062
01063
01064
01065 if ( !isListedUrl( CTFile, directory, QString(), no_hidden_files ) )
01066 {
01067 q->stop();
01068 q->clear();
01069
01070 setListedUrl( CTFile, directory, QString(), no_hidden_files );
01071
01072
01073 bool append_slash = ( popup_append_slash
01074 && (q->completionMode() == KGlobalSettings::CompletionPopup ||
01075 q->completionMode() == KGlobalSettings::CompletionPopupAuto ) );
01076
01077 bool only_dir = ( mode == KUrlCompletion::DirCompletion );
01078
01079 *pMatch = listDirectories( dirList, QString(), false, only_dir, no_hidden_files,
01080 append_slash );
01081 }
01082 else if ( !q->isRunning() ) {
01083 *pMatch = finished();
01084 }
01085 else {
01086 pMatch->clear();
01087 }
01088
01089 return true;
01090 }
01091
01094
01095
01096
01097 bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
01098 {
01099
01100 if (onlyLocalProto && KProtocolInfo::protocolClass(url.protocol()) != QLatin1String(":local"))
01101 return false;
01102
01103
01104 KUrl url_cwd( cwd );
01105
01106
01107 KUrl url_dir( url_cwd, url.kurl()->url() );
01108
01109
01110
01111
01112
01113
01114
01115 bool man_or_info = ( url_dir.protocol() == QLatin1String("man")
01116 || url_dir.protocol() == QLatin1String("info") );
01117
01118 if ( !url_dir.isValid()
01119 || !KProtocolManager::supportsListing( url_dir )
01120 || ( !man_or_info
01121 && ( url_dir.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash).isEmpty()
01122 || ( isAutoCompletion()
01123 && !url_auto_completion ) ) ) ) {
01124 return false;
01125 }
01126
01127 url_dir.setFileName(QString());
01128
01129
01130 QString directory = unescape( url_dir.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash) );
01131
01132 url_dir.setPath( directory );
01133
01134
01135
01136 if ( !isListedUrl( CTUrl, url_dir.prettyUrl(), url.file() ) )
01137 {
01138 q->stop();
01139 q->clear();
01140
01141 setListedUrl( CTUrl, url_dir.prettyUrl(), QString() );
01142
01143 QList<KUrl*> url_list;
01144 url_list.append( new KUrl( url_dir ) );
01145
01146 listUrls( url_list, QString(), false );
01147
01148 pMatch->clear();
01149 }
01150 else if ( !q->isRunning() ) {
01151 *pMatch = finished();
01152 }
01153 else {
01154 pMatch->clear();
01155 }
01156
01157 return true;
01158 }
01159
01162
01163
01164
01165
01166
01167
01168
01169
01170 void KUrlCompletionPrivate::addMatches( const QStringList &matchList )
01171 {
01172 q->insertItems(matchList);
01173 }
01174
01175
01176
01177
01178
01179
01180
01181
01182
01183
01184
01185
01186
01187 QString KUrlCompletionPrivate::listDirectories(
01188 const QStringList &dirList,
01189 const QString &filter,
01190 bool only_exe,
01191 bool only_dir,
01192 bool no_hidden,
01193 bool append_slash_to_dir)
01194 {
01195 assert( !q->isRunning() );
01196
01197 if ( qgetenv("KURLCOMPLETION_LOCAL_KIO").isEmpty() ) {
01198
01199
01200
01201
01202
01203 if ( dirListThread )
01204 dirListThread->requestTermination();
01205
01206 QStringList dirs;
01207
01208 for ( QStringList::ConstIterator it = dirList.begin();
01209 it != dirList.end();
01210 ++it )
01211 {
01212 KUrl url;
01213 url.setPath(*it);
01214 if ( KAuthorized::authorizeUrlAction( QLatin1String("list"), KUrl(), url ) )
01215 dirs.append( *it );
01216 }
01217
01218 dirListThread = new DirectoryListThread( this, dirs, filter, only_exe, only_dir,
01219 no_hidden, append_slash_to_dir );
01220 dirListThread->start();
01221 dirListThread->wait( 200 );
01222 addMatches( dirListThread->matches() );
01223
01224 return finished();
01225 }
01226
01227
01228
01229
01230 QList<KUrl*> url_list;
01231
01232 QStringList::ConstIterator it = dirList.begin();
01233
01234 for ( ; it != dirList.end(); ++it ) {
01235 url_list.append( new KUrl( *it ) );
01236 }
01237
01238 listUrls( url_list, filter, only_exe, no_hidden );
01239
01240
01241 return QString();
01242 }
01243
01244
01245
01246
01247
01248
01249
01250
01251
01252 void KUrlCompletionPrivate::listUrls(
01253 const QList<KUrl *> &urls,
01254 const QString &filter,
01255 bool only_exe,
01256 bool no_hidden )
01257 {
01258 assert( list_urls.isEmpty() );
01259 assert( list_job == 0L );
01260
01261 list_urls = urls;
01262 list_urls_filter = filter;
01263 list_urls_only_exe = only_exe;
01264 list_urls_no_hidden = no_hidden;
01265
01266
01267
01268
01269
01270
01271
01272
01273 _k_slotIOFinished(0);
01274 }
01275
01276
01277
01278
01279
01280
01281 void KUrlCompletionPrivate::_k_slotEntries(KIO::Job*, const KIO::UDSEntryList& entries)
01282 {
01283 QStringList matchList;
01284
01285 KIO::UDSEntryList::ConstIterator it = entries.begin();
01286 const KIO::UDSEntryList::ConstIterator end = entries.end();
01287
01288 QString filter = list_urls_filter;
01289
01290 int filter_len = filter.length();
01291
01292
01293
01294 for (; it != end; ++it) {
01295 const KIO::UDSEntry& entry = *it;
01296 const QString url = entry.stringValue( KIO::UDSEntry::UDS_URL );
01297
01298 QString entry_name;
01299 if (!url.isEmpty()) {
01300
01301 entry_name = KUrl(url).fileName();
01302 } else {
01303 entry_name = entry.stringValue( KIO::UDSEntry::UDS_NAME );
01304 }
01305
01306
01307
01308 if ( (!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char('.')) &&
01309 ( list_urls_no_hidden ||
01310 entry_name.length() == 1 ||
01311 ( entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.') ) ) )
01312 continue;
01313
01314 const bool isDir = entry.isDir();
01315
01316 if ( mode == KUrlCompletion::DirCompletion && !isDir )
01317 continue;
01318
01319 if ( filter_len == 0 || entry_name.left(filter_len) == filter ) {
01320
01321 QString toAppend = complete_url ? QUrl::toPercentEncoding(entry_name) : entry_name;
01322
01323 if (isDir)
01324 toAppend.append( QLatin1Char( '/' ) );
01325
01326 if ( !list_urls_only_exe ||
01327 (entry.numberValue( KIO::UDSEntry::UDS_ACCESS ) & MODE_EXE)
01328 ) {
01329 matchList.append( prepend + toAppend );
01330 }
01331 }
01332 }
01333
01334 addMatches( matchList );
01335 }
01336
01337
01338
01339
01340
01341
01342
01343
01344
01345 void KUrlCompletionPrivate::_k_slotIOFinished( KJob * job )
01346 {
01347 assert( job == list_job );
01348
01349 if ( list_urls.isEmpty() ) {
01350
01351 list_job = 0L;
01352
01353 finished();
01354
01355 }
01356 else {
01357
01358 KUrl *kurl = list_urls.takeFirst();
01359
01360
01361
01362
01363
01364 list_job = KIO::listDir( *kurl, KIO::HideProgressInfo );
01365 list_job->addMetaData("no-auth-prompt", "true");
01366
01367 assert( list_job );
01368
01369 q->connect( list_job,
01370 SIGNAL(result(KJob*)),
01371 SLOT(_k_slotIOFinished(KJob*)) );
01372
01373 q->connect( list_job,
01374 SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList&)),
01375 SLOT( _k_slotEntries( KIO::Job*, const KIO::UDSEntryList&)) );
01376
01377 delete kurl;
01378 }
01379 }
01380
01383
01384
01385
01386
01387
01388
01389
01390
01391
01392 void KUrlCompletion::postProcessMatch( QString *pMatch ) const
01393 {
01394
01395
01396 if ( !pMatch->isEmpty() ) {
01397
01398
01399
01400 if ( d->last_compl_type == CTFile
01401 && pMatch->at( pMatch->length()-1 ) != QLatin1Char('/') )
01402 {
01403 QString copy;
01404
01405 if ( pMatch->startsWith( QLatin1String("file:") ) )
01406 copy = KUrl(*pMatch).path();
01407 else
01408 copy = *pMatch;
01409
01410 expandTilde( copy );
01411 expandEnv( copy );
01412 #ifdef Q_WS_WIN
01413 DWORD dwAttr = GetFileAttributesW( (LPCWSTR) copy.utf16() );
01414 if ( dwAttr == INVALID_FILE_ATTRIBUTES ) {
01415 kDebug() << "Could not get file attribs ( "
01416 << GetLastError()
01417 << " ) for "
01418 << copy;
01419 } else
01420 if ( ( dwAttr & FILE_ATTRIBUTE_DIRECTORY ) == FILE_ATTRIBUTE_DIRECTORY )
01421 pMatch->append( QLatin1Char( '/' ) );
01422 #else
01423 if ( QDir::isRelativePath(copy) )
01424 copy.prepend( d->cwd + QLatin1Char('/') );
01425
01426
01427
01428 KDE_struct_stat sbuff;
01429
01430 QByteArray file = QFile::encodeName( copy );
01431
01432 if ( KDE_stat( file.data(), &sbuff ) == 0 ) {
01433 if ( S_ISDIR ( sbuff.st_mode ) )
01434 pMatch->append( QLatin1Char( '/' ) );
01435 }
01436 else {
01437 kDebug() << "Could not stat file" << copy;
01438 }
01439 #endif
01440 }
01441 }
01442 }
01443
01444 void KUrlCompletion::postProcessMatches( QStringList * ) const
01445 {
01446
01447
01448
01449 }
01450
01451 void KUrlCompletion::postProcessMatches( KCompletionMatches * ) const
01452 {
01453
01454
01455
01456 }
01457
01458 void KUrlCompletion::customEvent(QEvent *e)
01459 {
01460 if ( e->type() == CompletionMatchEvent::uniqueType() ) {
01461
01462 CompletionMatchEvent *matchEvent = static_cast<CompletionMatchEvent *>( e );
01463
01464 matchEvent->completionThread()->wait();
01465
01466 if ( !d->isListedUrl( CTUser ) ) {
01467 stop();
01468 clear();
01469 d->addMatches( matchEvent->completionThread()->matches() );
01470 }
01471
01472 d->setListedUrl( CTUser );
01473
01474 if ( d->userListThread == matchEvent->completionThread() )
01475 d->userListThread = 0;
01476
01477 if ( d->dirListThread == matchEvent->completionThread() )
01478 d->dirListThread = 0;
01479
01480 delete matchEvent->completionThread();
01481 }
01482 }
01483
01484
01485 QString KUrlCompletion::replacedPath( const QString& text, bool replaceHome, bool replaceEnv )
01486 {
01487 if ( text.isEmpty() )
01488 return text;
01489
01490 KUrlCompletionPrivate::MyURL url( text, QString() );
01491 if ( !url.kurl()->isLocalFile() )
01492 return text;
01493
01494 url.filter( replaceHome, replaceEnv );
01495 return url.dir() + url.file();
01496 }
01497
01498
01499 QString KUrlCompletion::replacedPath( const QString& text ) const
01500 {
01501 return replacedPath( text, d->replace_home, d->replace_env );
01502 }
01503
01506
01507
01508
01509
01510
01511
01512
01513
01514 static bool expandEnv( QString &text )
01515 {
01516
01517
01518 int pos = 0;
01519
01520 bool expanded = false;
01521
01522 while ( (pos = text.indexOf(QLatin1Char('$'), pos)) != -1 ) {
01523
01524
01525
01526 if ( pos > 0 && text.at(pos-1) == QLatin1Char('\\') ) {
01527 pos++;
01528 }
01529
01530
01531 else {
01532
01533
01534 int pos2 = text.indexOf( QLatin1Char(' '), pos+1 );
01535 int pos_tmp = text.indexOf( QLatin1Char('/'), pos+1 );
01536
01537 if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) )
01538 pos2 = pos_tmp;
01539
01540 if ( pos2 == -1 )
01541 pos2 = text.length();
01542
01543
01544
01545
01546 if ( pos2 >= 0 ) {
01547 int len = pos2 - pos;
01548 QString key = text.mid( pos+1, len-1);
01549 QString value =
01550 QString::fromLocal8Bit( qgetenv(key.toLocal8Bit()) );
01551
01552 if ( !value.isEmpty() ) {
01553 expanded = true;
01554 text.replace( pos, len, value );
01555 pos = pos + value.length();
01556 }
01557 else {
01558 pos = pos2;
01559 }
01560 }
01561 }
01562 }
01563
01564 return expanded;
01565 }
01566
01567
01568
01569
01570
01571
01572
01573 static bool expandTilde(QString &text)
01574 {
01575 if ( text.isEmpty() || ( text.at(0) != QLatin1Char('~') ))
01576 return false;
01577
01578 bool expanded = false;
01579
01580
01581
01582 int pos2 = text.indexOf( QLatin1Char(' '), 1 );
01583 int pos_tmp = text.indexOf( QLatin1Char('/'), 1 );
01584
01585 if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) )
01586 pos2 = pos_tmp;
01587
01588 if ( pos2 == -1 )
01589 pos2 = text.length();
01590
01591
01592
01593 if ( pos2 >= 0 ) {
01594
01595 QString user = text.mid( 1, pos2-1 );
01596 QString dir;
01597
01598
01599
01600 if ( user.isEmpty() ) {
01601 dir = QDir::homePath();
01602 }
01603
01604
01605 else {
01606 struct passwd *pw = ::getpwnam( user.toLocal8Bit() );
01607
01608 if ( pw )
01609 dir = QFile::decodeName( pw->pw_dir );
01610
01611 ::endpwent();
01612 }
01613
01614 if ( !dir.isEmpty() ) {
01615 expanded = true;
01616 text.replace(0, pos2, dir);
01617 }
01618 }
01619
01620 return expanded;
01621 }
01622
01623
01624
01625
01626
01627
01628
01629 static QString unescape(const QString &text)
01630 {
01631 QString result;
01632
01633 for (int pos = 0; pos < text.length(); pos++)
01634 if ( text.at(pos) != QLatin1Char('\\') )
01635 result.insert( result.length(), text.at(pos) );
01636
01637 return result;
01638 }
01639
01640 #include "kurlcompletion.moc"