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"