summaryrefslogtreecommitdiffstats
path: root/tdeio/tdeio/kurlcompletion.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tdeio/tdeio/kurlcompletion.cpp')
-rw-r--r--tdeio/tdeio/kurlcompletion.cpp1604
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"
+