diff --git a/addons/project/kateprojectplugin.cpp b/addons/project/kateprojectplugin.cpp index dc814ea85..53b6f4607 100644 --- a/addons/project/kateprojectplugin.cpp +++ b/addons/project/kateprojectplugin.cpp @@ -1,360 +1,421 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 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 "kateprojectplugin.h" #include "kateproject.h" #include "kateprojectconfigpage.h" #include "kateprojectpluginview.h" #include #include #include +#include // delete, when we depend on KF 5.63 +#include #include #include #include #include #include #include #ifdef HAVE_CTERMID #include #include #include #include #include #endif namespace { const QString ProjectFileName = QStringLiteral(".kateproject"); const QString GitFolderName = QStringLiteral(".git"); const QString SubversionFolderName = QStringLiteral(".svn"); const QString MercurialFolderName = QStringLiteral(".hg"); const QString GitConfig = QStringLiteral("git"); const QString SubversionConfig = QStringLiteral("subversion"); const QString MercurialConfig = QStringLiteral("mercurial"); const QStringList DefaultConfig = QStringList() << GitConfig << SubversionConfig << MercurialConfig; } KateProjectPlugin::KateProjectPlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) , m_completion(this) , m_autoGit(true) , m_autoSubversion(true) , m_autoMercurial(true) , m_weaver(new ThreadWeaver::Queue(this)) { qRegisterMetaType("KateProjectSharedQStandardItem"); qRegisterMetaType("KateProjectSharedQMapStringItem"); qRegisterMetaType("KateProjectSharedProjectIndex"); connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentCreated, this, &KateProjectPlugin::slotDocumentCreated); connect(&m_fileWatcher, &QFileSystemWatcher::directoryChanged, this, &KateProjectPlugin::slotDirectoryChanged); #ifdef HAVE_CTERMID /** * open project for our current working directory, if this kate has a terminal * http://stackoverflow.com/questions/1312922/detect-if-stdin-is-a-terminal-or-pipe-in-c-c-qt */ char tty[L_ctermid + 1] = {0}; ctermid(tty); int fd = ::open(tty, O_RDONLY); if (fd >= 0) { projectForDir(QDir::current()); ::close(fd); } #endif readConfig(); for (auto document : KTextEditor::Editor::instance()->application()->documents()) { slotDocumentCreated(document); } + + registerVariables(); } KateProjectPlugin::~KateProjectPlugin() { + unregisterVariables(); + for (KateProject *project : m_projects) { m_fileWatcher.removePath(QFileInfo(project->fileName()).canonicalPath()); delete project; } m_projects.clear(); m_weaver->shutDown(); delete m_weaver; } QObject *KateProjectPlugin::createView(KTextEditor::MainWindow *mainWindow) { return new KateProjectPluginView(this, mainWindow); } int KateProjectPlugin::configPages() const { return 1; } KTextEditor::ConfigPage *KateProjectPlugin::configPage(int number, QWidget *parent) { if (number != 0) { return nullptr; } return new KateProjectConfigPage(parent, this); } KateProject *KateProjectPlugin::createProjectForFileName(const QString &fileName) { KateProject *project = new KateProject(m_weaver); if (!project->loadFromFile(fileName)) { delete project; return nullptr; } m_projects.append(project); m_fileWatcher.addPath(QFileInfo(fileName).canonicalPath()); emit projectCreated(project); return project; } KateProject *KateProjectPlugin::projectForDir(QDir dir) { /** * search project file upwards * with recursion guard * do this first for all level and only after this fails try to invent projects * otherwise one e.g. invents projects for .kateproject tree structures with sub .git clones */ QSet seenDirectories; std::vector directoryStack; while (!seenDirectories.contains(dir.absolutePath())) { // update guard seenDirectories.insert(dir.absolutePath()); // remember directory for later project creation based on other criteria directoryStack.push_back(dir.absolutePath()); // check for project and load it if found const QString canonicalPath = dir.canonicalPath(); const QString canonicalFileName = dir.filePath(ProjectFileName); for (KateProject *project : m_projects) { if (project->baseDir() == canonicalPath || project->fileName() == canonicalFileName) { return project; } } // project file found => done if (dir.exists(ProjectFileName)) { return createProjectForFileName(canonicalFileName); } // else: cd up, if possible or abort if (!dir.cdUp()) { break; } } /** * if we arrive here, we found no .kateproject * => we want to invent a project based on e.g. version control system info */ for (const QString &dir : directoryStack) { // try to invent project based on version control stuff KateProject *project = nullptr; if ((project = detectGit(dir)) || (project = detectSubversion(dir)) || (project = detectMercurial(dir))) { return project; } } // no project found, bad luck return nullptr; } KateProject *KateProjectPlugin::projectForUrl(const QUrl &url) { if (url.isEmpty() || !url.isLocalFile()) { return nullptr; } return projectForDir(QFileInfo(url.toLocalFile()).absoluteDir()); } void KateProjectPlugin::slotDocumentCreated(KTextEditor::Document *document) { connect(document, &KTextEditor::Document::documentUrlChanged, this, &KateProjectPlugin::slotDocumentUrlChanged); connect(document, &KTextEditor::Document::destroyed, this, &KateProjectPlugin::slotDocumentDestroyed); slotDocumentUrlChanged(document); } void KateProjectPlugin::slotDocumentDestroyed(QObject *document) { if (KateProject *project = m_document2Project.value(document)) { project->unregisterDocument(static_cast(document)); } m_document2Project.remove(document); } void KateProjectPlugin::slotDocumentUrlChanged(KTextEditor::Document *document) { KateProject *project = projectForUrl(document->url()); if (KateProject *project = m_document2Project.value(document)) { project->unregisterDocument(document); } if (!project) { m_document2Project.remove(document); } else { m_document2Project[document] = project; } if (KateProject *project = m_document2Project.value(document)) { project->registerDocument(document); } } void KateProjectPlugin::slotDirectoryChanged(const QString &path) { QString fileName = QDir(path).filePath(ProjectFileName); for (KateProject *project : m_projects) { if (project->fileName() == fileName) { QDateTime lastModified = QFileInfo(fileName).lastModified(); if (project->fileLastModified().isNull() || (lastModified > project->fileLastModified())) { project->reload(); } break; } } } KateProject *KateProjectPlugin::detectGit(const QDir &dir) { // allow .git as dir and file (file for git worktree stuff, https://git-scm.com/docs/git-worktree) if (m_autoGit && dir.exists(GitFolderName)) { return createProjectForRepository(QStringLiteral("git"), dir); } return nullptr; } KateProject *KateProjectPlugin::detectSubversion(const QDir &dir) { if (m_autoSubversion && dir.exists(SubversionFolderName) && QFileInfo(dir, SubversionFolderName).isDir()) { return createProjectForRepository(QStringLiteral("svn"), dir); } return nullptr; } KateProject *KateProjectPlugin::detectMercurial(const QDir &dir) { if (m_autoMercurial && dir.exists(MercurialFolderName) && QFileInfo(dir, MercurialFolderName).isDir()) { return createProjectForRepository(QStringLiteral("hg"), dir); } return nullptr; } KateProject *KateProjectPlugin::createProjectForRepository(const QString &type, const QDir &dir) { QVariantMap cnf, files; files[type] = 1; cnf[QStringLiteral("name")] = dir.dirName(); cnf[QStringLiteral("files")] = (QVariantList() << files); KateProject *project = new KateProject(m_weaver); project->loadFromData(cnf, dir.canonicalPath()); m_projects.append(project); emit projectCreated(project); return project; } void KateProjectPlugin::setAutoRepository(bool onGit, bool onSubversion, bool onMercurial) { m_autoGit = onGit; m_autoSubversion = onSubversion; m_autoMercurial = onMercurial; writeConfig(); } bool KateProjectPlugin::autoGit() const { return m_autoGit; } bool KateProjectPlugin::autoSubversion() const { return m_autoSubversion; } bool KateProjectPlugin::autoMercurial() const { return m_autoMercurial; } void KateProjectPlugin::readConfig() { KConfigGroup config(KSharedConfig::openConfig(), "project"); QStringList autorepository = config.readEntry("autorepository", DefaultConfig); m_autoGit = m_autoSubversion = m_autoMercurial = false; if (autorepository.contains(GitConfig)) { m_autoGit = true; } if (autorepository.contains(SubversionConfig)) { m_autoSubversion = true; } if (autorepository.contains(MercurialConfig)) { m_autoMercurial = true; } } void KateProjectPlugin::writeConfig() { KConfigGroup config(KSharedConfig::openConfig(), "project"); QStringList repos; if (m_autoGit) { repos << GitConfig; } if (m_autoSubversion) { repos << SubversionConfig; } if (m_autoMercurial) { repos << MercurialConfig; } config.writeEntry("autorepository", repos); } + +#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 63, 0) +static KateProjectPlugin *findProjectPlugin() +{ + auto plugin = KTextEditor::Editor::instance()->application()->plugin(QStringLiteral("kateprojectplugin")); + return qobject_cast(plugin); +} +#endif + +void KateProjectPlugin::registerVariables() +{ +#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 63, 0) + auto editor = KTextEditor::Editor::instance(); + editor->registerVariableMatch(QStringLiteral("Project:Path"), i18n("Full path to current project excluding the file name."), + [](const QStringView &, KTextEditor::View *view) { + if (!view) { + return QString(); + } + auto projectPlugin = findProjectPlugin(); + if (!projectPlugin) { + return QString(); + } + auto kateProject = findProjectPlugin()->projectForUrl(view->document()->url()); + if (!kateProject) { + return QString(); + } + return QDir(kateProject->baseDir()).absolutePath(); + }); + + editor->registerVariableMatch(QStringLiteral("Project:NativePath"), i18n("Full path to current project excluding the file name, with native path separator (backslash on Windows)."), + [](const QStringView &, KTextEditor::View *view) { + if (!view) { + return QString(); + } + auto projectPlugin = findProjectPlugin(); + if (!projectPlugin) { + return QString(); + } + auto kateProject = findProjectPlugin()->projectForUrl(view->document()->url()); + if (!kateProject) { + return QString(); + } + return QDir::toNativeSeparators(QDir(kateProject->baseDir()).absolutePath()); + }); +#endif +} + +void KateProjectPlugin::unregisterVariables() +{ +#if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5, 63, 0) + auto editor = KTextEditor::Editor::instance(); + editor->unregisterVariableMatch(QStringLiteral("Project:Path")); + editor->unregisterVariableMatch(QStringLiteral("Project:NativePath")); +#endif +} diff --git a/addons/project/kateprojectplugin.h b/addons/project/kateprojectplugin.h index d69f2feb9..8a00cbe78 100644 --- a/addons/project/kateprojectplugin.h +++ b/addons/project/kateprojectplugin.h @@ -1,183 +1,186 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 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. */ #ifndef _KATE_PROJECT_PLUGIN_H_ #define _KATE_PROJECT_PLUGIN_H_ #include #include #include #include #include #include #include "kateproject.h" #include "kateprojectcompletion.h" namespace ThreadWeaver { class Queue; } class KateProjectPlugin : public KTextEditor::Plugin { Q_OBJECT public: explicit KateProjectPlugin(QObject *parent = nullptr, const QList & = QList()); ~KateProjectPlugin() override; QObject *createView(KTextEditor::MainWindow *mainWindow) override; int configPages() const override; KTextEditor::ConfigPage *configPage(int number = 0, QWidget *parent = nullptr) override; /** * Create new project for given project filename. * Null pointer if no project can be opened. * File name will be canonicalized! * @param fileName canonicalized file name for the project * @return project or null if not openable */ KateProject *createProjectForFileName(const QString &fileName); /** * Search and open project for given dir, if possible. * Will search upwards for .kateproject file. * Will use internally projectForFileName if project file is found. * @param dir dir to search matching project for * @return project or null if not openable */ KateProject *projectForDir(QDir dir); /** * Search and open project that contains given url, if possible. * Will search upwards for .kateproject file, if the url is a local file. * Will use internally projectForDir. * @param url url to search matching project for * @return project or null if not openable */ KateProject *projectForUrl(const QUrl &url); /** * get list of all current open projects * @return list of all open projects */ QList projects() const { return m_projects; } /** * Get global code completion. * @return global completion object for KTextEditor::View */ KateProjectCompletion *completion() { return &m_completion; } /** * Map current open documents to projects. * @param document document we want to know which project it belongs to * @return project or 0 if none found for this document */ KateProject *projectForDocument(KTextEditor::Document *document) { return m_document2Project.value(document); } void setAutoRepository(bool onGit, bool onSubversion, bool onMercurial); bool autoGit() const; bool autoSubversion() const; bool autoMercurial() const; Q_SIGNALS: /** * Signal that a new project got created. * @param project new created project */ void projectCreated(KateProject *project); public Q_SLOTS: /** * New document got created, we need to update our connections * @param document new created document */ void slotDocumentCreated(KTextEditor::Document *document); /** * Document got destroyed. * @param document deleted document */ void slotDocumentDestroyed(QObject *document); /** * Url changed, to auto-load projects */ void slotDocumentUrlChanged(KTextEditor::Document *document); /** * did some project file change? * @param path name of directory that did change */ void slotDirectoryChanged(const QString &path); private: KateProject *createProjectForRepository(const QString &type, const QDir &dir); KateProject *detectGit(const QDir &dir); KateProject *detectSubversion(const QDir &dir); KateProject *detectMercurial(const QDir &dir); void readConfig(); void writeConfig(); + void registerVariables(); + void unregisterVariables(); + private: /** * open plugins, maps project base directory => project */ QList m_projects; /** * filesystem watcher to keep track of all project files * and auto-reload */ QFileSystemWatcher m_fileWatcher; /** * Mapping document => project */ QHash m_document2Project; /** * Project completion */ KateProjectCompletion m_completion; bool m_autoGit : 1; bool m_autoSubversion : 1; bool m_autoMercurial : 1; ThreadWeaver::Queue *m_weaver; }; #endif