File indexing completed on 2024-05-12 05:52:08

0001 /*
0002     SPDX-FileCopyrightText: 2005 Christoph Cullmann <cullmann@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "katesessionmanager.h"
0008 
0009 #include "katesessionmanagedialog.h"
0010 
0011 #include "kateapp.h"
0012 #include "katemainwindow.h"
0013 #include "katepluginmanager.h"
0014 #include "katerunninginstanceinfo.h"
0015 
0016 #include <KConfigGroup>
0017 #include <KDesktopFile>
0018 #include <KLocalizedString>
0019 #include <KMessageBox>
0020 #include <KService>
0021 #include <KSharedConfig>
0022 #include <KShell>
0023 #include <kwidgetsaddons_version.h>
0024 
0025 #include <QApplication>
0026 #include <QCryptographicHash>
0027 #include <QDir>
0028 #include <QInputDialog>
0029 #include <QTimer>
0030 #include <QUrl>
0031 
0032 #ifndef Q_OS_WIN
0033 #include <unistd.h>
0034 #endif
0035 
0036 // BEGIN KateSessionManager
0037 
0038 KateSessionManager::KateSessionManager(QObject *parent, const QString &sessionsDir)
0039     : QObject(parent)
0040     , m_sessionsDir(sessionsDir)
0041 {
0042     // create dir if needed
0043     Q_ASSERT(!m_sessionsDir.isEmpty());
0044     QDir().mkpath(m_sessionsDir);
0045 
0046     // monitor our session directory for outside changes
0047     m_dirWatch.addPath(m_sessionsDir);
0048     connect(&m_dirWatch, &QFileSystemWatcher::directoryChanged, this, &KateSessionManager::updateSessionList);
0049 
0050     // initial creation of the session list from disk files
0051     updateSessionList();
0052 
0053     // init the session auto save
0054     m_sessionSaveTimer.setInterval(5000);
0055     m_sessionSaveTimer.setSingleShot(true);
0056     auto startTimer = [this] {
0057         if (m_sessionSaveTimerBlocked == 0 && !m_sessionSaveTimer.isActive()) {
0058             m_sessionSaveTimer.start();
0059         }
0060     };
0061     auto dm = KateApp::self()->documentManager();
0062     connect(dm, &KateDocManager::documentCreated, &m_sessionSaveTimer, startTimer);
0063     connect(dm, &KateDocManager::documentDeleted, &m_sessionSaveTimer, startTimer);
0064     m_sessionSaveTimer.callOnTimeout(this, [this] {
0065         if (m_sessionSaveTimerBlocked == 0) {
0066             saveActiveSession(true);
0067         }
0068     });
0069 }
0070 
0071 KateSessionManager::~KateSessionManager() = default;
0072 
0073 void KateSessionManager::updateSessionList()
0074 {
0075     QStringList list;
0076 
0077     // Let's get a list of all session we have atm
0078     QDir dir(m_sessionsDir, QStringLiteral("*.katesession"), QDir::Time);
0079 
0080     for (unsigned int i = 0; i < dir.count(); ++i) {
0081         QString name = dir[i];
0082         name.chop(12); // .katesession
0083         list << QUrl::fromPercentEncoding(name.toLatin1());
0084     }
0085 
0086     // write jump list actions to disk in the kate.desktop file
0087     updateJumpListActions(list);
0088 
0089     bool changed = false;
0090 
0091     // Add new sessions to our list
0092     for (const QString &session : qAsConst(list)) {
0093         if (!m_sessions.contains(session)) {
0094             const QString file = sessionFileForName(session);
0095             m_sessions.insert(session, KateSession::create(file, session));
0096             changed = true;
0097         }
0098     }
0099 
0100     // Remove gone sessions from our list
0101     for (auto it = m_sessions.begin(); it != m_sessions.end();) {
0102         if (list.indexOf(it.key()) < 0 && it.value() != activeSession()) {
0103             it = m_sessions.erase(it);
0104             changed = true;
0105         } else {
0106             ++it;
0107         }
0108     }
0109 
0110     if (changed) {
0111         Q_EMIT sessionListChanged();
0112     }
0113 }
0114 
0115 bool KateSessionManager::activateSession(KateSession::Ptr session, const bool closeAndSaveLast, const bool loadNew)
0116 {
0117     if (activeSession() == session) {
0118         return true;
0119     }
0120 
0121     // we want no auto saving during session switches
0122     AutoSaveBlocker blocker(this);
0123 
0124     if (!session->isAnonymous()) {
0125         // check if the requested session is already open in another instance
0126         const auto instances = fillinRunningKateAppInstances();
0127         for (const auto &instance : instances) {
0128             if (session->name() == instance.sessionName) {
0129                 if (KMessageBox::questionTwoActions(
0130                         nullptr,
0131                         i18n("Session '%1' is already opened in another Kate instance. Switch to that or reopen in this instance?", session->name()),
0132                         QString(),
0133                         KGuiItem(i18nc("@action:button", "Switch to Instance"), QStringLiteral("window")),
0134                         KGuiItem(i18nc("@action:button", "Reopen"), QStringLiteral("document-open")),
0135                         QStringLiteral("katesessionmanager_switch_instance"))
0136                     == KMessageBox::PrimaryAction) {
0137                     instance.dbus_if->call(QStringLiteral("activate"));
0138                     return false;
0139                 }
0140                 break;
0141             }
0142         }
0143     }
0144     // try to close and save last session
0145     if (closeAndSaveLast) {
0146         if (KateApp::self()->activeKateMainWindow()) {
0147             if (!KateApp::self()->activeKateMainWindow()->queryClose_internal()) {
0148                 return true;
0149             }
0150         }
0151 
0152         // Remove all open widgets
0153         const auto mainWindows = KateApp::self()->mainWindowsCount();
0154         for (int i = 0; i < mainWindows; ++i) {
0155             if (KateMainWindow *win = KateApp::self()->mainWindow(i)) {
0156                 const auto widgets = win->widgets();
0157                 for (auto w : widgets) {
0158                     win->removeWidget(w);
0159                 }
0160             }
0161         }
0162 
0163         // save last session or not?
0164         saveActiveSession();
0165 
0166         // really close last
0167         KateApp::self()->documentManager()->closeAllDocuments();
0168     }
0169 
0170     // set the new session
0171     m_activeSession = session;
0172 
0173     // there is one case in which we don't want the restoration and that is
0174     // when restoring session from session manager.
0175     // In that case the restore is handled by the caller
0176     if (loadNew) {
0177         loadSession(session);
0178     }
0179 
0180     Q_EMIT sessionChanged();
0181     return true;
0182 }
0183 
0184 void KateSessionManager::loadSession(const KateSession::Ptr &session) const
0185 {
0186     // open the new session
0187     KSharedConfigPtr sharedConfig = KSharedConfig::openConfig();
0188     KConfig *sc = session->config();
0189     const bool loadDocs = !session->isAnonymous(); // do not load docs for new sessions
0190 
0191     // if we have no session config object, try to load the default
0192     // (anonymous/unnamed sessions)
0193     // load plugin config + plugins
0194     KateApp::self()->pluginManager()->loadConfig(sc);
0195 
0196     if (loadDocs) {
0197         KateApp::self()->documentManager()->restoreDocumentList(sc);
0198     }
0199 
0200     // window config
0201     KConfigGroup c(sharedConfig, QStringLiteral("General"));
0202 
0203     KConfig *cfg = sc;
0204     bool delete_cfg = false;
0205     // a new, named session, read settings of the default session.
0206     if (!sc->hasGroup(QLatin1String("Open MainWindows"))) {
0207         delete_cfg = true;
0208         cfg = new KConfig(anonymousSessionFile(), KConfig::SimpleConfig);
0209     }
0210 
0211     if (c.readEntry("Restore Window Configuration", true)) {
0212         int wCount = cfg->group(QStringLiteral("Open MainWindows")).readEntry("Count", 1);
0213 
0214         for (int i = 0; i < wCount; ++i) {
0215             if (i >= KateApp::self()->mainWindowsCount()) {
0216                 KateApp::self()->newMainWindow(cfg, QStringLiteral("MainWindow%1").arg(i));
0217             } else {
0218                 KateApp::self()->mainWindow(i)->readProperties(KConfigGroup(cfg, QStringLiteral("MainWindow%1").arg(i)));
0219             }
0220 
0221             KateApp::self()->mainWindow(i)->restoreWindowConfig(KConfigGroup(cfg, QStringLiteral("MainWindow%1 Settings").arg(i)));
0222         }
0223 
0224         // remove mainwindows we need no longer...
0225         if (wCount > 0) {
0226             while (wCount < KateApp::self()->mainWindowsCount()) {
0227                 delete KateApp::self()->mainWindow(KateApp::self()->mainWindowsCount() - 1);
0228             }
0229         }
0230     } else {
0231         // load recent files for all existing windows, see bug 408499
0232         for (int i = 0; i < KateApp::self()->mainWindowsCount(); ++i) {
0233             KateApp::self()->mainWindow(i)->loadOpenRecent(cfg);
0234         }
0235     }
0236 
0237     // ensure we have at least one window, always! load recent files for it, too, see bug 408499
0238     if (KateApp::self()->mainWindowsCount() == 0) {
0239         auto w = KateApp::self()->newMainWindow();
0240         w->loadOpenRecent(cfg);
0241     }
0242 
0243     if (delete_cfg) {
0244         delete cfg;
0245     }
0246 
0247     // we shall always have some existing windows here!
0248     Q_ASSERT(KateApp::self()->mainWindowsCount() > 0);
0249 }
0250 
0251 bool KateSessionManager::activateSession(const QString &name, const bool closeAndSaveLast, const bool loadNew)
0252 {
0253     return activateSession(giveSession(name), closeAndSaveLast, loadNew);
0254 }
0255 
0256 bool KateSessionManager::activateAnonymousSession()
0257 {
0258     return activateSession(QString(), false);
0259 }
0260 
0261 KateSession::Ptr KateSessionManager::giveSession(const QString &name)
0262 {
0263     if (name.isEmpty()) {
0264         return KateSession::createAnonymous(anonymousSessionFile());
0265     }
0266 
0267     if (m_sessions.contains(name)) {
0268         return m_sessions.value(name);
0269     }
0270 
0271     KateSession::Ptr s = KateSession::create(sessionFileForName(name), name);
0272     saveSessionTo(s->config());
0273     m_sessions[name] = s;
0274     // Due to this add to m_sessions will updateSessionList() no signal emit,
0275     // but it's important to add. Otherwise could it be happen that m_activeSession
0276     // is not part of m_sessions but a double
0277     Q_EMIT sessionListChanged();
0278 
0279     return s;
0280 }
0281 
0282 bool KateSessionManager::deleteSession(KateSession::Ptr session)
0283 {
0284     if (sessionIsActive(session->name())) {
0285         return false;
0286     }
0287 
0288     KConfigGroup c(KSharedConfig::openConfig(), QStringLiteral("General"));
0289     if (c.readEntry("Last Session") == session->name()) {
0290         c.writeEntry("Last Session", QString());
0291         c.sync();
0292     }
0293 
0294     QFile::remove(session->file());
0295 
0296     // ensure session list is updated
0297     m_sessions.remove(session->name());
0298     Q_EMIT sessionListChanged();
0299     return true;
0300 }
0301 
0302 QString KateSessionManager::copySession(const KateSession::Ptr &session, const QString &newName)
0303 {
0304     const QString name = askForNewSessionName(session, newName);
0305 
0306     if (name.isEmpty()) {
0307         return name;
0308     }
0309 
0310     const QString newFile = sessionFileForName(name);
0311 
0312     KateSession::Ptr ns = KateSession::createFrom(session, newFile, name);
0313     ns->config()->sync();
0314 
0315     // ensure session list is updated
0316     m_sessions[ns->name()] = ns;
0317     Q_EMIT sessionListChanged();
0318     return name;
0319 }
0320 
0321 QString KateSessionManager::renameSession(KateSession::Ptr session, const QString &newName)
0322 {
0323     const QString name = askForNewSessionName(session, newName);
0324 
0325     if (name.isEmpty()) {
0326         return name;
0327     }
0328 
0329     const QString newFile = sessionFileForName(name);
0330 
0331     session->config()->sync();
0332 
0333     if (!QFile::rename(session->file(), newFile)) {
0334         KMessageBox::error(QApplication::activeWindow(),
0335                            i18n("The session could not be renamed to \"%1\". Failed to write to \"%2\"", newName, newFile),
0336                            i18n("Session Renaming"));
0337         return QString();
0338     }
0339 
0340     // update session name and sync
0341     const auto oldName = session->name();
0342     session->setName(newName);
0343     session->setFile(newFile);
0344     session->config()->sync();
0345 
0346     // ensure session list is updated
0347     m_sessions[session->name()] = m_sessions.take(oldName);
0348     Q_EMIT sessionListChanged();
0349 
0350     if (session == activeSession()) {
0351         Q_EMIT sessionChanged();
0352     }
0353 
0354     return name;
0355 }
0356 
0357 void KateSessionManager::saveSessionTo(KConfig *sc)
0358 {
0359     // Clear the session file to avoid to accumulate outdated entries
0360     const auto groupList = sc->groupList();
0361     for (const auto &group : groupList) {
0362         // Don't delete groups for loaded documents that have
0363         // ViewSpace config in session but do not have any views.
0364         if (!isViewLessDocumentViewSpaceGroup(group)) {
0365             sc->deleteGroup(group);
0366         }
0367     }
0368 
0369     // save plugin configs and which plugins to load
0370     KateApp::self()->pluginManager()->writeConfig(sc);
0371 
0372     // save document configs + which documents to load
0373     KateApp::self()->documentManager()->saveDocumentList(sc);
0374 
0375     sc->group(QStringLiteral("Open MainWindows")).writeEntry("Count", KateApp::self()->mainWindowsCount());
0376 
0377     // save config for all windows around ;)
0378     bool saveWindowConfig = KConfigGroup(KSharedConfig::openConfig(), QStringLiteral("General")).readEntry("Restore Window Configuration", true);
0379     for (int i = 0; i < KateApp::self()->mainWindowsCount(); ++i) {
0380         KConfigGroup cg(sc, QStringLiteral("MainWindow%1").arg(i));
0381         // saveProperties() handles saving the "open recent" files list
0382         // don't store splitters and co. for KWrite
0383         // see bug 461355 and bug 459366 and bug 463139
0384         KateApp::self()->mainWindow(i)->saveProperties(cg, KateApp::isKate());
0385         if (saveWindowConfig) {
0386             KateApp::self()->mainWindow(i)->saveWindowConfig(KConfigGroup(sc, QStringLiteral("MainWindow%1 Settings").arg(i)));
0387         }
0388     }
0389 
0390     sc->sync();
0391 
0392     /**
0393      * try to sync file to disk
0394      */
0395     QFile fileToSync(sc->name());
0396     if (fileToSync.open(QIODevice::ReadOnly)) {
0397 #ifndef Q_OS_WIN
0398         // ensure that the file is written to disk
0399 #ifdef HAVE_FDATASYNC
0400         fdatasync(fileToSync.handle());
0401 #else
0402         fsync(fileToSync.handle());
0403 #endif
0404 #endif
0405     }
0406 }
0407 
0408 bool KateSessionManager::saveActiveSession(bool rememberAsLast)
0409 {
0410     if (!activeSession()) {
0411         return false;
0412     }
0413 
0414     KConfig *sc = activeSession()->config();
0415 
0416     saveSessionTo(sc);
0417 
0418     if (rememberAsLast && !activeSession()->isAnonymous()) {
0419         KSharedConfigPtr c = KSharedConfig::openConfig();
0420         c->group(QStringLiteral("General")).writeEntry("Last Session", activeSession()->name());
0421         c->sync();
0422     }
0423     return true;
0424 }
0425 
0426 bool KateSessionManager::chooseSession()
0427 {
0428     const KConfigGroup c(KSharedConfig::openConfig(), QStringLiteral("General"));
0429 
0430     // get last used session, default to default session
0431     const QString lastSession(c.readEntry("Last Session", QString()));
0432     const QString sesStart(c.readEntry("Startup Session", "manual"));
0433 
0434     // uhh, just open last used session, show no chooser
0435     if (sesStart == QLatin1String("last")) {
0436         return activateSession(lastSession, false);
0437     }
0438 
0439     // start with empty new session or in case no sessions exist
0440     if (sesStart == QLatin1String("new") || sessionList().empty()) {
0441         return activateAnonymousSession();
0442     }
0443 
0444     // else: ask the user
0445     KateSessionManageDialog dlg(nullptr, lastSession);
0446     return dlg.exec();
0447 }
0448 
0449 void KateSessionManager::sessionNew()
0450 {
0451     activateSession(giveSession(QString()));
0452 }
0453 
0454 void KateSessionManager::sessionSave()
0455 {
0456     if (activeSession() && activeSession()->isAnonymous()) {
0457         sessionSaveAs();
0458     } else {
0459         saveActiveSession();
0460     }
0461 }
0462 
0463 void KateSessionManager::sessionSaveAs()
0464 {
0465     const QString newName = askForNewSessionName(activeSession());
0466 
0467     if (newName.isEmpty()) {
0468         return;
0469     }
0470 
0471     activeSession()->config()->sync();
0472 
0473     KateSession::Ptr ns = KateSession::createFrom(activeSession(), sessionFileForName(newName), newName);
0474     m_activeSession = ns;
0475     saveActiveSession();
0476 
0477     Q_EMIT sessionChanged();
0478 }
0479 
0480 QString KateSessionManager::askForNewSessionName(KateSession::Ptr session, const QString &newName)
0481 {
0482     if (session->name() == newName && !session->isAnonymous()) {
0483         return QString();
0484     }
0485 
0486     const QString messagePrompt = i18n("Session name:");
0487     const KLocalizedString messageExist = ki18n(
0488         "There is already an existing session with your chosen name: %1\n"
0489         "Please choose a different one.");
0490     const QString messageEmpty = i18n("To save a session, you must specify a name.");
0491 
0492     QString messageTotal = messagePrompt;
0493     QString name = newName;
0494 
0495     while (true) {
0496         QString preset = name;
0497 
0498         if (name.isEmpty()) {
0499             preset = suggestNewSessionName(session->name());
0500             messageTotal = messageEmpty + QLatin1String("\n\n") + messagePrompt;
0501 
0502         } else if (QFile::exists(sessionFileForName(name))) {
0503             preset = suggestNewSessionName(name);
0504             if (preset.isEmpty()) {
0505                 // Very unlikely, but as fall back we keep users input
0506                 preset = name;
0507             }
0508             messageTotal = messageExist.subs(name).toString() + QLatin1String("\n\n") + messagePrompt;
0509 
0510         } else {
0511             return name;
0512         }
0513 
0514         QInputDialog dlg(KateApp::self()->activeKateMainWindow());
0515         dlg.setInputMode(QInputDialog::TextInput);
0516         if (session->isAnonymous()) {
0517             dlg.setWindowTitle(i18n("Specify a name for this session"));
0518         } else {
0519             dlg.setWindowTitle(i18n("Specify a new name for session: %1", session->name()));
0520         }
0521         dlg.setLabelText(messageTotal);
0522         dlg.setTextValue(preset);
0523         dlg.resize(900, 100); // FIXME Calc somehow a proper size
0524         bool ok = dlg.exec();
0525         name = dlg.textValue();
0526 
0527         if (!ok) {
0528             return QString();
0529         }
0530     }
0531 }
0532 
0533 QString KateSessionManager::suggestNewSessionName(const QString &target)
0534 {
0535     if (target.isEmpty()) {
0536         // Here could also a default name set or the current session name used
0537         return QString();
0538     }
0539 
0540     const QString mask = QStringLiteral("%1 (%2)");
0541     QString name;
0542 
0543     for (int i = 2; i < 1000000; i++) { // Should be enough to get an unique name
0544         name = mask.arg(target).arg(i);
0545 
0546         if (!QFile::exists(sessionFileForName(name))) {
0547             return name;
0548         }
0549     }
0550 
0551     return QString();
0552 }
0553 
0554 void KateSessionManager::sessionManage()
0555 {
0556     KateSessionManageDialog dlg(KateApp::self()->activeKateMainWindow());
0557     dlg.exec();
0558 }
0559 
0560 bool KateSessionManager::sessionIsActive(const QString &session)
0561 {
0562     // first check the current instance, that is skipped below
0563     if (activeSession() && activeSession()->name() == session) {
0564         return true;
0565     }
0566 
0567     // check if the requested session is open in another instance
0568     const auto instances = fillinRunningKateAppInstances();
0569     const auto it = std::find_if(instances.cbegin(), instances.cend(), [&session](const auto &instance) {
0570         return instance.sessionName == session;
0571     });
0572     return it != instances.end();
0573 }
0574 
0575 QString KateSessionManager::anonymousSessionFile() const
0576 {
0577     const QString file = m_sessionsDir + QStringLiteral("/../anonymous.katesession");
0578     return QDir().cleanPath(file);
0579 }
0580 
0581 QString KateSessionManager::sessionFileForName(const QString &name) const
0582 {
0583     Q_ASSERT(!name.isEmpty());
0584     const QString sname = QString::fromLatin1(QUrl::toPercentEncoding(name, QByteArray(), QByteArray(".")));
0585     return m_sessionsDir + QStringLiteral("/") + sname + QStringLiteral(".katesession");
0586 }
0587 
0588 KateSessionList KateSessionManager::sessionList()
0589 {
0590     return m_sessions.values();
0591 }
0592 
0593 void KateSessionManager::updateJumpListActions(const QStringList &sessionList)
0594 {
0595     KService::Ptr service = KService::serviceByStorageId(qApp->desktopFileName());
0596     if (!service) {
0597         return;
0598     }
0599 
0600     std::unique_ptr<KDesktopFile> df(new KDesktopFile(service->entryPath()));
0601 
0602     QStringList newActions = df->readActions();
0603 
0604     // try to keep existing custom actions intact, only remove our "Session" actions and add them back later
0605     newActions.erase(std::remove_if(newActions.begin(),
0606                                     newActions.end(),
0607                                     [](const QString &action) {
0608                                         return action.startsWith(QLatin1String("Session "));
0609                                     }),
0610                      newActions.end());
0611 
0612     // Limit the number of list entries we like to offer
0613     const int maxEntryCount = std::min<int>(sessionList.count(), 10);
0614 
0615     // sessionList is ordered by time, but we like it alphabetical to avoid even more a needed update
0616     QStringList sessionSubList = sessionList.mid(0, maxEntryCount);
0617     sessionSubList.sort();
0618 
0619     // we compute the new group names in advance so we can tell whether we changed something
0620     // and avoid touching the desktop file leading to an expensive ksycoca recreation
0621     QStringList sessionActions;
0622     sessionActions.reserve(maxEntryCount);
0623 
0624     for (int i = 0; i < maxEntryCount; ++i) {
0625         sessionActions
0626             << QStringLiteral("Session %1").arg(QString::fromLatin1(QCryptographicHash::hash(sessionSubList.at(i).toUtf8(), QCryptographicHash::Md5).toHex()));
0627     }
0628 
0629     newActions += sessionActions;
0630 
0631     // nothing to do
0632     if (df->readActions() == newActions) {
0633         return;
0634     }
0635 
0636     const QString &localPath = service->locateLocal();
0637     if (service->entryPath() != localPath) {
0638         df.reset(df->copyTo(localPath));
0639     }
0640 
0641     // remove all Session action groups first to not leave behind any cruft
0642     const auto actions = df->readActions();
0643     for (const QString &action : actions) {
0644         if (action.startsWith(QLatin1String("Session "))) {
0645             // TODO is there no deleteGroup(QLatin1String(KConfigGroup))?
0646             df->deleteGroup(df->actionGroup(action).name());
0647         }
0648     }
0649 
0650     for (int i = 0; i < maxEntryCount; ++i) {
0651         const QString &action = sessionActions.at(i); // is a transform of sessionSubList, so count and order is identical
0652         const QString &session = sessionSubList.at(i);
0653 
0654         KConfigGroup grp = df->actionGroup(action);
0655         grp.writeEntry("Name", session);
0656         grp.writeEntry("Exec", QStringLiteral("kate -n -s %1").arg(KShell::quoteArg(session)));
0657     }
0658 
0659     df->desktopGroup().writeXdgListEntry("Actions", newActions);
0660 }
0661 
0662 bool KateSessionManager::isViewLessDocumentViewSpaceGroup(const QString &group)
0663 {
0664     if (KateApp::self()->sessionManager()->activeSession()->isAnonymous()) {
0665         return false;
0666     }
0667 
0668     if (!group.startsWith(QStringLiteral("MainWindow"))) {
0669         return false;
0670     }
0671 
0672     static const QRegularExpression re(QStringLiteral("^MainWindow\\d+\\-ViewSpace\\s\\d+\\s(.*)$"));
0673     QRegularExpressionMatch match = re.match(group);
0674     if (match.hasMatch()) {
0675         QUrl url(match.captured(1));
0676         auto *docMan = KateApp::self()->documentManager();
0677         auto *doc = docMan->findDocument(url);
0678         if (doc && doc->views().empty() && docMan->documentList().contains(doc)) {
0679             return true;
0680         }
0681     }
0682     return false;
0683 }
0684 
0685 // END KateSessionManager
0686 
0687 #include "moc_katesessionmanager.cpp"