diff options
Diffstat (limited to 'tdeio/tdeio/kurlcompletion.cpp')
-rw-r--r-- | tdeio/tdeio/kurlcompletion.cpp | 1604 |
1 files changed, 1604 insertions, 0 deletions
diff --git a/tdeio/tdeio/kurlcompletion.cpp b/tdeio/tdeio/kurlcompletion.cpp new file mode 100644 index 000000000..22bbe147a --- /dev/null +++ b/tdeio/tdeio/kurlcompletion.cpp @@ -0,0 +1,1604 @@ +/* -*- indent-tabs-mode: t; tab-width: 4; c-basic-offset:4 -*- + + This file is part of the KDE libraries + Copyright (C) 2000 David Smith <dsmith@algonet.se> + Copyright (C) 2004 Scott Wheeler <wheeler@kde.org> + + This class was inspired by a previous KURLCompletion by + Henner Zeller <zeller@think.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include <config.h> +#include <stdlib.h> +#include <assert.h> +#include <limits.h> + +#include <tqstring.h> +#include <tqstringlist.h> +#include <tqvaluelist.h> +#include <tqregexp.h> +#include <tqtimer.h> +#include <tqdir.h> +#include <tqfile.h> +#include <tqtextstream.h> +#include <tqdeepcopy.h> +#include <tqthread.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kcompletion.h> +#include <kurl.h> +#include <tdeio/jobclasses.h> +#include <tdeio/job.h> +#include <kprotocolinfo.h> +#include <tdeconfig.h> +#include <kglobal.h> +#include <klocale.h> +#include <kde_file.h> + +#include <sys/types.h> +#include <dirent.h> +#include <unistd.h> +#include <sys/stat.h> +#include <pwd.h> +#include <time.h> +#include <sys/param.h> + +#include "kurlcompletion.h" + +static bool expandTilde(TQString &); +static bool expandEnv(TQString &); + +static TQString unescape(const TQString &text); + +// Permission mask for files that are executable by +// user, group or other +#define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH) + +// Constants for types of completion +enum ComplType {CTNone=0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo}; + +class CompletionThread; + +/** + * A custom event type that is used to return a list of completion + * matches from an asyncrynous lookup. + */ + +class CompletionMatchEvent : public TQCustomEvent +{ +public: + CompletionMatchEvent( CompletionThread *thread ) : + TQCustomEvent( uniqueType() ), + m_completionThread( thread ) + {} + + CompletionThread *completionThread() const { return m_completionThread; } + static int uniqueType() { return User + 61080; } + +private: + CompletionThread *m_completionThread; +}; + +class CompletionThread : public TQThread +{ +protected: + CompletionThread( KURLCompletion *receiver ) : + TQThread(), + m_receiver( receiver ), + m_terminationRequested( false ) + {} + +public: + void requestTermination() { m_terminationRequested = true; } + TQDeepCopy<TQStringList> matches() const { return m_matches; } + +protected: + void addMatch( const TQString &match ) { m_matches.append( match ); } + bool terminationRequested() const { return m_terminationRequested; } + void done() + { + if ( !m_terminationRequested ) + kapp->postEvent( m_receiver, new CompletionMatchEvent( this ) ); + else + delete this; + } + +private: + KURLCompletion *m_receiver; + TQStringList m_matches; + bool m_terminationRequested; +}; + +/** + * A simple thread that fetches a list of tilde-completions and returns this + * to the caller via a CompletionMatchEvent. + */ + +class UserListThread : public CompletionThread +{ +public: + UserListThread( KURLCompletion *receiver ) : + CompletionThread( receiver ) + {} + +protected: + virtual void run() + { + static const TQChar tilde = '~'; + + struct passwd *pw; + while ( ( pw = ::getpwent() ) && !terminationRequested() ) + addMatch( tilde + TQString::fromLocal8Bit( pw->pw_name ) ); + + ::endpwent(); + + addMatch( tilde ); + + done(); + } +}; + +class DirectoryListThread : public CompletionThread +{ +public: + DirectoryListThread( KURLCompletion *receiver, + const TQStringList &dirList, + const TQString &filter, + bool onlyExe, + bool onlyDir, + bool noHidden, + bool appendSlashToDir ) : + CompletionThread( receiver ), + m_dirList( TQDeepCopy<TQStringList>( dirList ) ), + m_filter( TQDeepCopy<TQString>( filter ) ), + m_onlyExe( onlyExe ), + m_onlyDir( onlyDir ), + m_noHidden( noHidden ), + m_appendSlashToDir( appendSlashToDir ) + {} + + virtual void run(); + +private: + TQStringList m_dirList; + TQString m_filter; + bool m_onlyExe; + bool m_onlyDir; + bool m_noHidden; + bool m_appendSlashToDir; +}; + +void DirectoryListThread::run() +{ + // Thread safety notes: + // + // There very possibly may be thread safety issues here, but I've done a check + // of all of the things that would seem to be problematic. Here are a few + // things that I have checked to be safe here (some used indirectly): + // + // TQDir::currentDirPath(), TQDir::setCurrent(), TQFile::decodeName(), TQFile::encodeName() + // TQString::fromLocal8Bit(), TQString::local8Bit(), TQTextCodec::codecForLocale() + // + // Also see (for POSIX functions): + // http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html + + DIR *dir = 0; + + for ( TQStringList::ConstIterator it = m_dirList.begin(); + it != m_dirList.end() && !terminationRequested(); + ++it ) + { + // Open the next directory + + if ( !dir ) { + dir = ::opendir( TQFile::encodeName( *it ) ); + if ( ! dir ) { + kdDebug() << "Failed to open dir: " << *it << endl; + done(); + return; + } + } + + // A trick from KIO that helps performance by a little bit: + // chdir to the directroy so we won't have to deal with full paths + // with stat() + + TQString path = TQDir::currentDirPath(); + TQDir::setCurrent( *it ); + + // Loop through all directory entries + // Solaris and IRIX dirent structures do not allocate space for d_name. On + // systems that do (HP-UX, Linux, Tru64 UNIX), we overallocate space but + // that's ok. +#ifndef HAVE_READDIR_R + struct dirent *dirEntry = 0; + while ( !terminationRequested() && + (dirEntry = ::readdir( dir))) +#else +#if !defined(MAXPATHLEN) && defined(__GNU__) +#define MAXPATHLEN UCHAR_MAX +#endif + struct dirent *dirPosition = (struct dirent *) malloc( sizeof( struct dirent ) + MAXPATHLEN + 1 ); + struct dirent *dirEntry = 0; + while ( !terminationRequested() && + ::readdir_r( dir, dirPosition, &dirEntry ) == 0 && dirEntry ) +#endif + + { + // Skip hidden files if m_noHidden is true + + if ( dirEntry->d_name[0] == '.' && m_noHidden ) + continue; + + // Skip "." + + if ( dirEntry->d_name[0] == '.' && dirEntry->d_name[1] == '\0' ) + continue; + + // Skip ".." + + if ( dirEntry->d_name[0] == '.' && dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0' ) + continue; + + TQString file = TQFile::decodeName( dirEntry->d_name ); + + if ( m_filter.isEmpty() || file.startsWith( m_filter ) ) { + + if ( m_onlyExe || m_onlyDir || m_appendSlashToDir ) { + KDE_struct_stat sbuff; + + if ( KDE_stat( dirEntry->d_name, &sbuff ) == 0 ) { + + // Verify executable + + if ( m_onlyExe && ( sbuff.st_mode & MODE_EXE ) == 0 ) + continue; + + // Verify directory + + if ( m_onlyDir && !S_ISDIR( sbuff.st_mode ) ) + continue; + + // Add '/' to directories + + if ( m_appendSlashToDir && S_ISDIR( sbuff.st_mode ) ) + file.append( '/' ); + + } + else { + kdDebug() << "Could not stat file " << file << endl; + continue; + } + } + + addMatch( file ); + } + } + + // chdir to the original directory + + TQDir::setCurrent( path ); + + ::closedir( dir ); + dir = 0; +#ifdef HAVE_READDIR_R + free( dirPosition ); +#endif + } + + done(); +} + +/////////////////////////////////////////////////////// +/////////////////////////////////////////////////////// +// MyURL - wrapper for KURL with some different functionality +// + +class KURLCompletion::MyURL +{ +public: + MyURL(const TQString &url, const TQString &cwd); + MyURL(const MyURL &url); + ~MyURL(); + + KURL *kurl() const { return m_kurl; } + + TQString protocol() const { return m_kurl->protocol(); } + // The directory with a trailing '/' + TQString dir() const { return m_kurl->directory(false, false); } + TQString file() const { return m_kurl->fileName(false); } + + // The initial, unparsed, url, as a string. + TQString url() const { return m_url; } + + // Is the initial string a URL, or just a path (whether absolute or relative) + bool isURL() const { return m_isURL; } + + void filter( bool replace_user_dir, bool replace_env ); + +private: + void init(const TQString &url, const TQString &cwd); + + KURL *m_kurl; + TQString m_url; + bool m_isURL; +}; + +KURLCompletion::MyURL::MyURL(const TQString &url, const TQString &cwd) +{ + init(url, cwd); +} + +KURLCompletion::MyURL::MyURL(const MyURL &url) +{ + m_kurl = new KURL( *(url.m_kurl) ); + m_url = url.m_url; + m_isURL = url.m_isURL; +} + +void KURLCompletion::MyURL::init(const TQString &url, const TQString &cwd) +{ + // Save the original text + m_url = url; + + // Non-const copy + TQString url_copy = url; + + // Special shortcuts for "man:" and "info:" + if ( url_copy[0] == '#' ) { + if ( url_copy[1] == '#' ) + url_copy.replace( 0, 2, TQString("info:") ); + else + url_copy.replace( 0, 1, TQString("man:") ); + } + + // Look for a protocol in 'url' + TQRegExp protocol_regex = TQRegExp( "^[^/\\s\\\\]*:" ); + + // Assume "file:" or whatever is given by 'cwd' if there is + // no protocol. (KURL does this only for absoute paths) + if ( protocol_regex.search( url_copy ) == 0 ) + { + m_kurl = new KURL( url_copy ); + m_isURL = true; + } + else // relative path or ~ or $something + { + m_isURL = false; + if ( cwd.isEmpty() ) + { + m_kurl = new KURL(); + if ( !TQDir::isRelativePath(url_copy) || url_copy[0] == '$' || url_copy[0] == '~' ) + m_kurl->setPath( url_copy ); + else + *m_kurl = url_copy; + } + else + { + KURL base = KURL::fromPathOrURL( cwd ); + base.adjustPath(+1); + + if ( !TQDir::isRelativePath(url_copy) || url_copy[0] == '~' || url_copy[0] == '$' ) + { + m_kurl = new KURL(); + m_kurl->setPath( url_copy ); + } + else // relative path + { + //m_kurl = new KURL( base, url_copy ); + m_kurl = new KURL( base ); + m_kurl->addPath( url_copy ); + } + } + } +} + +KURLCompletion::MyURL::~MyURL() +{ + delete m_kurl; +} + +void KURLCompletion::MyURL::filter( bool replace_user_dir, bool replace_env ) +{ + TQString d = dir() + file(); + if ( replace_user_dir ) expandTilde( d ); + if ( replace_env ) expandEnv( d ); + m_kurl->setPath( d ); +} + +/////////////////////////////////////////////////////// +/////////////////////////////////////////////////////// +// KURLCompletionPrivate +// +class KURLCompletionPrivate +{ +public: + KURLCompletionPrivate() : url_auto_completion(true), + userListThread(0), + dirListThread(0) {} + ~KURLCompletionPrivate(); + + TQValueList<KURL*> list_urls; + + bool onlyLocalProto; + + // urlCompletion() in Auto/Popup mode? + bool url_auto_completion; + + // Append '/' to directories in Popup mode? + // Doing that stat's all files and is slower + bool popup_append_slash; + + // Keep track of currently listed files to avoid reading them again + TQString last_path_listed; + TQString last_file_listed; + TQString last_prepend; + int last_compl_type; + int last_no_hidden; + + TQString cwd; // "current directory" = base dir for completion + + KURLCompletion::Mode mode; // ExeCompletion, FileCompletion, DirCompletion + bool replace_env; + bool replace_home; + bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path + + TDEIO::ListJob *list_job; // kio job to list directories + + TQString prepend; // text to prepend to listed items + TQString compl_text; // text to pass on to KCompletion + + // Filters for files read with kio + bool list_urls_only_exe; // true = only list executables + bool list_urls_no_hidden; + TQString list_urls_filter; // filter for listed files + + CompletionThread *userListThread; + CompletionThread *dirListThread; +}; + +KURLCompletionPrivate::~KURLCompletionPrivate() +{ + if ( userListThread ) + userListThread->requestTermination(); + if ( dirListThread ) + dirListThread->requestTermination(); +} + +/////////////////////////////////////////////////////// +/////////////////////////////////////////////////////// +// KURLCompletion +// + +KURLCompletion::KURLCompletion() : KCompletion() +{ + init(); +} + + +KURLCompletion::KURLCompletion( Mode mode ) : KCompletion() +{ + init(); + setMode ( mode ); +} + +KURLCompletion::~KURLCompletion() +{ + stop(); + delete d; +} + + +void KURLCompletion::init() +{ + d = new KURLCompletionPrivate; + + d->cwd = TQDir::homeDirPath(); + + d->replace_home = true; + d->replace_env = true; + d->last_no_hidden = false; + d->last_compl_type = 0; + d->list_job = 0L; + d->mode = KURLCompletion::FileCompletion; + + // Read settings + TDEConfig *c = TDEGlobal::config(); + TDEConfigGroupSaver cgs( c, "URLCompletion" ); + + d->url_auto_completion = c->readBoolEntry("alwaysAutoComplete", true); + d->popup_append_slash = c->readBoolEntry("popupAppendSlash", true); + d->onlyLocalProto = c->readBoolEntry("LocalProtocolsOnly", false); +} + +void KURLCompletion::setDir(const TQString &dir) +{ + d->cwd = dir; +} + +TQString KURLCompletion::dir() const +{ + return d->cwd; +} + +KURLCompletion::Mode KURLCompletion::mode() const +{ + return d->mode; +} + +void KURLCompletion::setMode( Mode mode ) +{ + d->mode = mode; +} + +bool KURLCompletion::replaceEnv() const +{ + return d->replace_env; +} + +void KURLCompletion::setReplaceEnv( bool replace ) +{ + d->replace_env = replace; +} + +bool KURLCompletion::replaceHome() const +{ + return d->replace_home; +} + +void KURLCompletion::setReplaceHome( bool replace ) +{ + d->replace_home = replace; +} + +/* + * makeCompletion() + * + * Entry point for file name completion + */ +TQString KURLCompletion::makeCompletion(const TQString &text) +{ + //kdDebug() << "KURLCompletion::makeCompletion: " << text << " d->cwd=" << d->cwd << endl; + + MyURL url(text, d->cwd); + + d->compl_text = text; + + // Set d->prepend to the original URL, with the filename [and ref/query] stripped. + // This is what gets prepended to the directory-listing matches. + int toRemove = url.file().length() - url.kurl()->query().length(); + if ( url.kurl()->hasRef() ) + toRemove += url.kurl()->ref().length() + 1; + d->prepend = text.left( text.length() - toRemove ); + d->complete_url = url.isURL(); + + TQString match; + + // Environment variables + // + if ( d->replace_env && envCompletion( url, &match ) ) + return match; + + // User directories + // + if ( d->replace_home && userCompletion( url, &match ) ) + return match; + + // Replace user directories and variables + url.filter( d->replace_home, d->replace_env ); + + //kdDebug() << "Filtered: proto=" << url.protocol() + // << ", dir=" << url.dir() + // << ", file=" << url.file() + // << ", kurl url=" << *url.kurl() << endl; + + if ( d->mode == ExeCompletion ) { + // Executables + // + if ( exeCompletion( url, &match ) ) + return match; + + // KRun can run "man:" and "info:" etc. so why not treat them + // as executables... + + if ( urlCompletion( url, &match ) ) + return match; + } + else if ( d->mode == SystemExeCompletion ) { + // Executables + // + if ( systemexeCompletion( url, &match ) ) + return match; + + // KRun can run "man:" and "info:" etc. so why not treat them + // as executables... + + if ( urlCompletion( url, &match ) ) + return match; + } + else { + // Local files, directories + // + if ( fileCompletion( url, &match ) ) + return match; + + // All other... + // + if ( urlCompletion( url, &match ) ) + return match; + } + + setListedURL( CTNone ); + stop(); + + return TQString::null; +} + +/* + * finished + * + * Go on and call KCompletion. + * Called when all matches have been added + */ +TQString KURLCompletion::finished() +{ + if ( d->last_compl_type == CTInfo ) + return KCompletion::makeCompletion( d->compl_text.lower() ); + else + return KCompletion::makeCompletion( d->compl_text ); +} + +/* + * isRunning + * + * Return true if either a KIO job or the DirLister + * is running + */ +bool KURLCompletion::isRunning() const +{ + return d->list_job || (d->dirListThread && !d->dirListThread->finished()); +} + +/* + * stop + * + * Stop and delete a running KIO job or the DirLister + */ +void KURLCompletion::stop() +{ + if ( d->list_job ) { + d->list_job->kill(); + d->list_job = 0L; + } + + if ( !d->list_urls.isEmpty() ) { + TQValueList<KURL*>::Iterator it = d->list_urls.begin(); + for ( ; it != d->list_urls.end(); it++ ) + delete (*it); + d->list_urls.clear(); + } + + if ( d->dirListThread ) { + d->dirListThread->requestTermination(); + d->dirListThread = 0; + } +} + +/* + * Keep track of the last listed directory + */ +void KURLCompletion::setListedURL( int complType, + const TQString& dir, + const TQString& filter, + bool no_hidden ) +{ + d->last_compl_type = complType; + d->last_path_listed = dir; + d->last_file_listed = filter; + d->last_no_hidden = (int)no_hidden; + d->last_prepend = d->prepend; +} + +bool KURLCompletion::isListedURL( int complType, + const TQString& dir, + const TQString& filter, + bool no_hidden ) +{ + return d->last_compl_type == complType + && ( d->last_path_listed == dir + || (dir.isEmpty() && d->last_path_listed.isEmpty()) ) + && ( filter.startsWith(d->last_file_listed) + || (filter.isEmpty() && d->last_file_listed.isEmpty()) ) + && d->last_no_hidden == (int)no_hidden + && d->last_prepend == d->prepend; // e.g. relative path vs absolute +} + +/* + * isAutoCompletion + * + * Returns true if completion mode is Auto or Popup + */ +bool KURLCompletion::isAutoCompletion() +{ + return completionMode() == TDEGlobalSettings::CompletionAuto + || completionMode() == TDEGlobalSettings::CompletionPopup + || completionMode() == TDEGlobalSettings::CompletionMan + || completionMode() == TDEGlobalSettings::CompletionPopupAuto; +} +////////////////////////////////////////////////// +////////////////////////////////////////////////// +// User directories +// + +bool KURLCompletion::userCompletion(const MyURL &url, TQString *match) +{ + if ( url.protocol() != "file" + || !url.dir().isEmpty() + || url.file().at(0) != '~' ) + return false; + + if ( !isListedURL( CTUser ) ) { + stop(); + clear(); + + if ( !d->userListThread ) { + d->userListThread = new UserListThread( this ); + d->userListThread->start(); + + // If the thread finishes quickly make sure that the results + // are added to the first matching case. + + d->userListThread->wait( 200 ); + TQStringList l = d->userListThread->matches(); + addMatches( l ); + } + } + *match = finished(); + return true; +} + +///////////////////////////////////////////////////// +///////////////////////////////////////////////////// +// Environment variables +// + +extern char **environ; // Array of environment variables + +bool KURLCompletion::envCompletion(const MyURL &url, TQString *match) +{ + if ( url.file().at(0) != '$' ) + return false; + + if ( !isListedURL( CTEnv ) ) { + stop(); + clear(); + + char **env = environ; + + TQString dollar = TQString("$"); + + TQStringList l; + + while ( *env ) { + TQString s = TQString::fromLocal8Bit( *env ); + + int pos = s.find('='); + + if ( pos == -1 ) + pos = s.length(); + + if ( pos > 0 ) + l.append( dollar + s.left(pos) ); + + env++; + } + + addMatches( l ); + } + + setListedURL( CTEnv ); + + *match = finished(); + return true; +} + +////////////////////////////////////////////////// +////////////////////////////////////////////////// +// Executables +// + +bool KURLCompletion::exeCompletion(const MyURL &url, TQString *match) +{ + if ( url.protocol() != "file" ) + return false; + + TQString dir = url.dir(); + + dir = unescape( dir ); // remove escapes + + // Find directories to search for completions, either + // + // 1. complete path given in url + // 2. current directory (d->cwd) + // 3. $PATH + // 4. no directory at all + + TQStringList dirList; + + if ( !TQDir::isRelativePath(dir) ) { + // complete path in url + dirList.append( dir ); + } + else if ( !dir.isEmpty() && !d->cwd.isEmpty() ) { + // current directory + dirList.append( d->cwd + '/' + dir ); + } + else if ( !url.file().isEmpty() ) { + // $PATH + dirList = TQStringList::split(KPATH_SEPARATOR, + TQString::fromLocal8Bit(::getenv("PATH"))); + + TQStringList::Iterator it = dirList.begin(); + + for ( ; it != dirList.end(); it++ ) + (*it).append('/'); + } + + // No hidden files unless the user types "." + bool no_hidden_files = url.file().at(0) != '.'; + + // List files if needed + // + if ( !isListedURL( CTExe, dir, url.file(), no_hidden_files ) ) + { + stop(); + clear(); + + setListedURL( CTExe, dir, url.file(), no_hidden_files ); + + *match = listDirectories( dirList, url.file(), true, false, no_hidden_files ); + } + else if ( !isRunning() ) { + *match = finished(); + } + else { + if ( d->dirListThread ) + setListedURL( CTExe, dir, url.file(), no_hidden_files ); + *match = TQString::null; + } + + return true; +} + +////////////////////////////////////////////////// +////////////////////////////////////////////////// +// System Executables +// + +bool KURLCompletion::systemexeCompletion(const MyURL &url, TQString *match) +{ + if ( url.protocol() != "file" ) + return false; + + TQString dir = url.dir(); + + dir = unescape( dir ); // remove escapes + + // Find directories to search for completions, either + // + // 1. complete path given in url + // 2. current directory (d->cwd) + // 3. $PATH + // 4. no directory at all + + TQStringList dirList; + + if ( !url.file().isEmpty() ) { + // $PATH + dirList = TQStringList::split(KPATH_SEPARATOR, + TQString::fromLocal8Bit(::getenv("PATH"))); + + TQStringList::Iterator it = dirList.begin(); + + for ( ; it != dirList.end(); it++ ) + (*it).append('/'); + } + + // No hidden files unless the user types "." + bool no_hidden_files = url.file().at(0) != '.'; + + // List files if needed + // + if ( !isListedURL( CTExe, dir, url.file(), no_hidden_files ) ) + { + stop(); + clear(); + + setListedURL( CTExe, dir, url.file(), no_hidden_files ); + + *match = listDirectories( dirList, url.file(), true, false, no_hidden_files ); + } + else if ( !isRunning() ) { + *match = finished(); + } + else { + if ( d->dirListThread ) + setListedURL( CTExe, dir, url.file(), no_hidden_files ); + *match = TQString::null; + } + + return true; +} + +////////////////////////////////////////////////// +////////////////////////////////////////////////// +// Local files +// + +bool KURLCompletion::fileCompletion(const MyURL &url, TQString *match) +{ + if ( url.protocol() != "file" ) + return false; + + TQString dir = url.dir(); + + if (url.url()[0] == '.') + { + if (url.url().length() == 1) + { + *match = + ( completionMode() == TDEGlobalSettings::CompletionMan )? "." : ".."; + return true; + } + if (url.url().length() == 2 && url.url()[1]=='.') + { + *match=".."; + return true; + } + } + + //kdDebug() << "fileCompletion " << url.url() << " dir=" << dir << endl; + + dir = unescape( dir ); // remove escapes + + // Find directories to search for completions, either + // + // 1. complete path given in url + // 2. current directory (d->cwd) + // 3. no directory at all + + TQStringList dirList; + + if ( !TQDir::isRelativePath(dir) ) { + // complete path in url + dirList.append( dir ); + } + else if ( !d->cwd.isEmpty() ) { + // current directory + dirList.append( d->cwd + '/' + dir ); + } + + // No hidden files unless the user types "." + bool no_hidden_files = ( url.file().at(0) != '.' ); + + // List files if needed + // + if ( !isListedURL( CTFile, dir, "", no_hidden_files ) ) + { + stop(); + clear(); + + setListedURL( CTFile, dir, "", no_hidden_files ); + + // Append '/' to directories in Popup mode? + bool append_slash = ( d->popup_append_slash + && (completionMode() == TDEGlobalSettings::CompletionPopup || + completionMode() == TDEGlobalSettings::CompletionPopupAuto ) ); + + bool only_dir = ( d->mode == DirCompletion ); + + *match = listDirectories( dirList, "", false, only_dir, no_hidden_files, + append_slash ); + } + else if ( !isRunning() ) { + *match = finished(); + } + else { + *match = TQString::null; + } + + return true; +} + +////////////////////////////////////////////////// +////////////////////////////////////////////////// +// URLs not handled elsewhere... +// + +bool KURLCompletion::urlCompletion(const MyURL &url, TQString *match) +{ + //kdDebug() << "urlCompletion: url = " << *url.kurl() << endl; + if (d->onlyLocalProto && KProtocolInfo::protocolClass(url.protocol()) != ":local") + return false; + + // Use d->cwd as base url in case url is not absolute + KURL url_cwd = KURL::fromPathOrURL( d->cwd ); + + // Create an URL with the directory to be listed + KURL url_dir( url_cwd, url.kurl()->url() ); + + // Don't try url completion if + // 1. malformed url + // 2. protocol that doesn't have listDir() + // 3. there is no directory (e.g. "ftp://ftp.kd" shouldn't do anything) + // 4. auto or popup completion mode depending on settings + + bool man_or_info = ( url_dir.protocol() == TQString("man") + || url_dir.protocol() == TQString("info") ); + + if ( !url_dir.isValid() + || !KProtocolInfo::supportsListing( url_dir ) + || ( !man_or_info + && ( url_dir.directory(false,false).isEmpty() + || ( isAutoCompletion() + && !d->url_auto_completion ) ) ) ) { + return false; + } + + url_dir.setFileName(""); // not really nesseccary, but clear the filename anyway... + + // Remove escapes + TQString dir = url_dir.directory( false, false ); + + dir = unescape( dir ); + + url_dir.setPath( dir ); + + // List files if needed + // + if ( !isListedURL( CTUrl, url_dir.prettyURL(), url.file() ) ) + { + stop(); + clear(); + + setListedURL( CTUrl, url_dir.prettyURL(), "" ); + + TQValueList<KURL*> url_list; + url_list.append( new KURL( url_dir ) ); + + listURLs( url_list, "", false ); + + *match = TQString::null; + } + else if ( !isRunning() ) { + *match = finished(); + } + else { + *match = TQString::null; + } + + return true; +} + +////////////////////////////////////////////////// +////////////////////////////////////////////////// +// Directory and URL listing +// + +/* + * addMatches + * + * Called to add matches to KCompletion + */ +void KURLCompletion::addMatches( const TQStringList &matches ) +{ + TQStringList::ConstIterator it = matches.begin(); + TQStringList::ConstIterator end = matches.end(); + + if ( d->complete_url ) + for ( ; it != end; it++ ) + addItem( d->prepend + KURL::encode_string(*it)); + else + for ( ; it != end; it++ ) + addItem( d->prepend + (*it)); +} + +/* + * listDirectories + * + * List files starting with 'filter' in the given directories, + * either using DirLister or listURLs() + * + * In either case, addMatches() is called with the listed + * files, and eventually finished() when the listing is done + * + * Returns the match if available, or TQString::null if + * DirLister timed out or using kio + */ +TQString KURLCompletion::listDirectories( + const TQStringList &dirList, + const TQString &filter, + bool only_exe, + bool only_dir, + bool no_hidden, + bool append_slash_to_dir) +{ + assert( !isRunning() ); + + if ( !::getenv("KURLCOMPLETION_LOCAL_KIO") ) { + + //kdDebug() << "Listing (listDirectories): " << dirList << " filter=" << filter << " without KIO" << endl; + + // Don't use KIO + + if ( d->dirListThread ) + d->dirListThread->requestTermination(); + + TQStringList dirs; + + for ( TQStringList::ConstIterator it = dirList.begin(); + it != dirList.end(); + ++it ) + { + KURL url; + url.setPath(*it); + if ( kapp->authorizeURLAction( "list", KURL(), url ) ) + dirs.append( *it ); + } + + d->dirListThread = new DirectoryListThread( this, dirs, filter, only_exe, only_dir, + no_hidden, append_slash_to_dir ); + d->dirListThread->start(); + d->dirListThread->wait( 200 ); + addMatches( d->dirListThread->matches() ); + + return finished(); + } + else { + + // Use KIO + //kdDebug() << "Listing (listDirectories): " << dirList << " with KIO" << endl; + + TQValueList<KURL*> url_list; + + TQStringList::ConstIterator it = dirList.begin(); + + for ( ; it != dirList.end(); it++ ) + url_list.append( new KURL(*it) ); + + listURLs( url_list, filter, only_exe, no_hidden ); + // Will call addMatches() and finished() + + return TQString::null; + } +} + +/* + * listURLs + * + * Use KIO to list the given urls + * + * addMatches() is called with the listed files + * finished() is called when the listing is done + */ +void KURLCompletion::listURLs( + const TQValueList<KURL *> &urls, + const TQString &filter, + bool only_exe, + bool no_hidden ) +{ + assert( d->list_urls.isEmpty() ); + assert( d->list_job == 0L ); + + d->list_urls = urls; + d->list_urls_filter = filter; + d->list_urls_only_exe = only_exe; + d->list_urls_no_hidden = no_hidden; + +// kdDebug() << "Listing URLs: " << urls[0]->prettyURL() << ",..." << endl; + + // Start it off by calling slotIOFinished + // + // This will start a new list job as long as there + // are urls in d->list_urls + // + slotIOFinished(0L); +} + +/* + * slotEntries + * + * Receive files listed by KIO and call addMatches() + */ +void KURLCompletion::slotEntries(TDEIO::Job*, const TDEIO::UDSEntryList& entries) +{ + TQStringList matches; + + TDEIO::UDSEntryListConstIterator it = entries.begin(); + TDEIO::UDSEntryListConstIterator end = entries.end(); + + TQString filter = d->list_urls_filter; + + int filter_len = filter.length(); + + // Iterate over all files + // + for (; it != end; ++it) { + TQString name; + TQString url; + bool is_exe = false; + bool is_dir = false; + + TDEIO::UDSEntry e = *it; + TDEIO::UDSEntry::ConstIterator it_2 = e.begin(); + + for( ; it_2 != e.end(); it_2++ ) { + switch ( (*it_2).m_uds ) { + case TDEIO::UDS_NAME: + name = (*it_2).m_str; + break; + case TDEIO::UDS_ACCESS: + is_exe = ((*it_2).m_long & MODE_EXE) != 0; + break; + case TDEIO::UDS_FILE_TYPE: + is_dir = ((*it_2).m_long & S_IFDIR) != 0; + break; + case TDEIO::UDS_URL: + url = (*it_2).m_str; + break; + } + } + + if (!url.isEmpty()) { + // kdDebug() << "KURLCompletion::slotEntries url: " << url << endl; + name = KURL(url).fileName(); + } + + // kdDebug() << "KURLCompletion::slotEntries name: " << name << endl; + + if ( name[0] == '.' && + ( d->list_urls_no_hidden || + name.length() == 1 || + ( name.length() == 2 && name[1] == '.' ) ) ) + continue; + + if ( d->mode == DirCompletion && !is_dir ) + continue; + + if ( filter_len == 0 || name.left(filter_len) == filter ) { + if ( is_dir ) + name.append( '/' ); + + if ( is_exe || !d->list_urls_only_exe ) + matches.append( name ); + } + } + + addMatches( matches ); +} + +/* + * slotIOFinished + * + * Called when a KIO job is finished. + * + * Start a new list job if there are still urls in + * d->list_urls, otherwise call finished() + */ +void KURLCompletion::slotIOFinished( TDEIO::Job * job ) +{ +// kdDebug() << "slotIOFinished() " << endl; + + assert( job == d->list_job ); + + if ( d->list_urls.isEmpty() ) { + + d->list_job = 0L; + + finished(); // will call KCompletion::makeCompletion() + + } + else { + + KURL *kurl = d->list_urls.first(); + + d->list_urls.remove( kurl ); + +// kdDebug() << "Start KIO: " << kurl->prettyURL() << endl; + + d->list_job = TDEIO::listDir( *kurl, false ); + d->list_job->addMetaData("no-auth-prompt", "true"); + + assert( d->list_job ); + + connect( d->list_job, + TQT_SIGNAL(result(TDEIO::Job*)), + TQT_SLOT(slotIOFinished(TDEIO::Job*)) ); + + connect( d->list_job, + TQT_SIGNAL( entries( TDEIO::Job*, const TDEIO::UDSEntryList&)), + TQT_SLOT( slotEntries( TDEIO::Job*, const TDEIO::UDSEntryList&)) ); + + delete kurl; + } +} + +/////////////////////////////////////////////////// +/////////////////////////////////////////////////// + +/* + * postProcessMatch, postProcessMatches + * + * Called by KCompletion before emitting match() and matches() + * + * Append '/' to directories for file completion. This is + * done here to avoid stat()'ing a lot of files + */ +void KURLCompletion::postProcessMatch( TQString *match ) const +{ +// kdDebug() << "KURLCompletion::postProcess: " << *match << endl; + + if ( !match->isEmpty() ) { + + // Add '/' to directories in file completion mode + // unless it has already been done + if ( d->last_compl_type == CTFile ) + adjustMatch( *match ); + } +} + +void KURLCompletion::adjustMatch( TQString& match ) const +{ + if ( match.at( match.length()-1 ) != '/' ) + { + TQString copy; + + if ( match.startsWith( TQString("file:") ) ) + copy = KURL(match).path(); + else + copy = match; + + expandTilde( copy ); + expandEnv( copy ); + if ( TQDir::isRelativePath(copy) ) + copy.prepend( d->cwd + '/' ); + +// kdDebug() << "postProcess: stating " << copy << endl; + + KDE_struct_stat sbuff; + + TQCString file = TQFile::encodeName( copy ); + + if ( KDE_stat( (const char*)file, &sbuff ) == 0 ) { + if ( S_ISDIR ( sbuff.st_mode ) ) + match.append( '/' ); + } + else { + kdDebug() << "Could not stat file " << copy << endl; + } + } +} + +void KURLCompletion::postProcessMatches( TQStringList * matches ) const +{ + if ( !matches->isEmpty() && d->last_compl_type == CTFile ) { + TQStringList::Iterator it = matches->begin(); + for (; it != matches->end(); ++it ) { + adjustMatch( (*it) ); + } + } +} + +void KURLCompletion::postProcessMatches( KCompletionMatches * matches ) const +{ + if ( !matches->isEmpty() && d->last_compl_type == CTFile ) { + KCompletionMatches::Iterator it = matches->begin(); + for (; it != matches->end(); ++it ) { + adjustMatch( (*it).value() ); + } + } +} + +void KURLCompletion::customEvent(TQCustomEvent *e) +{ + if ( e->type() == CompletionMatchEvent::uniqueType() ) { + + CompletionMatchEvent *event = static_cast<CompletionMatchEvent *>( e ); + + event->completionThread()->wait(); + + if ( !isListedURL( CTUser ) ) { + stop(); + clear(); + addMatches( event->completionThread()->matches() ); + } + + setListedURL( CTUser ); + + if ( d->userListThread == event->completionThread() ) + d->userListThread = 0; + + if ( d->dirListThread == event->completionThread() ) + d->dirListThread = 0; + + delete event->completionThread(); + } +} + +// static +TQString KURLCompletion::replacedPath( const TQString& text, bool replaceHome, bool replaceEnv ) +{ + if ( text.isEmpty() ) + return text; + + MyURL url( text, TQString::null ); // no need to replace something of our current cwd + if ( !url.kurl()->isLocalFile() ) + return text; + + url.filter( replaceHome, replaceEnv ); + return url.dir() + url.file(); +} + + +TQString KURLCompletion::replacedPath( const TQString& text ) +{ + return replacedPath( text, d->replace_home, d->replace_env ); +} + +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// +// Static functions + +/* + * expandEnv + * + * Expand environment variables in text. Escaped '$' are ignored. + * Return true if expansion was made. + */ +static bool expandEnv( TQString &text ) +{ + // Find all environment variables beginning with '$' + // + int pos = 0; + + bool expanded = false; + + while ( (pos = text.find('$', pos)) != -1 ) { + + // Skip escaped '$' + // + if ( text[pos-1] == '\\' ) { + pos++; + } + // Variable found => expand + // + else { + // Find the end of the variable = next '/' or ' ' + // + int pos2 = text.find( ' ', pos+1 ); + int pos_tmp = text.find( '/', pos+1 ); + + if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) ) + pos2 = pos_tmp; + + if ( pos2 == -1 ) + pos2 = text.length(); + + // Replace if the variable is terminated by '/' or ' ' + // and defined + // + if ( pos2 >= 0 ) { + int len = pos2 - pos; + TQString key = text.mid( pos+1, len-1); + TQString value = + TQString::fromLocal8Bit( ::getenv(key.local8Bit()) ); + + if ( !value.isEmpty() ) { + expanded = true; + text.replace( pos, len, value ); + pos = pos + value.length(); + } + else { + pos = pos2; + } + } + } + } + + return expanded; +} + +/* + * expandTilde + * + * Replace "~user" with the users home directory + * Return true if expansion was made. + */ +static bool expandTilde(TQString &text) +{ + if ( text[0] != '~' ) + return false; + + bool expanded = false; + + // Find the end of the user name = next '/' or ' ' + // + int pos2 = text.find( ' ', 1 ); + int pos_tmp = text.find( '/', 1 ); + + if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) ) + pos2 = pos_tmp; + + if ( pos2 == -1 ) + pos2 = text.length(); + + // Replace ~user if the user name is terminated by '/' or ' ' + // + if ( pos2 >= 0 ) { + + TQString user = text.mid( 1, pos2-1 ); + TQString dir; + + // A single ~ is replaced with $HOME + // + if ( user.isEmpty() ) { + dir = TQDir::homeDirPath(); + } + // ~user is replaced with the dir from passwd + // + else { + struct passwd *pw = ::getpwnam( user.local8Bit() ); + + if ( pw ) + dir = TQFile::decodeName( pw->pw_dir ); + + ::endpwent(); + } + + if ( !dir.isEmpty() ) { + expanded = true; + text.replace(0, pos2, dir); + } + } + + return expanded; +} + +/* + * unescape + * + * Remove escapes and return the result in a new string + * + */ +static TQString unescape(const TQString &text) +{ + TQString result; + + for (uint pos = 0; pos < text.length(); pos++) + if ( text[pos] != '\\' ) + result.insert( result.length(), text[pos] ); + + return result; +} + +void KURLCompletion::virtual_hook( int id, void* data ) +{ KCompletion::virtual_hook( id, data ); } + +#include "kurlcompletion.moc" + |