diff --git a/addons/project/kateprojectindex.cpp b/addons/project/kateprojectindex.cpp index 436a8c333..c3241af07 100644 --- a/addons/project/kateprojectindex.cpp +++ b/addons/project/kateprojectindex.cpp @@ -1,205 +1,205 @@ /* This file is part of the Kate project. * * Copyright (C) 2012 Christoph Cullmann * * 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 "kateprojectindex.h" #include #include /** * include ctags reading */ #include "ctags/readtags.c" KateProjectIndex::KateProjectIndex(const QStringList &files, const QVariantMap &ctagsMap) : m_ctagsIndexFile(QDir::tempPath() + QStringLiteral("/kate.project.ctags")) , m_ctagsIndexHandle(nullptr) { /** * load ctags */ loadCtags(files, ctagsMap); } KateProjectIndex::~KateProjectIndex() { /** * delete ctags handle if any */ if (m_ctagsIndexHandle) { tagsClose(m_ctagsIndexHandle); m_ctagsIndexHandle = nullptr; } } void KateProjectIndex::loadCtags(const QStringList &files, const QVariantMap &ctagsMap) { /** * create temporary file * if not possible, fail */ if (!m_ctagsIndexFile.open()) { return; } /** * close file again, other process will use it */ m_ctagsIndexFile.close(); /** * try to run ctags for all files in this project * output to our ctags index file */ QProcess ctags; QStringList args; args << QStringLiteral("-L") << QStringLiteral("-") << QStringLiteral("-f") << m_ctagsIndexFile.fileName() << QStringLiteral("--fields=+K+n"); const QString keyOptions = QStringLiteral("options"); for (const QVariant &optVariant : ctagsMap[keyOptions].toList()) { args << optVariant.toString(); } ctags.start(QStringLiteral("ctags"), args); if (!ctags.waitForStarted()) { return; } /** * write files list and close write channel */ ctags.write(files.join(QStringLiteral("\n")).toLocal8Bit()); ctags.closeWriteChannel(); /** * wait for done */ - if (!ctags.waitForFinished()) { + if (!ctags.waitForFinished(-1)) { return; } /** * file not openable, bad */ if (!m_ctagsIndexFile.open()) { return; } /** * get size */ qint64 size = m_ctagsIndexFile.size(); /** * close again */ m_ctagsIndexFile.close(); /** * empty file, bad */ if (!size) { return; } /** * try to open ctags file */ tagFileInfo info; memset(&info, 0, sizeof(tagFileInfo)); m_ctagsIndexHandle = tagsOpen(m_ctagsIndexFile.fileName().toLocal8Bit().constData(), &info); } void KateProjectIndex::findMatches(QStandardItemModel &model, const QString &searchWord, MatchType type) { /** * abort if no ctags index */ if (!m_ctagsIndexHandle) { return; } /** * word to complete * abort if empty */ QByteArray word = searchWord.toLocal8Bit(); if (word.isEmpty()) { return; } /** * try to search entry * fail if none found */ tagEntry entry; if (tagsFind(m_ctagsIndexHandle, &entry, word.constData(), TAG_PARTIALMATCH | TAG_OBSERVECASE) != TagSuccess) { return; } /** * set to show words only once for completion matches */ QSet guard; /** * loop over all found tags * first one is filled by above find, others by find next */ do { /** * skip if no name */ if (!entry.name) { continue; } /** * get name */ QString name(QString::fromLocal8Bit(entry.name)); /** * construct right items */ switch (type) { case CompletionMatches: /** * add new completion item, if new name */ if (!guard.contains(name)) { model.appendRow(new QStandardItem(name)); guard.insert(name); } break; case FindMatches: /** * add new find item, contains of multiple columns */ QList items; items << new QStandardItem(name); items << new QStandardItem(entry.kind ? QString::fromLocal8Bit(entry.kind) : QString()); items << new QStandardItem(entry.file ? QString::fromLocal8Bit(entry.file) : QString()); items << new QStandardItem(QString::number(entry.address.lineNumber)); model.appendRow(items); break; } } while (tagsFindNext(m_ctagsIndexHandle, &entry) == TagSuccess); } diff --git a/addons/project/kateprojecttreeviewcontextmenu.cpp b/addons/project/kateprojecttreeviewcontextmenu.cpp index a213be950..0bf652014 100644 --- a/addons/project/kateprojecttreeviewcontextmenu.cpp +++ b/addons/project/kateprojecttreeviewcontextmenu.cpp @@ -1,132 +1,132 @@ /* This file is part of the Kate project. * * Copyright (C) 2013 Dominik Haumann * * 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 "kateprojecttreeviewcontextmenu.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KateProjectTreeViewContextMenu::KateProjectTreeViewContextMenu() { } KateProjectTreeViewContextMenu::~KateProjectTreeViewContextMenu() { } static bool isGit(const QString &filename) { QFileInfo fi(filename); QDir dir(fi.absoluteDir()); QProcess git; git.setWorkingDirectory(dir.absolutePath()); QStringList args; args << QStringLiteral("ls-files") << fi.fileName(); git.start(QStringLiteral("git"), args); bool isGit = false; - if (git.waitForStarted() && git.waitForFinished()) { + if (git.waitForStarted() && git.waitForFinished(-1)) { QStringList files = QString::fromLocal8Bit(git.readAllStandardOutput()).split(QRegExp(QStringLiteral("[\n\r]")), QString::SkipEmptyParts); isGit = files.contains(fi.fileName()); } return isGit; } void KateProjectTreeViewContextMenu::exec(const QString &filename, const QPoint &pos, QWidget *parent) { /** * create context menu */ QMenu menu; QAction *copyAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Filename")); /** * handle "open with" * find correct mimetype to query for possible applications */ QMenu *openWithMenu = menu.addMenu(i18n("Open With")); QMimeType mimeType = QMimeDatabase().mimeTypeForFile(filename); KService::List offers = KMimeTypeTrader::self()->query(mimeType.name(), QStringLiteral("Application")); /** * for each one, insert a menu item... */ for (KService::List::Iterator it = offers.begin(); it != offers.end(); ++it) { KService::Ptr service = *it; if (service->name() == QStringLiteral("Kate")) { continue; // omit Kate } QAction *action = openWithMenu->addAction(QIcon::fromTheme(service->icon()), service->name()); action->setData(service->entryPath()); } /** * perhaps disable menu, if no entries! */ openWithMenu->setEnabled(!openWithMenu->isEmpty()); KMoreToolsMenuFactory menuFactory(QLatin1String("kate/addons/project/git-tools")); QMenu gitMenu; // must live as long as the maybe filled menu items should live if (isGit(filename)) { menuFactory.fillMenuFromGroupingNames(&gitMenu, { QLatin1String("git-clients-and-actions") }, QUrl::fromLocalFile(filename)); menu.addSection(i18n("Git:")); Q_FOREACH(auto action, gitMenu.actions()) { menu.addAction(action); } } /** * run menu and handle the triggered action */ if (QAction *action = menu.exec(pos)) { // handle apps if (copyAction == action) { QApplication::clipboard()->setText(filename); } else { // handle "open with" const QString openWith = action->data().toString(); if (KService::Ptr app = KService::serviceByDesktopPath(openWith)) { QList list; list << QUrl::fromLocalFile(filename); KRun::runService(*app, list, parent); } } } } diff --git a/addons/project/kateprojectworker.cpp b/addons/project/kateprojectworker.cpp index 2ddbe728c..8bc1febed 100644 --- a/addons/project/kateprojectworker.cpp +++ b/addons/project/kateprojectworker.cpp @@ -1,507 +1,507 @@ /* This file is part of the Kate project. * * Copyright (C) 2012 Christoph Cullmann * * 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 "kateprojectworker.h" #include "kateproject.h" #include #include #include #include #include #include #include #include #include KateProjectWorker::KateProjectWorker(const QString &baseDir, const QVariantMap &projectMap) : QObject() , ThreadWeaver::Job() , m_baseDir(baseDir) , m_projectMap(projectMap) { Q_ASSERT(!m_baseDir.isEmpty()); } void KateProjectWorker::run(ThreadWeaver::JobPointer, ThreadWeaver::Thread *) { /** * Create dummy top level parent item and empty map inside shared pointers * then load the project recursively */ KateProjectSharedQStandardItem topLevel(new QStandardItem()); KateProjectSharedQMapStringItem file2Item(new QMap ()); loadProject(topLevel.data(), m_projectMap, file2Item.data()); /** * create some local backup of some data we need for further processing! */ QStringList files = file2Item->keys(); emit loadDone(topLevel, file2Item); /** * load index */ loadIndex(files); } void KateProjectWorker::loadProject(QStandardItem *parent, const QVariantMap &project, QMap *file2Item) { /** * recurse to sub-projects FIRST */ QVariantList subGroups = project[QStringLiteral("projects")].toList(); for (const QVariant &subGroupVariant: subGroups) { /** * convert to map and get name, else skip */ QVariantMap subProject = subGroupVariant.toMap(); const QString keyName = QStringLiteral("name"); if (subProject[keyName].toString().isEmpty()) { continue; } /** * recurse */ QStandardItem *subProjectItem = new KateProjectItem(KateProjectItem::Project, subProject[keyName].toString()); loadProject(subProjectItem, subProject, file2Item); parent->appendRow(subProjectItem); } /** * load all specified files */ const QString keyFiles = QStringLiteral("files"); QVariantList files = project[keyFiles].toList(); for (const QVariant &fileVariant : files) { loadFilesEntry(parent, fileVariant.toMap(), file2Item); } } /** * small helper to construct directory parent items * @param dir2Item map for path => item * @param path current path we need item for * @return correct parent item for given path, will reuse existing ones */ static QStandardItem *directoryParent(QMap &dir2Item, QString path) { /** * throw away simple / */ if (path == QStringLiteral("/")) { path = QString(); } /** * quick check: dir already seen? */ if (dir2Item.contains(path)) { return dir2Item[path]; } /** * else: construct recursively */ int slashIndex = path.lastIndexOf(QLatin1Char('/')); /** * no slash? * simple, no recursion, append new item toplevel */ if (slashIndex < 0) { dir2Item[path] = new KateProjectItem(KateProjectItem::Directory, path); dir2Item[QString()]->appendRow(dir2Item[path]); return dir2Item[path]; } /** * else, split and recurse */ const QString leftPart = path.left(slashIndex); const QString rightPart = path.right(path.size() - (slashIndex + 1)); /** * special handling if / with nothing on one side are found */ if (leftPart.isEmpty() || rightPart.isEmpty()) { return directoryParent(dir2Item, leftPart.isEmpty() ? rightPart : leftPart); } /** * else: recurse on left side */ dir2Item[path] = new KateProjectItem(KateProjectItem::Directory, rightPart); directoryParent(dir2Item, leftPart)->appendRow(dir2Item[path]); return dir2Item[path]; } void KateProjectWorker::loadFilesEntry(QStandardItem *parent, const QVariantMap &filesEntry, QMap *file2Item) { QDir dir(m_baseDir); if (!dir.cd(filesEntry[QStringLiteral("directory")].toString())) { return; } QStringList files = findFiles(dir, filesEntry); if (files.isEmpty()) { return; } files.sort(); /** * construct paths first in tree and items in a map */ QMap dir2Item; dir2Item[QString()] = parent; QList > item2ParentPath; for (const QString &filePath : files) { /** * skip dupes */ if (file2Item->contains(filePath)) { continue; } /** * get file info and skip NON-files */ QFileInfo fileInfo(filePath); if (!fileInfo.isFile()) { continue; } /** * construct the item with right directory prefix * already hang in directories in tree */ KateProjectItem *fileItem = new KateProjectItem(KateProjectItem::File, fileInfo.fileName()); fileItem->setData(filePath, Qt::ToolTipRole); // get the directory's relative path to the base directory QString dirRelPath = dir.relativeFilePath(fileInfo.absolutePath()); // if the relative path is ".", clean it up if (dirRelPath == QStringLiteral(".")) { dirRelPath = QString(); } item2ParentPath.append(QPair(fileItem, directoryParent(dir2Item, dirRelPath))); fileItem->setData(filePath, Qt::UserRole); (*file2Item)[filePath] = fileItem; } /** * plug in the file items to the tree */ auto i = item2ParentPath.constBegin(); while (i != item2ParentPath.constEnd()) { i->second->appendRow(i->first); ++i; } } QStringList KateProjectWorker::findFiles(const QDir &dir, const QVariantMap& filesEntry) { const bool recursive = !filesEntry.contains(QStringLiteral("recursive")) || filesEntry[QStringLiteral("recursive")].toBool(); if (filesEntry[QStringLiteral("git")].toBool()) { return filesFromGit(dir, recursive); } else if (filesEntry[QStringLiteral("hg")].toBool()) { return filesFromMercurial(dir, recursive); } else if (filesEntry[QStringLiteral("svn")].toBool()) { return filesFromSubversion(dir, recursive); } else if (filesEntry[QStringLiteral("darcs")].toBool()) { return filesFromDarcs(dir, recursive); } else { QStringList files = filesEntry[QStringLiteral("list")].toStringList(); if (files.empty()) { QStringList filters = filesEntry[QStringLiteral("filters")].toStringList(); files = filesFromDirectory(dir, recursive, filters); } return files; } } QStringList KateProjectWorker::filesFromGit(const QDir &dir, bool recursive) { QStringList relFiles = gitLsFiles(dir); relFiles << gitSubmodulesFiles(dir); QStringList files; for (const QString &relFile : relFiles) { if (!recursive && (relFile.indexOf(QStringLiteral("/")) != -1)) { continue; } files.append(dir.absolutePath() + QLatin1Char('/') + relFile); } return files; } QStringList KateProjectWorker::gitLsFiles(const QDir &dir) { QStringList files; // git ls-files -z results a bytearray where each entry is \0-terminated. // NOTE: Without -z, Umlauts such as "Der Bäcker/Das Brötchen.txt" do not work (#389415) QStringList args; args << QStringLiteral("ls-files") << QStringLiteral("-z") << QStringLiteral("."); QProcess git; git.setWorkingDirectory(dir.absolutePath()); git.start(QStringLiteral("git"), args); - if (!git.waitForStarted() || !git.waitForFinished()) { + if (!git.waitForStarted() || !git.waitForFinished(-1)) { return files; } const QList byteArrayList = git.readAllStandardOutput().split('\0'); for (const QByteArray & byteArray : byteArrayList) { files << QString::fromUtf8(byteArray); } return files; } QStringList KateProjectWorker::gitSubmodulesFiles(const QDir &dir) { /** * git submodule command gives little to use for reliable file listing * so reading the .gitmodule file directly. After the module paths are found * just treat the new repositories as the main one. */ QStringList files; QString modulesPath = dir.filePath(QStringLiteral(".gitmodules")); if (!QFile::exists(modulesPath)) { return files; } QSettings config(modulesPath, QSettings::IniFormat); for (const QString &module: config.childGroups()) { QString path = config.value(module + QStringLiteral("/path")).toString(); QDir moduleDir = dir.filePath(path); QStringList relFiles = gitLsFiles(moduleDir); for (const QString &file: relFiles) { files << path + QLatin1Char('/') + file; } } return files; } QStringList KateProjectWorker::filesFromMercurial(const QDir &dir, bool recursive) { QStringList files; QProcess hg; hg.setWorkingDirectory(dir.absolutePath()); QStringList args; args << QStringLiteral("manifest") << QStringLiteral("."); hg.start(QStringLiteral("hg"), args); - if (!hg.waitForStarted() || !hg.waitForFinished()) { + if (!hg.waitForStarted() || !hg.waitForFinished(-1)) { return files; } const QStringList relFiles = QString::fromLocal8Bit(hg.readAllStandardOutput()).split(QRegExp(QStringLiteral("[\n\r]")), QString::SkipEmptyParts); for (const QString &relFile : relFiles) { if (!recursive && (relFile.indexOf(QStringLiteral("/")) != -1)) { continue; } files.append(dir.absolutePath() + QLatin1Char('/') + relFile); } return files; } QStringList KateProjectWorker::filesFromSubversion(const QDir &dir, bool recursive) { QStringList files; QProcess svn; svn.setWorkingDirectory(dir.absolutePath()); QStringList args; args << QStringLiteral("status") << QStringLiteral("--verbose") << QStringLiteral("."); if (recursive) { args << QStringLiteral("--depth=infinity"); } else { args << QStringLiteral("--depth=files"); } svn.start(QStringLiteral("svn"), args); - if (!svn.waitForStarted() || !svn.waitForFinished()) { + if (!svn.waitForStarted() || !svn.waitForFinished(-1)) { return files; } /** * get output and split up into lines */ const QStringList lines = QString::fromLocal8Bit(svn.readAllStandardOutput()).split(QRegExp(QStringLiteral("[\n\r]")), QString::SkipEmptyParts); /** * remove start of line that is no filename, sort out unknown and ignore */ bool first = true; int prefixLength = -1; for (const QString &line : lines) { /** * get length of stuff to cut */ if (first) { /** * try to find ., else fail */ prefixLength = line.lastIndexOf(QStringLiteral(".")); if (prefixLength < 0) { break; } /** * skip first */ first = false; continue; } /** * get file, if not unknown or ignored * prepend directory path */ if ((line.size() > prefixLength) && line[0] != QLatin1Char('?') && line[0] != QLatin1Char('I')) { files.append(dir.absolutePath() + QLatin1Char('/') + line.right(line.size() - prefixLength)); } } return files; } QStringList KateProjectWorker::filesFromDarcs(const QDir &dir, bool recursive) { QStringList files; const QString cmd = QStringLiteral("darcs"); QString root; { QProcess darcs; darcs.setWorkingDirectory(dir.absolutePath()); QStringList args; args << QStringLiteral("list") << QStringLiteral("repo"); darcs.start(cmd, args); - if (!darcs.waitForStarted() || !darcs.waitForFinished()) + if (!darcs.waitForStarted() || !darcs.waitForFinished(-1)) return files; auto str = QString::fromLocal8Bit(darcs.readAllStandardOutput()); QRegularExpression exp(QStringLiteral("Root: ([^\\n\\r]*)")); auto match = exp.match(str); if(!match.hasMatch()) return files; root = match.captured(1); } QStringList relFiles; { QProcess darcs; QStringList args; darcs.setWorkingDirectory(dir.absolutePath()); args << QStringLiteral("list") << QStringLiteral("files") << QStringLiteral("--no-directories") << QStringLiteral("--pending"); darcs.start(cmd, args); - if(!darcs.waitForStarted() || !darcs.waitForFinished()) + if(!darcs.waitForStarted() || !darcs.waitForFinished(-1)) return files; relFiles = QString::fromLocal8Bit(darcs.readAllStandardOutput()) .split(QRegularExpression(QStringLiteral("[\n\r]")), QString::SkipEmptyParts); } for (const QString &relFile: relFiles) { const QString path = dir.relativeFilePath(root + QStringLiteral("/") + relFile); if ((!recursive && (relFile.indexOf(QStringLiteral("/")) != -1)) || (recursive && (relFile.indexOf(QStringLiteral("..")) == 0)) ) { continue; } files.append(dir.absoluteFilePath(path)); } return files; } QStringList KateProjectWorker::filesFromDirectory(const QDir &_dir, bool recursive, const QStringList &filters) { QStringList files; QDir dir(_dir); dir.setFilter(QDir::Files); if (!filters.isEmpty()) { dir.setNameFilters(filters); } /** * construct flags for iterator */ QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags; if (recursive) { flags = flags | QDirIterator::Subdirectories; } /** * create iterator and collect all files */ QDirIterator dirIterator(dir, flags); while (dirIterator.hasNext()) { dirIterator.next(); files.append(dirIterator.filePath()); } return files; } void KateProjectWorker::loadIndex(const QStringList &files) { /** * create new index, this will do the loading in the constructor * wrap it into shared pointer for transfer to main thread */ const QString keyCtags = QStringLiteral("ctags"); KateProjectSharedProjectIndex index(new KateProjectIndex(files, m_projectMap[keyCtags].toMap())); emit loadIndexDone(index); }