File indexing completed on 2024-04-28 05:49:08

0001 /*  This file is part of the Kate project.
0002  *
0003  *  SPDX-FileCopyrightText: 2010 Christoph Cullmann <cullmann@kde.org>
0004  *
0005  *  SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #include "kateprojectplugin.h"
0009 
0010 #include "kateproject.h"
0011 #include "kateprojectconfigpage.h"
0012 #include "kateprojectpluginview.h"
0013 #include "ktexteditor_utils.h"
0014 
0015 #include <kateapp.h>
0016 
0017 #include <kcoreaddons_version.h>
0018 #include <ktexteditor/application.h>
0019 #include <ktexteditor/editor.h>
0020 #include <ktexteditor/view.h>
0021 
0022 #include <KConfigGroup>
0023 #include <KLocalizedString>
0024 #include <KNetworkMounts>
0025 #include <KSharedConfig>
0026 
0027 #include <QCoreApplication>
0028 #include <QFileInfo>
0029 #include <QJsonDocument>
0030 #include <QMessageBox>
0031 #include <QString>
0032 #include <QTime>
0033 #include <QTimer>
0034 
0035 #include <vector>
0036 
0037 namespace
0038 {
0039 const QString ProjectFileName = QStringLiteral(".kateproject");
0040 const QString GitFolderName = QStringLiteral(".git");
0041 const QString SubversionFolderName = QStringLiteral(".svn");
0042 const QString MercurialFolderName = QStringLiteral(".hg");
0043 const QString FossilCheckoutFileName = QStringLiteral(".fslckout");
0044 
0045 const QString GitConfig = QStringLiteral("git");
0046 const QString SubversionConfig = QStringLiteral("subversion");
0047 const QString MercurialConfig = QStringLiteral("mercurial");
0048 const QString FossilConfig = QStringLiteral("fossil");
0049 
0050 const QStringList DefaultConfig = QStringList() << GitConfig << SubversionConfig << MercurialConfig;
0051 }
0052 
0053 KateProjectPlugin::KateProjectPlugin(QObject *parent, const QVariantList &)
0054     : KTextEditor::Plugin(parent)
0055     , m_completion(this)
0056 {
0057     qRegisterMetaType<KateProjectSharedQStandardItem>("KateProjectSharedQStandardItem");
0058     qRegisterMetaType<KateProjectSharedQHashStringItem>("KateProjectSharedQHashStringItem");
0059     qRegisterMetaType<KateProjectSharedProjectIndex>("KateProjectSharedProjectIndex");
0060 
0061     connect(KTextEditor::Editor::instance()->application(), &KTextEditor::Application::documentCreated, this, &KateProjectPlugin::slotDocumentCreated);
0062 
0063     // read configuration prior to cwd project setup below
0064     readConfig();
0065 
0066     // register all already open documents, later we keep track of all newly created ones
0067     const auto docs = KTextEditor::Editor::instance()->application()->documents();
0068     for (auto document : docs) {
0069         slotDocumentCreated(document);
0070     }
0071 
0072     // make project plugin variables known to KTextEditor::Editor
0073     registerVariables();
0074 
0075     // forward to meta-object system friendly version
0076     connect(this, &KateProjectPlugin::projectCreated, this, &KateProjectPlugin::projectAdded);
0077     connect(this, &KateProjectPlugin::pluginViewProjectClosing, this, &KateProjectPlugin::projectRemoved);
0078 }
0079 
0080 KateProjectPlugin::~KateProjectPlugin()
0081 {
0082     unregisterVariables();
0083 
0084     for (KateProject *project : qAsConst(m_projects)) {
0085         delete project;
0086     }
0087     m_projects.clear();
0088 }
0089 
0090 QObject *KateProjectPlugin::createView(KTextEditor::MainWindow *mainWindow)
0091 {
0092     return new KateProjectPluginView(this, mainWindow);
0093 }
0094 
0095 int KateProjectPlugin::configPages() const
0096 {
0097     return 1;
0098 }
0099 
0100 KTextEditor::ConfigPage *KateProjectPlugin::configPage(int number, QWidget *parent)
0101 {
0102     if (number != 0) {
0103         return nullptr;
0104     }
0105     return new KateProjectConfigPage(parent, this);
0106 }
0107 
0108 KateProject *KateProjectPlugin::createProjectForFileName(const QString &fileName)
0109 {
0110     // check if we already have the needed project opened
0111     if (auto project = openProjectForDirectory(QFileInfo(fileName).dir())) {
0112         return project;
0113     }
0114 
0115     KateProject *project = new KateProject(m_threadPool, this, fileName);
0116     if (!project->isValid()) {
0117         delete project;
0118         return nullptr;
0119     }
0120 
0121     m_projects.append(project);
0122     Q_EMIT projectCreated(project);
0123     return project;
0124 }
0125 
0126 KateProject *KateProjectPlugin::openProjectForDirectory(const QDir &dir)
0127 {
0128     // check for project and load it if found
0129     const QDir absDir(dir.absolutePath());
0130     const QString absolutePath = absDir.path();
0131     const QString projectFileName = absDir.filePath(ProjectFileName);
0132     for (KateProject *project : qAsConst(m_projects)) {
0133         if (project->baseDir() == absolutePath || project->fileName() == projectFileName) {
0134             return project;
0135         }
0136     }
0137     return nullptr;
0138 }
0139 
0140 KateProject *KateProjectPlugin::projectForDir(QDir dir, bool userSpecified)
0141 {
0142     /**
0143      * Save dir to create a project from directory if nothing works
0144      */
0145     const QDir originalDir = dir;
0146 
0147     /**
0148      * search project file upwards
0149      * with recursion guard
0150      * do this first for all level and only after this fails try to invent projects
0151      * otherwise one e.g. invents projects for .kateproject tree structures with sub .git clones
0152      */
0153     QSet<QString> seenDirectories;
0154     std::vector<QString> directoryStack;
0155     while (!seenDirectories.contains(dir.absolutePath())) {
0156         // update guard
0157         seenDirectories.insert(dir.absolutePath());
0158 
0159         // remember directory for later project creation based on other criteria
0160         directoryStack.push_back(dir.absolutePath());
0161 
0162         // check for project and load it if found
0163         if (auto project = openProjectForDirectory(dir)) {
0164             return project;
0165         }
0166 
0167         // project file found => done
0168         if (dir.exists(ProjectFileName)) {
0169             return createProjectForFileName(dir.filePath(ProjectFileName));
0170         }
0171 
0172         // else: cd up, if possible or abort
0173         if (!dir.cdUp()) {
0174             break;
0175         }
0176     }
0177 
0178     /**
0179      * if we arrive here, we found no .kateproject
0180      * => we want to invent a project based on e.g. version control system info
0181      */
0182     for (const QString &dir : directoryStack) {
0183         // try to invent project based on version control stuff
0184         KateProject *project = nullptr;
0185         if ((project = detectGit(dir)) || (project = detectSubversion(dir)) || (project = detectMercurial(dir)) || (project = detectFossil(dir))) {
0186             return project;
0187         }
0188     }
0189 
0190     /**
0191      * Version control not found? Load the directory as project
0192      */
0193     if (userSpecified) {
0194         return createProjectForDirectory(originalDir);
0195     }
0196 
0197     /**
0198      * Give up
0199      */
0200     return nullptr;
0201 }
0202 
0203 void KateProjectPlugin::closeProject(KateProject *project)
0204 {
0205     // collect all documents we have mapped to the projects we want to close
0206     // we can not delete projects that still have some mapping
0207     QList<KTextEditor::Document *> projectDocuments;
0208     for (const auto &it : m_document2Project) {
0209         if (it.second == project) {
0210             projectDocuments.append(it.first);
0211         }
0212     }
0213 
0214     // if we have some documents open for this project, ask if we want to close, else just do it
0215     if (!projectDocuments.isEmpty()) {
0216         QWidget *window = KTextEditor::Editor::instance()->application()->activeMainWindow()->window();
0217         const QString title = i18n("Confirm project closing: %1", project->name());
0218         const QString text = i18n("Do you want to close the project %1 and the related %2 open documents?", project->name(), projectDocuments.size());
0219         if (QMessageBox::Yes != QMessageBox::question(window, title, text, QMessageBox::No | QMessageBox::Yes, QMessageBox::Yes)) {
0220             return;
0221         }
0222 
0223         // best effort document closing, some might survive
0224         KTextEditor::Editor::instance()->application()->closeDocuments(projectDocuments);
0225     }
0226 
0227     // check: did all documents of the project go away? if not we shall not close it
0228     if (!projectHasOpenDocuments(project)) {
0229         Q_EMIT pluginViewProjectClosing(project);
0230         m_projects.removeOne(project);
0231         delete project;
0232     }
0233 }
0234 
0235 QList<QObject *> KateProjectPlugin::projectsObjects() const
0236 {
0237     QList<QObject *> list;
0238     for (auto &p : m_projects) {
0239         list.push_back(p);
0240     }
0241     return list;
0242 }
0243 
0244 bool KateProjectPlugin::projectHasOpenDocuments(KateProject *project) const
0245 {
0246     for (const auto &it : m_document2Project) {
0247         if (it.second == project) {
0248             return true;
0249         }
0250     }
0251     return false;
0252 }
0253 
0254 KateProject *KateProjectPlugin::projectForUrl(const QUrl &url)
0255 {
0256     if (url.isEmpty() || !url.isLocalFile()
0257         || KNetworkMounts::self()->isOptionEnabledForPath(url.toLocalFile(), KNetworkMounts::MediumSideEffectsOptimizations)) {
0258         return nullptr;
0259     }
0260 
0261     return projectForDir(QFileInfo(url.toLocalFile()).absoluteDir());
0262 }
0263 
0264 void KateProjectPlugin::slotDocumentCreated(KTextEditor::Document *document)
0265 {
0266     connect(document, &KTextEditor::Document::documentUrlChanged, this, &KateProjectPlugin::slotDocumentUrlChanged);
0267     connect(document, &KTextEditor::Document::destroyed, this, &KateProjectPlugin::slotDocumentDestroyed);
0268 
0269     slotDocumentUrlChanged(document);
0270 }
0271 
0272 void KateProjectPlugin::slotDocumentDestroyed(QObject *document)
0273 {
0274     const auto it = m_document2Project.find(static_cast<KTextEditor::Document *>(document));
0275     if (it == m_document2Project.end()) {
0276         return;
0277     }
0278 
0279     it->second->unregisterDocument(static_cast<KTextEditor::Document *>(document));
0280     m_document2Project.erase(it);
0281 }
0282 
0283 void KateProjectPlugin::slotDocumentUrlChanged(KTextEditor::Document *document)
0284 {
0285     // unregister from old mapping
0286     slotDocumentDestroyed(document);
0287 
0288     // register for new project, if any
0289     if (KateProject *project = projectForUrl(document->url())) {
0290         m_document2Project.emplace(document, project);
0291         project->registerDocument(document);
0292     }
0293 }
0294 
0295 KateProject *KateProjectPlugin::detectGit(const QDir &dir)
0296 {
0297     // allow .git as dir and file (file for git worktree stuff, https://git-scm.com/docs/git-worktree)
0298     if (m_autoGit && dir.exists(GitFolderName)) {
0299         return createProjectForRepository(QStringLiteral("git"), dir);
0300     }
0301 
0302     return nullptr;
0303 }
0304 
0305 KateProject *KateProjectPlugin::detectSubversion(const QDir &dir)
0306 {
0307     if (m_autoSubversion && dir.exists(SubversionFolderName) && QFileInfo(dir, SubversionFolderName).isDir()) {
0308         return createProjectForRepository(QStringLiteral("svn"), dir);
0309     }
0310 
0311     return nullptr;
0312 }
0313 
0314 KateProject *KateProjectPlugin::detectMercurial(const QDir &dir)
0315 {
0316     if (m_autoMercurial && dir.exists(MercurialFolderName) && QFileInfo(dir, MercurialFolderName).isDir()) {
0317         return createProjectForRepository(QStringLiteral("hg"), dir);
0318     }
0319 
0320     return nullptr;
0321 }
0322 
0323 KateProject *KateProjectPlugin::detectFossil(const QDir &dir)
0324 {
0325     if (m_autoFossil && dir.exists(FossilCheckoutFileName) && QFileInfo(dir, FossilCheckoutFileName).isReadable()) {
0326         return createProjectForRepository(QStringLiteral("fossil"), dir);
0327     }
0328 
0329     return nullptr;
0330 }
0331 
0332 KateProject *KateProjectPlugin::createProjectForRepository(const QString &type, const QDir &dir)
0333 {
0334     // check if we already have the needed project opened
0335     if (auto project = openProjectForDirectory(dir)) {
0336         return project;
0337     }
0338 
0339     QVariantMap cnf, files;
0340     files[type] = 1;
0341     cnf[QStringLiteral("name")] = dir.dirName();
0342     cnf[QStringLiteral("files")] = (QVariantList() << files);
0343 
0344     KateProject *project = new KateProject(m_threadPool, this, cnf, dir.absolutePath());
0345 
0346     m_projects.append(project);
0347 
0348     Q_EMIT projectCreated(project);
0349     return project;
0350 }
0351 
0352 KateProject *KateProjectPlugin::createProjectForDirectory(const QDir &dir)
0353 {
0354     // check if we already have the needed project opened
0355     if (auto project = openProjectForDirectory(dir)) {
0356         return project;
0357     }
0358 
0359     QVariantMap cnf, files;
0360     files[QStringLiteral("directory")] = QStringLiteral("./");
0361     cnf[QStringLiteral("name")] = dir.dirName();
0362     cnf[QStringLiteral("files")] = (QVariantList() << files);
0363 
0364     KateProject *project = new KateProject(m_threadPool, this, cnf, dir.absolutePath());
0365 
0366     m_projects.append(project);
0367 
0368     Q_EMIT projectCreated(project);
0369     return project;
0370 }
0371 
0372 KateProject *KateProjectPlugin::createProjectForDirectory(const QDir &dir, const QVariantMap &projectMap)
0373 {
0374     // check if we already have the needed project opened
0375     if (auto project = openProjectForDirectory(dir)) {
0376         return project;
0377     }
0378 
0379     KateProject *project = new KateProject(m_threadPool, this, projectMap, dir.absolutePath());
0380     if (!project->isValid()) {
0381         delete project;
0382         return nullptr;
0383     }
0384 
0385     m_projects.append(project);
0386     Q_EMIT projectCreated(project);
0387     return project;
0388 }
0389 
0390 void KateProjectPlugin::setAutoRepository(bool onGit, bool onSubversion, bool onMercurial, bool onFossil)
0391 {
0392     m_autoGit = onGit;
0393     m_autoSubversion = onSubversion;
0394     m_autoMercurial = onMercurial;
0395     m_autoFossil = onFossil;
0396     writeConfig();
0397 }
0398 
0399 bool KateProjectPlugin::autoGit() const
0400 {
0401     return m_autoGit;
0402 }
0403 
0404 bool KateProjectPlugin::autoSubversion() const
0405 {
0406     return m_autoSubversion;
0407 }
0408 
0409 bool KateProjectPlugin::autoMercurial() const
0410 {
0411     return m_autoMercurial;
0412 }
0413 
0414 bool KateProjectPlugin::autoFossil() const
0415 {
0416     return m_autoFossil;
0417 }
0418 
0419 void KateProjectPlugin::setIndex(bool enabled, const QUrl &directory)
0420 {
0421     m_indexEnabled = enabled;
0422     m_indexDirectory = directory;
0423     writeConfig();
0424 }
0425 
0426 bool KateProjectPlugin::getIndexEnabled() const
0427 {
0428     return m_indexEnabled;
0429 }
0430 
0431 QUrl KateProjectPlugin::getIndexDirectory() const
0432 {
0433     return m_indexDirectory;
0434 }
0435 
0436 bool KateProjectPlugin::multiProjectCompletion() const
0437 {
0438     return m_multiProjectCompletion;
0439 }
0440 
0441 bool KateProjectPlugin::multiProjectGoto() const
0442 {
0443     return m_multiProjectGoto;
0444 }
0445 
0446 void KateProjectPlugin::setSingleClickAction(ClickAction cb)
0447 {
0448     m_singleClickAction = cb;
0449     writeConfig();
0450 }
0451 
0452 ClickAction KateProjectPlugin::singleClickAcion()
0453 {
0454     return m_singleClickAction;
0455 }
0456 
0457 void KateProjectPlugin::setDoubleClickAction(ClickAction cb)
0458 {
0459     m_doubleClickAction = cb;
0460     writeConfig();
0461 }
0462 
0463 ClickAction KateProjectPlugin::doubleClickAcion()
0464 {
0465     return m_doubleClickAction;
0466 }
0467 
0468 void KateProjectPlugin::setMultiProject(bool completion, bool gotoSymbol)
0469 {
0470     m_multiProjectCompletion = completion;
0471     m_multiProjectGoto = gotoSymbol;
0472     writeConfig();
0473 }
0474 
0475 void KateProjectPlugin::setRestoreProjectsForSession(bool enabled)
0476 {
0477     m_restoreProjectsForSession = enabled;
0478     writeConfig();
0479 }
0480 
0481 bool KateProjectPlugin::restoreProjectsForSession() const
0482 {
0483     return m_restoreProjectsForSession;
0484 }
0485 
0486 void KateProjectPlugin::readConfig()
0487 {
0488     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("project"));
0489 
0490     const QStringList autorepository = config.readEntry("autorepository", DefaultConfig);
0491     m_autoGit = autorepository.contains(GitConfig);
0492     m_autoSubversion = autorepository.contains(SubversionConfig);
0493     m_autoMercurial = autorepository.contains(MercurialConfig);
0494     m_autoFossil = autorepository.contains(FossilConfig);
0495 
0496     m_indexEnabled = config.readEntry("index", false);
0497     m_indexDirectory = config.readEntry("indexDirectory", QUrl());
0498 
0499     m_multiProjectCompletion = config.readEntry("multiProjectCompletion", false);
0500     m_multiProjectGoto = config.readEntry("multiProjectCompletion", false);
0501 
0502     m_singleClickAction = (ClickAction)config.readEntry("gitStatusSingleClick", (int)ClickAction::NoAction);
0503     m_doubleClickAction = (ClickAction)config.readEntry("gitStatusDoubleClick", (int)ClickAction::StageUnstage);
0504 
0505     m_restoreProjectsForSession = config.readEntry("restoreProjectsForSessions", false);
0506 
0507     Q_EMIT configUpdated();
0508 }
0509 
0510 void KateProjectPlugin::writeConfig()
0511 {
0512     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("project"));
0513     QStringList repos;
0514 
0515     if (m_autoGit) {
0516         repos << GitConfig;
0517     }
0518 
0519     if (m_autoSubversion) {
0520         repos << SubversionConfig;
0521     }
0522 
0523     if (m_autoMercurial) {
0524         repos << MercurialConfig;
0525     }
0526 
0527     if (m_autoFossil) {
0528         repos << FossilConfig;
0529     }
0530 
0531     config.writeEntry("autorepository", repos);
0532 
0533     config.writeEntry("index", m_indexEnabled);
0534     config.writeEntry("indexDirectory", m_indexDirectory);
0535 
0536     config.writeEntry("multiProjectCompletion", m_multiProjectCompletion);
0537     config.writeEntry("multiProjectGoto", m_multiProjectGoto);
0538 
0539     config.writeEntry("gitStatusSingleClick", (int)m_singleClickAction);
0540     config.writeEntry("gitStatusDoubleClick", (int)m_doubleClickAction);
0541 
0542     config.writeEntry("restoreProjectsForSessions", m_restoreProjectsForSession);
0543 
0544     Q_EMIT configUpdated();
0545 }
0546 
0547 static KateProjectPlugin *findProjectPlugin()
0548 {
0549     auto plugin = KTextEditor::Editor::instance()->application()->plugin(QStringLiteral("kateprojectplugin"));
0550     return qobject_cast<KateProjectPlugin *>(plugin);
0551 }
0552 
0553 void KateProjectPlugin::registerVariables()
0554 {
0555     auto editor = KTextEditor::Editor::instance();
0556     editor->registerVariableMatch(QStringLiteral("Project:Path"),
0557                                   i18n("Full path to current project excluding the file name."),
0558                                   [](const QStringView &, KTextEditor::View *view) {
0559                                       if (!view) {
0560                                           return QString();
0561                                       }
0562                                       auto projectPlugin = findProjectPlugin();
0563                                       if (!projectPlugin) {
0564                                           return QString();
0565                                       }
0566                                       auto kateProject = findProjectPlugin()->projectForUrl(view->document()->url());
0567                                       if (!kateProject) {
0568                                           return QString();
0569                                       }
0570                                       return QDir(kateProject->baseDir()).absolutePath();
0571                                   });
0572 
0573     editor->registerVariableMatch(QStringLiteral("Project:NativePath"),
0574                                   i18n("Full path to current project excluding the file name, with native path separator (backslash on Windows)."),
0575                                   [](const QStringView &, KTextEditor::View *view) {
0576                                       if (!view) {
0577                                           return QString();
0578                                       }
0579                                       auto projectPlugin = findProjectPlugin();
0580                                       if (!projectPlugin) {
0581                                           return QString();
0582                                       }
0583                                       auto kateProject = findProjectPlugin()->projectForUrl(view->document()->url());
0584                                       if (!kateProject) {
0585                                           return QString();
0586                                       }
0587                                       return QDir::toNativeSeparators(QDir(kateProject->baseDir()).absolutePath());
0588                                   });
0589 }
0590 void KateProjectPlugin::unregisterVariables()
0591 {
0592     auto editor = KTextEditor::Editor::instance();
0593     editor->unregisterVariable(QStringLiteral("Project:Path"));
0594     editor->unregisterVariable(QStringLiteral("Project:NativePath"));
0595 }
0596 
0597 void KateProjectPlugin::readSessionConfig(const KConfigGroup &config)
0598 {
0599     // de-serialize all open projects as list of JSON documents if allowed
0600     if (restoreProjectsForSession()) {
0601         const auto projectList = config.readEntry("projects", QStringList());
0602         for (const auto &project : projectList) {
0603             const QVariantMap sMap = QJsonDocument::fromJson(project.toUtf8()).toVariant().toMap();
0604 
0605             // valid file backed project?
0606             if (const auto file = sMap[QStringLiteral("file")].toString(); !file.isEmpty() && QFileInfo(file).exists()) {
0607                 createProjectForFileName(file);
0608                 continue;
0609             }
0610 
0611             // valid path + data project?
0612             if (const auto path = sMap[QStringLiteral("path")].toString(); !path.isEmpty() && QFileInfo(path).exists()) {
0613                 createProjectForDirectory(QDir(path), sMap[QStringLiteral("data")].toMap());
0614                 continue;
0615             }
0616 
0617             // we might arrive here if invalid data is store, just ignore that, we just loose session data
0618         }
0619     }
0620 
0621     // always load projects from command line or current working directory first time we arrive here
0622     if (m_initialReadSessionConfigDone) {
0623         return;
0624     }
0625     m_initialReadSessionConfigDone = true;
0626 
0627     /**
0628      * delayed activation after session restore
0629      * we do this both for session restoration to not take preference and
0630      * to be able to signal errors during project loading via message() signals
0631      */
0632     KateProject *projectToActivate = nullptr;
0633 
0634     // open directories as projects
0635     auto args = qApp->arguments();
0636     args.removeFirst(); // The first argument is the executable name
0637     for (const QString &arg : qAsConst(args)) {
0638         QFileInfo info(arg);
0639         if (info.isDir()) {
0640             projectToActivate = projectForDir(info.absoluteFilePath(), true);
0641         }
0642     }
0643 
0644     /**
0645      * open project for our current working directory, if this kate has a terminal
0646      */
0647     if (!projectToActivate && KateApp::isInsideTerminal()) {
0648         projectToActivate = projectForDir(QDir::current());
0649     }
0650 
0651     // if we have some project opened, ensure it is the active one, this happens after session restore
0652     if (projectToActivate) {
0653         // delay this to ensure main windows are already there
0654         QTimer::singleShot(0, projectToActivate, [projectToActivate]() {
0655             if (auto pluginView = KTextEditor::Editor::instance()->application()->activeMainWindow()->pluginView(QStringLiteral("kateprojectplugin"))) {
0656                 static_cast<KateProjectPluginView *>(pluginView)->openProject(projectToActivate);
0657             }
0658         });
0659     }
0660 }
0661 
0662 void KateProjectPlugin::writeSessionConfig(KConfigGroup &config)
0663 {
0664     // serialize all open projects as list of JSON documents if allowed, always write the list to not leave over old data forever
0665     QStringList projectList;
0666     if (restoreProjectsForSession()) {
0667         for (const auto project : projects()) {
0668             QVariantMap sMap;
0669 
0670             // for file backed stuff, we just remember the file
0671             if (project->isFileBacked()) {
0672                 sMap[QStringLiteral("file")] = project->fileName();
0673             }
0674 
0675             // otherwise we remember the data we generated purely in memory
0676             else {
0677                 sMap[QStringLiteral("data")] = project->projectMap();
0678                 sMap[QStringLiteral("path")] = project->baseDir();
0679             }
0680 
0681             // encode as one-lines JSON string
0682             projectList.push_back(QString::fromUtf8(QJsonDocument::fromVariant(QVariant(sMap)).toJson(QJsonDocument::Compact)));
0683         }
0684     }
0685     config.writeEntry("projects", projectList);
0686 }
0687 
0688 void KateProjectPlugin::sendMessage(const QString &text, bool error)
0689 {
0690     const auto icon = QIcon::fromTheme(QStringLiteral("project-open"));
0691     Utils::showMessage(text, icon, i18n("Project"), error ? MessageType::Error : MessageType::Info);
0692 }
0693 
0694 QString KateProjectPlugin::projectBaseDirForDocument(KTextEditor::Document *doc)
0695 {
0696     // quick lookup first, then search
0697     auto project = projectForDocument(doc);
0698     if (!project) {
0699         project = projectForUrl(doc->url());
0700     }
0701     return project ? project->baseDir() : QString();
0702 }
0703 
0704 QVariantMap KateProjectPlugin::projectMapForDocument(KTextEditor::Document *doc)
0705 {
0706     // quick lookup first, then search
0707     auto project = projectForDocument(doc);
0708     if (!project) {
0709         project = projectForUrl(doc->url());
0710     }
0711     return project ? project->projectMap() : QVariantMap();
0712 }
0713 
0714 #include "moc_kateprojectplugin.cpp"