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"