diff --git a/addons/close-except-like/close_except_plugin.cpp b/addons/close-except-like/close_except_plugin.cpp index 0f66a7d6c..39a34e047 100644 --- a/addons/close-except-like/close_except_plugin.cpp +++ b/addons/close-except-like/close_except_plugin.cpp @@ -1,276 +1,276 @@ /** * \file * * \brief Kate Close Except/Like plugin implementation * * Copyright (C) 2012 Alex Turbov * * \date Thu Mar 8 08:13:43 MSK 2012 -- Initial design */ /* * KateCloseExceptPlugin is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * KateCloseExceptPlugin is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ // Project specific includes #include "close_except_plugin.h" #include "close_confirm_dialog.h" // Standard includes #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(CloseExceptPluginFactory, "katecloseexceptplugin.json", registerPlugin();) namespace kate { // BEGIN CloseExceptPlugin CloseExceptPlugin::CloseExceptPlugin(QObject *application, const QList &) : KTextEditor::Plugin(application) { } QObject *CloseExceptPlugin::createView(KTextEditor::MainWindow *parent) { return new CloseExceptPluginView(parent, this); } void CloseExceptPlugin::readSessionConfig(const KConfigGroup &config) { const KConfigGroup scg(&config, QStringLiteral("menu")); m_show_confirmation_needed = scg.readEntry(QStringLiteral("ShowConfirmation"), true); } void CloseExceptPlugin::writeSessionConfig(KConfigGroup &config) { KConfigGroup scg(&config, QStringLiteral("menu")); scg.writeEntry(QStringLiteral("ShowConfirmation"), m_show_confirmation_needed); scg.sync(); } // END CloseExceptPlugin // BEGIN CloseExceptPluginView CloseExceptPluginView::CloseExceptPluginView(KTextEditor::MainWindow *mw, CloseExceptPlugin *plugin) : QObject(mw) , KXMLGUIClient() , m_plugin(plugin) , m_show_confirmation_action(new KToggleAction(i18nc("@action:inmenu", "Show Confirmation"), this)) , m_except_menu(new KActionMenu(i18nc("@action:inmenu close docs except the following...", "Close Except"), this)) , m_like_menu(new KActionMenu(i18nc("@action:inmenu close docs like the following...", "Close Like"), this)) , m_mainWindow(mw) { KXMLGUIClient::setComponentName(QStringLiteral("katecloseexceptplugin"), i18n("Close Except/Like Plugin")); setXMLFile(QStringLiteral("ui.rc")); actionCollection()->addAction(QStringLiteral("file_close_except"), m_except_menu); actionCollection()->addAction(QStringLiteral("file_close_like"), m_like_menu); connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::documentCreated, this, &CloseExceptPluginView::documentCreated); // Configure toggle action and connect it to update state m_show_confirmation_action->setChecked(m_plugin->showConfirmationNeeded()); connect(m_show_confirmation_action.data(), &KToggleAction::toggled, m_plugin, &CloseExceptPlugin::toggleShowConfirmation); // connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &CloseExceptPluginView::viewCreated); // Fill menu w/ currently opened document masks/groups updateMenu(); m_mainWindow->guiFactory()->addClient(this); } CloseExceptPluginView::~CloseExceptPluginView() { m_mainWindow->guiFactory()->removeClient(this); } void CloseExceptPluginView::viewCreated(KTextEditor::View *view) { connectToDocument(view->document()); updateMenu(); } void CloseExceptPluginView::documentCreated(KTextEditor::Editor *, KTextEditor::Document *document) { connectToDocument(document); updateMenu(); } void CloseExceptPluginView::connectToDocument(KTextEditor::Document *document) { // Subscribe self to document close and name changes connect(document, &KTextEditor::Document::aboutToClose, this, &CloseExceptPluginView::updateMenuSlotStub); connect(document, &KTextEditor::Document::documentNameChanged, this, &CloseExceptPluginView::updateMenuSlotStub); connect(document, &KTextEditor::Document::documentUrlChanged, this, &CloseExceptPluginView::updateMenuSlotStub); } void CloseExceptPluginView::updateMenuSlotStub(KTextEditor::Document *) { updateMenu(); } void CloseExceptPluginView::appendActionsFrom(const std::set &paths, actions_map_type &actions, KActionMenu *menu, CloseFunction closeFunction) { - Q_FOREACH (const QUrl &path, paths) { + for (const QUrl &path : paths) { QString action = path.path() + QLatin1Char('*'); actions[action] = QPointer(new QAction(action, menu)); menu->addAction(actions[action]); connect(actions[action].data(), &QAction::triggered, this, [this, closeFunction, action]() { (this->*closeFunction)(action); }); } } void CloseExceptPluginView::appendActionsFrom(const std::set &masks, actions_map_type &actions, KActionMenu *menu, CloseFunction closeFunction) { - Q_FOREACH (const QString &mask, masks) { + for (const QString &mask : masks) { QString action = mask.startsWith(QLatin1Char('*')) ? mask : mask + QLatin1Char('*'); actions[action] = QPointer(new QAction(action, menu)); menu->addAction(actions[action]); connect(actions[action].data(), &QAction::triggered, this, [this, closeFunction, action]() { (this->*closeFunction)(action); }); } } void CloseExceptPluginView::updateMenu(const std::set &paths, const std::set &masks, actions_map_type &actions, KActionMenu *menu, CloseFunction closeFunction) { // turn menu ON or OFF depending on collected results menu->setEnabled(!paths.empty()); // Clear previous menus for (actions_map_type::iterator it = actions.begin(), last = actions.end(); it != last;) { menu->removeAction(*it); actions.erase(it++); } // Form a new one appendActionsFrom(paths, actions, menu, closeFunction); if (!masks.empty()) { if (!paths.empty()) menu->addSeparator(); // Add separator between paths and file's ext filters appendActionsFrom(masks, actions, menu, closeFunction); } // Append 'Show Confirmation' toggle menu item menu->addSeparator(); // Add separator between paths and show confirmation menu->addAction(m_show_confirmation_action); } void CloseExceptPluginView::updateMenu() { const QList &docs = KTextEditor::Editor::instance()->application()->documents(); if (docs.size() < 2) { // qDebug() << "No docs r (or the only) opened right now --> disable menu"; m_except_menu->setEnabled(false); m_except_menu->addSeparator(); m_like_menu->setEnabled(false); m_like_menu->addSeparator(); /// \note It seems there is always a document present... it named \em 'Untitled' } else { // Iterate over documents and form a set of candidates typedef std::set paths_set_type; typedef std::set paths_set_type_masks; paths_set_type doc_paths; paths_set_type_masks masks; - Q_FOREACH (KTextEditor::Document *document, docs) { + for (KTextEditor::Document *document : docs) { const QString &ext = QFileInfo(document->url().path()).completeSuffix(); if (!ext.isEmpty()) masks.insert(QStringLiteral("*.") + ext); doc_paths.insert(KIO::upUrl(document->url())); } paths_set_type paths = doc_paths; // qDebug() << "stage #1: Collected" << paths.size() << "paths and" << masks.size() << "masks"; // Add common paths to the collection for (paths_set_type::iterator it = doc_paths.begin(), last = doc_paths.end(); it != last; ++it) { for (QUrl url = *it; (!url.path().isEmpty()) && url.path() != QLatin1String("/"); url = KIO::upUrl(url)) { paths_set_type::iterator not_it = it; for (++not_it; not_it != last; ++not_it) if (!not_it->path().startsWith(url.path())) break; if (not_it == last) { paths.insert(url); break; } } } // qDebug() << "stage #2: Collected" << paths.size() << "paths and" << masks.size() << "masks"; // updateMenu(paths, masks, m_except_actions, m_except_menu, &CloseExceptPluginView::closeExcept); updateMenu(paths, masks, m_like_actions, m_like_menu, &CloseExceptPluginView::closeLike); } } void CloseExceptPluginView::close(const QString &item, const bool close_if_match) { QChar asterisk = QLatin1Char('*'); assert("Parameter seems invalid! Is smth has changed in the code?" && !item.isEmpty() && (item[0] == asterisk || item[item.size() - 1] == asterisk)); const bool is_path = item[0] != asterisk; const QString mask = is_path ? item.left(item.size() - 1) : item; // qDebug() << "Going to close items [" << close_if_match << "/" << is_path << "]: " << mask; QList docs2close; const QList &docs = KTextEditor::Editor::instance()->application()->documents(); - Q_FOREACH (KTextEditor::Document *document, docs) { + for (KTextEditor::Document *document : docs) { const QString &path = KIO::upUrl(document->url()).path(); /// \note Take a dot in account, so \c *.c would not match for \c blah.kcfgc const QString &ext = QLatin1Char('.') + QFileInfo(document->url().fileName()).completeSuffix(); const bool match = (!is_path && mask.endsWith(ext)) || (is_path && path.startsWith(mask)); if (match == close_if_match) { // qDebug() << "*** Will close: " << document->url(); docs2close.push_back(document); } } if (docs2close.isEmpty()) { displayMessage(i18nc("@title:window", "Error"), i18nc("@info:tooltip", "No files to close ..."), KTextEditor::Message::Error); return; } // Show confirmation dialog if needed const bool removeNeeded = !m_plugin->showConfirmationNeeded() || CloseConfirmDialog(docs2close, m_show_confirmation_action, qobject_cast(this)).exec(); if (removeNeeded) { if (docs2close.isEmpty()) { displayMessage(i18nc("@title:window", "Error"), i18nc("@info:tooltip", "No files to close ..."), KTextEditor::Message::Error); } else { // Close 'em all! KTextEditor::Editor::instance()->application()->closeDocuments(docs2close); updateMenu(); displayMessage(i18nc("@title:window", "Done"), i18np("%1 file closed", "%1 files closed", docs2close.size()), KTextEditor::Message::Positive); } } } void CloseExceptPluginView::displayMessage(const QString &title, const QString &msg, KTextEditor::Message::MessageType level) { KTextEditor::View *kv = m_mainWindow->activeView(); if (!kv) return; delete m_infoMessage; m_infoMessage = new KTextEditor::Message(xi18nc("@info", "%1%2", title, msg), level); m_infoMessage->setWordWrap(true); m_infoMessage->setPosition(KTextEditor::Message::TopInView); m_infoMessage->setAutoHide(5000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(kv); kv->document()->postMessage(m_infoMessage); } // END CloseExceptPluginView } // namespace kate #include "close_except_plugin.moc" // kate: hl C++11/Qt4; diff --git a/addons/filetree/katefiletreemodel.cpp b/addons/filetree/katefiletreemodel.cpp index 0ef240720..697d7f8f9 100644 --- a/addons/filetree/katefiletreemodel.cpp +++ b/addons/filetree/katefiletreemodel.cpp @@ -1,1345 +1,1345 @@ /* This file is part of the KDE project Copyright (C) 2010 Thomas Fjellstrom This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "katefiletreemodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "katefiletreedebug.h" class ProxyItemDir; class ProxyItem { friend class KateFileTreeModel; public: enum Flag { None = 0, Dir = 1, Modified = 2, ModifiedExternally = 4, DeletedExternally = 8, Empty = 16, ShowFullPath = 32, Host = 64 }; Q_DECLARE_FLAGS(Flags, Flag) ProxyItem(const QString &n, ProxyItemDir *p = nullptr, Flags f = ProxyItem::None); ~ProxyItem(); int addChild(ProxyItem *p); void remChild(ProxyItem *p); ProxyItemDir *parent() const; ProxyItem *child(int idx) const; int childCount() const; int row() const; const QString &display() const; const QString &documentName() const; const QString &path() const; void setPath(const QString &str); void setHost(const QString &host); const QString &host() const; void setIcon(const QIcon &i); const QIcon &icon() const; const QList &children() const; QList &children(); void setDoc(KTextEditor::Document *doc); KTextEditor::Document *doc() const; /** * the view usess this to close all the documents under the folder * @returns list of all the (nested) documents under this node */ QList docTree() const; void setFlags(Flags flags); void setFlag(Flag flag); void clearFlag(Flag flag); bool flag(Flag flag) const; private: QString m_path; QString m_documentName; ProxyItemDir *m_parent; QList m_children; int m_row; Flags m_flags; QString m_display; QIcon m_icon; KTextEditor::Document *m_doc; QString m_host; protected: void updateDisplay(); void updateDocumentName(); }; QDebug operator<<(QDebug dbg, ProxyItem *item) { if (!item) { dbg.nospace() << "ProxyItem(0x0) "; return dbg.maybeSpace(); } const void *parent = static_cast(item->parent()); dbg.nospace() << "ProxyItem(" << (void *)item << ","; dbg.nospace() << parent << "," << item->row() << ","; dbg.nospace() << item->doc() << "," << item->path() << ") "; return dbg.maybeSpace(); } class ProxyItemDir : public ProxyItem { public: ProxyItemDir(const QString &n, ProxyItemDir *p = nullptr) : ProxyItem(n, p) { setFlag(ProxyItem::Dir); updateDisplay(); setIcon(QIcon::fromTheme(QStringLiteral("folder"))); } }; QDebug operator<<(QDebug dbg, ProxyItemDir *item) { if (!item) { dbg.nospace() << "ProxyItemDir(0x0) "; return dbg.maybeSpace(); } const void *parent = static_cast(item->parent()); dbg.nospace() << "ProxyItemDir(" << (void *)item << ","; dbg.nospace() << parent << "," << item->row() << ","; dbg.nospace() << item->path() << ", children:" << item->childCount() << ") "; return dbg.maybeSpace(); } Q_DECLARE_OPERATORS_FOR_FLAGS(ProxyItem::Flags) // BEGIN ProxyItem ProxyItem::ProxyItem(const QString &d, ProxyItemDir *p, ProxyItem::Flags f) : m_path(d) , m_parent(Q_NULLPTR) , m_row(-1) , m_flags(f) , m_doc(nullptr) { updateDisplay(); /** * add to parent, if parent passed * we assigned above nullptr to parent to not trigger * remove from old parent here! */ if (p) { p->addChild(this); } } ProxyItem::~ProxyItem() { qDeleteAll(m_children); } void ProxyItem::updateDisplay() { // triggers only if this is a top level node and the root has the show full path flag set. if (flag(ProxyItem::Dir) && m_parent && !m_parent->m_parent && m_parent->flag(ProxyItem::ShowFullPath)) { m_display = m_path; if (m_display.startsWith(QDir::homePath())) { m_display.replace(0, QDir::homePath().length(), QStringLiteral("~")); } } else { m_display = m_path.section(QLatin1Char('/'), -1, -1); if (flag(ProxyItem::Host) && (!m_parent || (m_parent && !m_parent->m_parent))) { const QString hostPrefix = QStringLiteral("[%1]").arg(host()); if (hostPrefix != m_display) { m_display = hostPrefix + m_display; } } } } int ProxyItem::addChild(ProxyItem *item) { // remove from old parent, is any if (item->m_parent) { item->m_parent->remChild(item); } const int item_row = m_children.count(); item->m_row = item_row; m_children.append(item); item->m_parent = static_cast(this); item->updateDisplay(); return item_row; } void ProxyItem::remChild(ProxyItem *item) { const int idx = m_children.indexOf(item); Q_ASSERT(idx != -1); m_children.removeAt(idx); for (int i = idx; i < m_children.count(); i++) { m_children[i]->m_row = i; } item->m_parent = nullptr; } ProxyItemDir *ProxyItem::parent() const { return m_parent; } ProxyItem *ProxyItem::child(int idx) const { return (idx < 0 || idx >= m_children.count()) ? nullptr : m_children[idx]; } int ProxyItem::childCount() const { return m_children.count(); } int ProxyItem::row() const { return m_row; } const QIcon &ProxyItem::icon() const { return m_icon; } void ProxyItem::setIcon(const QIcon &i) { m_icon = i; } const QString &ProxyItem::documentName() const { return m_documentName; } const QString &ProxyItem::display() const { return m_display; } const QString &ProxyItem::path() const { return m_path; } void ProxyItem::setPath(const QString &p) { m_path = p; updateDisplay(); } const QList &ProxyItem::children() const { return m_children; } QList &ProxyItem::children() { return m_children; } void ProxyItem::setDoc(KTextEditor::Document *doc) { Q_ASSERT(doc); m_doc = doc; updateDocumentName(); } KTextEditor::Document *ProxyItem::doc() const { return m_doc; } QList ProxyItem::docTree() const { QList result; if (m_doc) { result.append(m_doc); return result; } for (const ProxyItem *item : qAsConst(m_children)) { result.append(item->docTree()); } return result; } bool ProxyItem::flag(Flag f) const { return m_flags & f; } void ProxyItem::setFlag(Flag f) { m_flags |= f; } void ProxyItem::setFlags(Flags f) { m_flags = f; } void ProxyItem::clearFlag(Flag f) { m_flags &= ~f; } void ProxyItem::setHost(const QString &host) { m_host = host; if (host.isEmpty()) { clearFlag(Host); } else { setFlag(Host); } updateDocumentName(); updateDisplay(); } const QString &ProxyItem::host() const { return m_host; } void ProxyItem::updateDocumentName() { const QString docName = m_doc ? m_doc->documentName() : QString(); if (flag(ProxyItem::Host)) { m_documentName = QStringLiteral("[%1]%2").arg(m_host, docName); } else { m_documentName = docName; } } // END ProxyItem KateFileTreeModel::KateFileTreeModel(QObject *p) : QAbstractItemModel(p) , m_root(new ProxyItemDir(QStringLiteral("m_root"), nullptr)) { // setup default settings // session init will set these all soon const KColorScheme colors(QPalette::Active); const QColor bg = colors.background().color(); m_editShade = KColorUtils::tint(bg, colors.foreground(KColorScheme::ActiveText).color(), 0.5); m_viewShade = KColorUtils::tint(bg, colors.foreground(KColorScheme::VisitedText).color(), 0.5); m_shadingEnabled = true; m_listMode = false; initModel(); } KateFileTreeModel::~KateFileTreeModel() { delete m_root; } bool KateFileTreeModel::shadingEnabled() const { return m_shadingEnabled; } void KateFileTreeModel::setShadingEnabled(bool se) { if (m_shadingEnabled != se) { updateBackgrounds(true); m_shadingEnabled = se; } } const QColor &KateFileTreeModel::editShade() const { return m_editShade; } void KateFileTreeModel::setEditShade(const QColor &es) { m_editShade = es; } const QColor &KateFileTreeModel::viewShade() const { return m_viewShade; } void KateFileTreeModel::setViewShade(const QColor &vs) { m_viewShade = vs; } bool KateFileTreeModel::showFullPathOnRoots(void) const { return m_root->flag(ProxyItem::ShowFullPath); } void KateFileTreeModel::setShowFullPathOnRoots(bool s) { if (s) { m_root->setFlag(ProxyItem::ShowFullPath); } else { m_root->clearFlag(ProxyItem::ShowFullPath); } const auto rootChildren = m_root->children(); for (ProxyItem *root : rootChildren) { root->updateDisplay(); } } void KateFileTreeModel::initModel() { // add already existing documents const auto documents = KTextEditor::Editor::instance()->application()->documents(); for (KTextEditor::Document *doc : documents) { documentOpened(doc); } } void KateFileTreeModel::clearModel() { // remove all items // can safely ignore documentClosed here beginRemoveRows(QModelIndex(), 0, qMax(m_root->childCount() - 1, 0)); delete m_root; m_root = new ProxyItemDir(QStringLiteral("m_root"), nullptr); m_docmap.clear(); m_viewHistory.clear(); m_editHistory.clear(); m_brushes.clear(); endRemoveRows(); } void KateFileTreeModel::connectDocument(const KTextEditor::Document *doc) { connect(doc, &KTextEditor::Document::documentNameChanged, this, &KateFileTreeModel::documentNameChanged); connect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateFileTreeModel::documentNameChanged); connect(doc, &KTextEditor::Document::modifiedChanged, this, &KateFileTreeModel::documentModifiedChanged); connect(doc, SIGNAL(modifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(documentModifiedOnDisc(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason))); } QModelIndex KateFileTreeModel::docIndex(const KTextEditor::Document *doc) const { if (!m_docmap.contains(doc)) { return QModelIndex(); } ProxyItem *item = m_docmap[doc]; return createIndex(item->row(), 0, item); } Qt::ItemFlags KateFileTreeModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = Qt::ItemIsEnabled; if (!index.isValid()) { return nullptr; } const ProxyItem *item = static_cast(index.internalPointer()); if (item) { if (!item->childCount()) { flags |= Qt::ItemIsSelectable; } if (item->doc() && item->doc()->url().isValid()) { flags |= Qt::ItemIsDragEnabled; } } return flags; } Q_DECLARE_METATYPE(QList) QVariant KateFileTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } ProxyItem *item = static_cast(index.internalPointer()); if (!item) { return QVariant(); } switch (role) { case KateFileTreeModel::PathRole: // allow to sort with hostname + path, bug 271488 return (item->doc() && !item->doc()->url().isEmpty()) ? item->doc()->url().toString() : item->path(); case KateFileTreeModel::DocumentRole: return QVariant::fromValue(item->doc()); case KateFileTreeModel::OpeningOrderRole: return item->row(); case KateFileTreeModel::DocumentTreeRole: return QVariant::fromValue(item->docTree()); case Qt::DisplayRole: // in list mode we want to use kate's fancy names. if (m_listMode) { return item->documentName(); } else { return item->display(); } case Qt::DecorationRole: return item->icon(); case Qt::ToolTipRole: { QString tooltip = item->path(); if (item->flag(ProxyItem::DeletedExternally) || item->flag(ProxyItem::ModifiedExternally)) { tooltip = i18nc("%1 is the full path", "

%1

The document has been modified by another application.

", item->path()); } return tooltip; } case Qt::ForegroundRole: { const KColorScheme colors(QPalette::Active); if (!item->flag(ProxyItem::Dir) && (!item->doc() || item->doc()->openingError())) { return colors.foreground(KColorScheme::InactiveText).color(); } } break; case Qt::BackgroundRole: // TODO: do that funky shading the file list does... if (m_shadingEnabled && m_brushes.contains(item)) { return m_brushes[item]; } break; } return QVariant(); } QMimeData *KateFileTreeModel::mimeData(const QModelIndexList &indexes) const { QList urls; for (const auto &index : indexes) { ProxyItem *item = static_cast(index.internalPointer()); if (!item || !item->doc() || !item->doc()->url().isValid()) { continue; } urls.append(item->doc()->url()); } if (urls.isEmpty()) { return nullptr; } QMimeData *mimeData = new QMimeData(); mimeData->setUrls(urls); return mimeData; } QVariant KateFileTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation); Q_UNUSED(role); if (section == 0) { return QLatin1String("name"); } return QVariant(); } int KateFileTreeModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return m_root->childCount(); } const ProxyItem *item = static_cast(parent.internalPointer()); if (!item) { return 0; } return item->childCount(); } int KateFileTreeModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } QModelIndex KateFileTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } const ProxyItem *item = static_cast(index.internalPointer()); if (!item) { return QModelIndex(); } if (!item->parent()) { return QModelIndex(); } if (item->parent() == m_root) { return QModelIndex(); } return createIndex(item->parent()->row(), 0, item->parent()); } QModelIndex KateFileTreeModel::index(int row, int column, const QModelIndex &parent) const { const ProxyItem *p = nullptr; if (column != 0) { return QModelIndex(); } if (!parent.isValid()) { p = m_root; } else { p = static_cast(parent.internalPointer()); } if (!p) { return QModelIndex(); } if (row < 0 || row >= p->childCount()) { return QModelIndex(); } return createIndex(row, 0, p->child(row)); } bool KateFileTreeModel::hasChildren(const QModelIndex &parent) const { if (!parent.isValid()) { return m_root->childCount() > 0; } const ProxyItem *item = static_cast(parent.internalPointer()); if (!item) { return false; } return item->childCount() > 0; } bool KateFileTreeModel::isDir(const QModelIndex &index) const { if (!index.isValid()) { return true; } const ProxyItem *item = static_cast(index.internalPointer()); if (!item) { return false; } return item->flag(ProxyItem::Dir); } bool KateFileTreeModel::listMode() const { return m_listMode; } void KateFileTreeModel::setListMode(bool lm) { if (lm != m_listMode) { m_listMode = lm; clearModel(); initModel(); } } void KateFileTreeModel::documentOpened(KTextEditor::Document *doc) { ProxyItem *item = new ProxyItem(QString()); item->setDoc(doc); updateItemPathAndHost(item); setupIcon(item); handleInsert(item); m_docmap[doc] = item; connectDocument(doc); } void KateFileTreeModel::documentsOpened(const QList &docs) { for (KTextEditor::Document *doc : docs) { if (m_docmap.contains(doc)) { documentNameChanged(doc); } else { documentOpened(doc); } } } void KateFileTreeModel::documentModifiedChanged(KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; if (doc->isModified()) { item->setFlag(ProxyItem::Modified); } else { item->clearFlag(ProxyItem::Modified); item->clearFlag(ProxyItem::ModifiedExternally); item->clearFlag(ProxyItem::DeletedExternally); } setupIcon(item); const QModelIndex idx = createIndex(item->row(), 0, item); emit dataChanged(idx, idx); } void KateFileTreeModel::documentModifiedOnDisc(KTextEditor::Document *doc, bool modified, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { Q_UNUSED(modified); if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; // This didn't do what I thought it did, on an ignore // we'd get !modified causing the warning icons to disappear if (!modified) { item->clearFlag(ProxyItem::ModifiedExternally); item->clearFlag(ProxyItem::DeletedExternally); } else { if (reason == KTextEditor::ModificationInterface::OnDiskDeleted) { item->setFlag(ProxyItem::DeletedExternally); } else if (reason == KTextEditor::ModificationInterface::OnDiskModified) { item->setFlag(ProxyItem::ModifiedExternally); } else if (reason == KTextEditor::ModificationInterface::OnDiskCreated) { // with out this, on "reload" we don't get the icons removed :( item->clearFlag(ProxyItem::ModifiedExternally); item->clearFlag(ProxyItem::DeletedExternally); } } setupIcon(item); const QModelIndex idx = createIndex(item->row(), 0, item); emit dataChanged(idx, idx); } void KateFileTreeModel::documentActivated(const KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; m_viewHistory.removeAll(item); m_viewHistory.prepend(item); while (m_viewHistory.count() > 10) { m_viewHistory.removeLast(); } updateBackgrounds(); } void KateFileTreeModel::documentEdited(const KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; m_editHistory.removeAll(item); m_editHistory.prepend(item); while (m_editHistory.count() > 10) { m_editHistory.removeLast(); } updateBackgrounds(); } void KateFileTreeModel::slotAboutToDeleteDocuments(const QList &docs) { for (const KTextEditor::Document *doc : docs) { disconnect(doc, &KTextEditor::Document::documentNameChanged, this, &KateFileTreeModel::documentNameChanged); disconnect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateFileTreeModel::documentNameChanged); disconnect(doc, &KTextEditor::Document::modifiedChanged, this, &KateFileTreeModel::documentModifiedChanged); disconnect(doc, SIGNAL(modifiedOnDisk(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(documentModifiedOnDisc(KTextEditor::Document *, bool, KTextEditor::ModificationInterface::ModifiedOnDiskReason))); } } void KateFileTreeModel::slotDocumentsDeleted(const QList &docs) { for (const KTextEditor::Document *doc : docs) { connectDocument(doc); } } class EditViewCount { public: EditViewCount() = default; int edit = 0; int view = 0; }; void KateFileTreeModel::updateBackgrounds(bool force) { if (!m_shadingEnabled && !force) { return; } QMap helper; int i = 1; for (ProxyItem *item : qAsConst(m_viewHistory)) { helper[item].view = i; i++; } i = 1; for (ProxyItem *item : qAsConst(m_editHistory)) { helper[item].edit = i; i++; } QMap oldBrushes = m_brushes; m_brushes.clear(); const int hc = m_viewHistory.count(); const int ec = m_editHistory.count(); for (QMap::iterator it = helper.begin(); it != helper.end(); ++it) { QColor shade(m_viewShade); QColor eshade(m_editShade); if (it.value().edit > 0) { int v = hc - it.value().view; int e = ec - it.value().edit + 1; e = e * e; const int n = qMax(v + e, 1); shade.setRgb(((shade.red() * v) + (eshade.red() * e)) / n, ((shade.green() * v) + (eshade.green() * e)) / n, ((shade.blue() * v) + (eshade.blue() * e)) / n); } // blend in the shade color; latest is most colored. const double t = double(hc - it.value().view + 1) / double(hc); m_brushes[it.key()] = QBrush(KColorUtils::mix(QPalette().color(QPalette::Base), shade, t)); } for (auto it = m_brushes.constBegin(), end = m_brushes.constEnd(); it != end; ++it) { ProxyItem *item = it.key(); oldBrushes.remove(item); const QModelIndex idx = createIndex(item->row(), 0, item); dataChanged(idx, idx); } for (auto it = oldBrushes.constBegin(), end = oldBrushes.constEnd(); it != end; ++it) { ProxyItem *item = it.key(); const QModelIndex idx = createIndex(item->row(), 0, item); dataChanged(idx, idx); } } void KateFileTreeModel::handleEmptyParents(ProxyItemDir *item) { Q_ASSERT(item != nullptr); if (!item->parent()) { return; } ProxyItemDir *parent = item->parent(); while (parent) { if (!item->childCount()) { const QModelIndex parent_index = (parent == m_root) ? QModelIndex() : createIndex(parent->row(), 0, parent); beginRemoveRows(parent_index, item->row(), item->row()); parent->remChild(item); endRemoveRows(); delete item; } else { // breakout early, if this node isn't empty, theres no use in checking its parents return; } item = parent; parent = item->parent(); } } void KateFileTreeModel::documentClosed(KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } if (m_shadingEnabled) { ProxyItem *toRemove = m_docmap[doc]; if (m_brushes.contains(toRemove)) { m_brushes.remove(toRemove); } if (m_viewHistory.contains(toRemove)) { m_viewHistory.removeAll(toRemove); } if (m_editHistory.contains(toRemove)) { m_editHistory.removeAll(toRemove); } } ProxyItem *node = m_docmap[doc]; ProxyItemDir *parent = node->parent(); const QModelIndex parent_index = (parent == m_root) ? QModelIndex() : createIndex(parent->row(), 0, parent); beginRemoveRows(parent_index, node->row(), node->row()); node->parent()->remChild(node); endRemoveRows(); delete node; handleEmptyParents(parent); m_docmap.remove(doc); } void KateFileTreeModel::documentNameChanged(KTextEditor::Document *doc) { if (!m_docmap.contains(doc)) { return; } ProxyItem *item = m_docmap[doc]; if (m_shadingEnabled) { ProxyItem *toRemove = m_docmap[doc]; if (m_brushes.contains(toRemove)) { QBrush brush = m_brushes[toRemove]; m_brushes.remove(toRemove); m_brushes.insert(item, brush); } if (m_viewHistory.contains(toRemove)) { const int idx = m_viewHistory.indexOf(toRemove); if (idx != -1) { m_viewHistory.replace(idx, item); } } if (m_editHistory.contains(toRemove)) { const int idx = m_editHistory.indexOf(toRemove); if (idx != -1) { m_editHistory.replace(idx, item); } } } handleNameChange(item); emit triggerViewChangeAfterNameChange(); // FIXME: heh, non-standard signal? } ProxyItemDir *KateFileTreeModel::findRootNode(const QString &name, const int r) const { const auto rootChildren = m_root->children(); for (ProxyItem *item : rootChildren) { if (!item->flag(ProxyItem::Dir)) { continue; } // make sure we're actually matching against the right dir, // previously the check below would match /foo/xy against /foo/x // and return /foo/x rather than /foo/xy // this seems a bit hackish, but is the simplest way to solve the // current issue. QString path = item->path().section(QLatin1Char('/'), 0, -r) + QLatin1Char('/'); if (name.startsWith(path)) { return static_cast(item); } } return nullptr; } ProxyItemDir *KateFileTreeModel::findChildNode(const ProxyItemDir *parent, const QString &name) const { Q_ASSERT(parent != nullptr); Q_ASSERT(!name.isEmpty()); if (!parent->childCount()) { return nullptr; } const auto children = parent->children(); for (ProxyItem *item : children) { if (!item->flag(ProxyItem::Dir)) { continue; } if (item->display() == name) { return static_cast(item); } } return nullptr; } void KateFileTreeModel::insertItemInto(ProxyItemDir *root, ProxyItem *item) { Q_ASSERT(root != nullptr); Q_ASSERT(item != nullptr); QString tail = item->path(); tail.remove(0, root->path().length()); QStringList parts = tail.split(QLatin1Char('/'), QString::SkipEmptyParts); ProxyItemDir *ptr = root; QStringList current_parts; current_parts.append(root->path()); // seems this can be empty, see bug 286191 if (!parts.isEmpty()) { parts.pop_back(); } for (const QString &part : qAsConst(parts)) { current_parts.append(part); ProxyItemDir *find = findChildNode(ptr, part); if (!find) { const QString new_name = current_parts.join(QLatin1Char('/')); const QModelIndex parent_index = (ptr == m_root) ? QModelIndex() : createIndex(ptr->row(), 0, ptr); beginInsertRows(parent_index, ptr->childCount(), ptr->childCount()); ptr = new ProxyItemDir(new_name, ptr); endInsertRows(); } else { ptr = find; } } const QModelIndex parent_index = (ptr == m_root) ? QModelIndex() : createIndex(ptr->row(), 0, ptr); beginInsertRows(parent_index, ptr->childCount(), ptr->childCount()); ptr->addChild(item); endInsertRows(); } void KateFileTreeModel::handleInsert(ProxyItem *item) { Q_ASSERT(item != nullptr); if (m_listMode || item->flag(ProxyItem::Empty)) { beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount()); m_root->addChild(item); endInsertRows(); return; } // case (item.path > root.path) ProxyItemDir *root = findRootNode(item->path()); if (root) { insertItemInto(root, item); return; } // trim off trailing file and dir QString base = item->path().section(QLatin1Char('/'), 0, -2); // create new root ProxyItemDir *new_root = new ProxyItemDir(base); new_root->setHost(item->host()); // add new root to m_root beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount()); m_root->addChild(new_root); endInsertRows(); // same fix as in findRootNode, try to match a full dir, instead of a partial path base += QLatin1Char('/'); // try and merge existing roots with the new root node (new_root.path < root.path) const auto rootChildren = m_root->children(); for (ProxyItem *root : rootChildren) { if (root == new_root || !root->flag(ProxyItem::Dir)) { continue; } if (root->path().startsWith(base)) { beginRemoveRows(QModelIndex(), root->row(), root->row()); m_root->remChild(root); endRemoveRows(); // beginInsertRows(new_root_index, new_root->childCount(), new_root->childCount()); // this can't use new_root->addChild directly, or it'll potentially miss a bunch of subdirs insertItemInto(new_root, root); // endInsertRows(); } } // add item to new root // have to call begin/endInsertRows here, or the new item won't show up. const QModelIndex new_root_index = createIndex(new_root->row(), 0, new_root); beginInsertRows(new_root_index, new_root->childCount(), new_root->childCount()); new_root->addChild(item); endInsertRows(); handleDuplicitRootDisplay(new_root); } void KateFileTreeModel::handleDuplicitRootDisplay(ProxyItemDir *init) { QStack rootsToCheck; rootsToCheck.push(init); // make sure the roots don't match (recursively) while (!rootsToCheck.isEmpty()) { ProxyItemDir *check_root = rootsToCheck.pop(); if (check_root->parent() != m_root) { continue; } const auto rootChildren = m_root->children(); for (ProxyItem *root : rootChildren) { if (root == check_root || !root->flag(ProxyItem::Dir)) { continue; } if (check_root->display() == root->display()) { bool changed = false; bool check_root_removed = false; const QString rdir = root->path().section(QLatin1Char('/'), 0, -2); if (!rdir.isEmpty()) { beginRemoveRows(QModelIndex(), root->row(), root->row()); m_root->remChild(root); endRemoveRows(); ProxyItemDir *irdir = new ProxyItemDir(rdir); beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount()); m_root->addChild(irdir); endInsertRows(); insertItemInto(irdir, root); const auto children = m_root->children(); for (ProxyItem *node : children) { if (node == irdir || !root->flag(ProxyItem::Dir)) { continue; } const QString xy = rdir + QLatin1Char('/'); if (node->path().startsWith(xy)) { beginRemoveRows(QModelIndex(), node->row(), node->row()); // check_root_removed must be sticky check_root_removed = check_root_removed || (node == check_root); m_root->remChild(node); endRemoveRows(); insertItemInto(irdir, node); } } rootsToCheck.push(irdir); changed = true; } if (!check_root_removed) { const QString nrdir = check_root->path().section(QLatin1Char('/'), 0, -2); if (!nrdir.isEmpty()) { beginRemoveRows(QModelIndex(), check_root->row(), check_root->row()); m_root->remChild(check_root); endRemoveRows(); ProxyItemDir *irdir = new ProxyItemDir(nrdir); beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount()); m_root->addChild(irdir); endInsertRows(); insertItemInto(irdir, check_root); rootsToCheck.push(irdir); changed = true; } } if (changed) { break; // restart } } - } // foreach root + } // for root } } void KateFileTreeModel::handleNameChange(ProxyItem *item) { Q_ASSERT(item != nullptr); Q_ASSERT(item->parent()); updateItemPathAndHost(item); if (m_listMode) { const QModelIndex idx = createIndex(item->row(), 0, item); setupIcon(item); emit dataChanged(idx, idx); return; } // in either case (new/change) we want to remove the item from its parent ProxyItemDir *parent = item->parent(); const QModelIndex parent_index = (parent == m_root) ? QModelIndex() : createIndex(parent->row(), 0, parent); beginRemoveRows(parent_index, item->row(), item->row()); parent->remChild(item); endRemoveRows(); handleEmptyParents(parent); // clear all but Empty flag if (item->flag(ProxyItem::Empty)) { item->setFlags(ProxyItem::Empty); } else { item->setFlags(ProxyItem::None); } setupIcon(item); handleInsert(item); } void KateFileTreeModel::updateItemPathAndHost(ProxyItem *item) const { const KTextEditor::Document *doc = item->doc(); Q_ASSERT(doc); // this method should not be called at directory items QString path = doc->url().path(); QString host; if (doc->url().isEmpty()) { path = doc->documentName(); item->setFlag(ProxyItem::Empty); } else { item->clearFlag(ProxyItem::Empty); host = doc->url().host(); if (!host.isEmpty()) { path = QStringLiteral("[%1]%2").arg(host, path); } } // for some reason we get useless name changes [should be fixed in 5.0] if (item->path() == path) { return; } item->setPath(path); item->setHost(host); } void KateFileTreeModel::setupIcon(ProxyItem *item) const { Q_ASSERT(item != nullptr); QString icon_name; if (item->flag(ProxyItem::Modified)) { icon_name = QStringLiteral("document-save"); } else { const QUrl url(item->path()); icon_name = QMimeDatabase().mimeTypeForFile(url.path(), QMimeDatabase::MatchExtension).iconName(); } QIcon icon = QIcon::fromTheme(icon_name); if (item->flag(ProxyItem::ModifiedExternally) || item->flag(ProxyItem::DeletedExternally)) { icon = KIconUtils::addOverlay(icon, QIcon(QLatin1String("emblem-important")), Qt::TopLeftCorner); } item->setIcon(icon); } void KateFileTreeModel::resetHistory() { QSet list = QSet::fromList(m_viewHistory); list += QSet::fromList(m_editHistory); m_viewHistory.clear(); m_editHistory.clear(); m_brushes.clear(); for (ProxyItem *item : qAsConst(list)) { QModelIndex idx = createIndex(item->row(), 0, item); dataChanged(idx, idx, QVector(1, Qt::BackgroundRole)); } } diff --git a/addons/konsole/kateconsole.cpp b/addons/konsole/kateconsole.cpp index b21a29df7..93a808504 100644 --- a/addons/konsole/kateconsole.cpp +++ b/addons/konsole/kateconsole.cpp @@ -1,500 +1,500 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2002 Joseph Wenninger Copyright (C) 2002 Anders Lund Copyright (C) 2007 Anders Lund Copyright (C) 2017 Ederag This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateconsole.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KateKonsolePluginFactory, "katekonsoleplugin.json", registerPlugin();) KateKonsolePlugin::KateKonsolePlugin(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { m_previousEditorEnv = qgetenv("EDITOR"); if (!KAuthorized::authorize(QStringLiteral("shell_access"))) { KMessageBox::sorry(nullptr, i18n("You do not have enough karma to access a shell or terminal emulation")); } } KateKonsolePlugin::~KateKonsolePlugin() { qputenv("EDITOR", m_previousEditorEnv.data()); } QObject *KateKonsolePlugin::createView(KTextEditor::MainWindow *mainWindow) { KateKonsolePluginView *view = new KateKonsolePluginView(this, mainWindow); return view; } KTextEditor::ConfigPage *KateKonsolePlugin::configPage(int number, QWidget *parent) { if (number != 0) return nullptr; return new KateKonsoleConfigPage(parent, this); } void KateKonsolePlugin::readConfig() { - foreach (KateKonsolePluginView *view, mViews) + for (KateKonsolePluginView *view : qAsConst(mViews)) view->readConfig(); } KateKonsolePluginView::KateKonsolePluginView(KateKonsolePlugin *plugin, KTextEditor::MainWindow *mainWindow) : QObject(mainWindow) , m_plugin(plugin) { // init console QWidget *toolview = mainWindow->createToolView(plugin, QStringLiteral("kate_private_plugin_katekonsoleplugin"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("utilities-terminal")), i18n("Terminal")); m_console = new KateConsole(m_plugin, mainWindow, toolview); // register this view m_plugin->mViews.append(this); } KateKonsolePluginView::~KateKonsolePluginView() { // unregister this view m_plugin->mViews.removeAll(this); // cleanup, kill toolview + console QWidget *toolview = m_console->parentWidget(); delete m_console; delete toolview; } void KateKonsolePluginView::readConfig() { m_console->readConfig(); } KateConsole::KateConsole(KateKonsolePlugin *plugin, KTextEditor::MainWindow *mw, QWidget *parent) : QWidget(parent) , m_part(nullptr) , m_mw(mw) , m_toolView(parent) , m_plugin(plugin) { KXMLGUIClient::setComponentName(QStringLiteral("katekonsole"), i18n("Kate Terminal")); setXMLFile(QStringLiteral("ui.rc")); // make sure we have a vertical layout new QVBoxLayout(this); layout()->setContentsMargins(0, 0, 0, 0); QAction *a = actionCollection()->addAction(QStringLiteral("katekonsole_tools_pipe_to_terminal")); a->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); a->setText(i18nc("@action", "&Pipe to Terminal")); connect(a, &QAction::triggered, this, &KateConsole::slotPipeToConsole); a = actionCollection()->addAction(QStringLiteral("katekonsole_tools_sync")); a->setText(i18nc("@action", "S&ynchronize Terminal with Current Document")); connect(a, &QAction::triggered, this, &KateConsole::slotManualSync); a = actionCollection()->addAction(QStringLiteral("katekonsole_tools_run")); a->setText(i18nc("@action", "Run Current Document")); connect(a, &QAction::triggered, this, &KateConsole::slotRun); a = actionCollection()->addAction(QStringLiteral("katekonsole_tools_toggle_focus")); a->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); a->setText(i18nc("@action", "&Focus Terminal")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::Key_F4)); connect(a, &QAction::triggered, this, &KateConsole::slotToggleFocus); m_mw->guiFactory()->addClient(this); readConfig(); } KateConsole::~KateConsole() { m_mw->guiFactory()->removeClient(this); if (m_part) disconnect(m_part, &KParts::ReadOnlyPart::destroyed, this, &KateConsole::slotDestroyed); } void KateConsole::loadConsoleIfNeeded() { if (m_part) return; if (!window() || !parentWidget()) return; if (!window() || !isVisibleTo(window())) return; /** * get konsole part factory */ KPluginFactory *factory = KPluginLoader(QStringLiteral("konsolepart")).factory(); if (!factory) return; m_part = factory->create(this, this); if (!m_part) return; layout()->addWidget(m_part->widget()); // start the terminal qobject_cast(m_part)->showShellInDir(QString()); // KGlobal::locale()->insertCatalog("konsole"); // FIXME KF5: insert catalog setFocusProxy(m_part->widget()); m_part->widget()->show(); connect(m_part, &KParts::ReadOnlyPart::destroyed, this, &KateConsole::slotDestroyed); connect(m_part, SIGNAL(overrideShortcut(QKeyEvent *, bool &)), this, SLOT(overrideShortcut(QKeyEvent *, bool &))); slotSync(); } void KateConsole::slotDestroyed() { m_part = nullptr; m_currentPath.clear(); setFocusProxy(nullptr); // hide the dockwidget if (parentWidget()) { m_mw->hideToolView(m_toolView); } } void KateConsole::overrideShortcut(QKeyEvent *, bool &override) { /** * let konsole handle all shortcuts */ override = true; } void KateConsole::showEvent(QShowEvent *) { if (m_part) return; loadConsoleIfNeeded(); } void KateConsole::cd(const QString &path) { if (m_currentPath == path) return; if (!m_part) return; m_currentPath = path; QString command = QLatin1String(" cd ") + KShell::quoteArg(m_currentPath) + QLatin1Char('\n'); // special handling for some interpreters TerminalInterface *t = qobject_cast(m_part); if (t) { // ghci doesn't allow \space dir names, does allow spaces in dir names // irb can take spaces or \space but doesn't allow " 'path' " if (t->foregroundProcessName() == QLatin1String("irb")) { command = QLatin1String("Dir.chdir(\"") + path + QLatin1String("\") \n"); } else if (t->foregroundProcessName() == QLatin1String("ghc")) { command = QLatin1String(":cd ") + path + QLatin1Char('\n'); } } // Send prior Ctrl-E, Ctrl-U to ensure the line is empty sendInput(QStringLiteral("\x05\x15")); sendInput(command); } void KateConsole::sendInput(const QString &text) { loadConsoleIfNeeded(); if (!m_part) return; TerminalInterface *t = qobject_cast(m_part); if (!t) return; t->sendInput(text); } void KateConsole::slotPipeToConsole() { if (KMessageBox::warningContinueCancel(m_mw->window(), i18n("Do you really want to pipe the text to the console? This will execute any contained commands with your user rights."), i18n("Pipe to Terminal?"), KGuiItem(i18n("Pipe to Terminal")), KStandardGuiItem::cancel(), QStringLiteral("Pipe To Terminal Warning")) != KMessageBox::Continue) return; KTextEditor::View *v = m_mw->activeView(); if (!v) return; if (v->selection()) sendInput(v->selectionText()); else sendInput(v->document()->text()); } void KateConsole::slotSync(KTextEditor::View *) { if (m_mw->activeView()) { QUrl u = m_mw->activeView()->document()->url(); if (u.isValid() && u.isLocalFile()) { QFileInfo fi(u.toLocalFile()); cd(fi.absolutePath()); } else if (!u.isEmpty()) { sendInput(QStringLiteral("### ") + i18n("Sorry, cannot cd into '%1'", u.toLocalFile()) + QLatin1Char('\n')); } } } void KateConsole::slotManualSync() { m_currentPath.clear(); slotSync(); if (!m_part || !m_part->widget()->isVisible()) m_mw->showToolView(parentWidget()); } void KateConsole::slotRun() { if (m_mw->activeView()) { KTextEditor::Document *document = m_mw->activeView()->document(); QUrl u = document->url(); if (!u.isLocalFile()) { QPointer message = new KTextEditor::Message(i18n("Not a local file: '%1'", u.path()), KTextEditor::Message::Error); // auto hide is enabled and set to a sane default value of several seconds. message->setAutoHide(2000); message->setAutoHideMode(KTextEditor::Message::Immediate); document->postMessage(message); return; } // ensure that file is saved if (document->isModified()) { document->save(); } // The string that should be output to terminal, upon acceptance QString output_str; // prefix first output_str += KConfigGroup(KSharedConfig::openConfig(), "Konsole").readEntry("RunPrefix", ""); // then filename if (KConfigGroup(KSharedConfig::openConfig(), "Konsole").readEntry("RemoveExtension", true)) { // append filename without extension (i.e. keep only the basename) output_str += QFileInfo(u.path()).baseName() + QLatin1Char('\n'); } else { // append filename to the terminal output_str += QFileInfo(u.path()).fileName() + QLatin1Char('\n'); } if (KMessageBox::Continue != KMessageBox::warningContinueCancel(m_mw->window(), i18n("Do you really want to Run the document ?\n" "This will execute the following command,\n" "with your user rights, in the terminal:\n" "'%1'", output_str), i18n("Run in Terminal?"), KGuiItem(i18n("Run")), KStandardGuiItem::cancel(), QStringLiteral("Konsole: Run in Terminal Warning"))) { return; } // echo to terminal sendInput(output_str); } } void KateConsole::slotToggleFocus() { QAction *action = actionCollection()->action(QStringLiteral("katekonsole_tools_toggle_focus")); if (!m_part) { m_mw->showToolView(parentWidget()); action->setText(i18n("Defocus Terminal")); return; // this shows and focuses the konsole } if (!m_part) return; if (m_part->widget()->hasFocus()) { if (m_mw->activeView()) m_mw->activeView()->setFocus(); action->setText(i18n("Focus Terminal")); } else { // show the view if it is hidden if (parentWidget()->isHidden()) m_mw->showToolView(parentWidget()); else // should focus the widget too! m_part->widget()->setFocus(Qt::OtherFocusReason); action->setText(i18n("Defocus Terminal")); } } void KateConsole::readConfig() { disconnect(m_mw, &KTextEditor::MainWindow::viewChanged, this, &KateConsole::slotSync); if (KConfigGroup(KSharedConfig::openConfig(), "Konsole").readEntry("AutoSyncronize", true)) { connect(m_mw, &KTextEditor::MainWindow::viewChanged, this, &KateConsole::slotSync); } if (KConfigGroup(KSharedConfig::openConfig(), "Konsole").readEntry("SetEditor", false)) qputenv("EDITOR", "kate -b"); else qputenv("EDITOR", m_plugin->previousEditorEnv().data()); } KateKonsoleConfigPage::KateKonsoleConfigPage(QWidget *parent, KateKonsolePlugin *plugin) : KTextEditor::ConfigPage(parent) , mPlugin(plugin) { QVBoxLayout *lo = new QVBoxLayout(this); lo->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); lo->setContentsMargins(0, 0, 0, 0); cbAutoSyncronize = new QCheckBox(i18n("&Automatically synchronize the terminal with the current document when possible"), this); lo->addWidget(cbAutoSyncronize); QVBoxLayout *vboxRun = new QVBoxLayout; QGroupBox *groupRun = new QGroupBox(i18n("Run in terminal"), this); // Remove extension cbRemoveExtension = new QCheckBox(i18n("&Remove extension"), this); vboxRun->addWidget(cbRemoveExtension); // Prefix QFrame *framePrefix = new QFrame(this); QHBoxLayout *hboxPrefix = new QHBoxLayout(framePrefix); QLabel *label = new QLabel(i18n("Prefix:"), framePrefix); hboxPrefix->addWidget(label); lePrefix = new QLineEdit(framePrefix); hboxPrefix->addWidget(lePrefix); vboxRun->addWidget(framePrefix); // show warning next time QFrame *frameWarn = new QFrame(this); QHBoxLayout *hboxWarn = new QHBoxLayout(frameWarn); QPushButton *buttonWarn = new QPushButton(i18n("&Show warning next time"), frameWarn); buttonWarn->setWhatsThis( i18n("The next time '%1' is executed, " "make sure a warning window will pop up, " "displaying the command to be sent to terminal, " "for review.", i18n("Run in terminal"))); connect(buttonWarn, &QPushButton::pressed, this, &KateKonsoleConfigPage::slotEnableRunWarning); hboxWarn->addWidget(buttonWarn); vboxRun->addWidget(frameWarn); groupRun->setLayout(vboxRun); lo->addWidget(groupRun); cbSetEditor = new QCheckBox(i18n("Set &EDITOR environment variable to 'kate -b'"), this); lo->addWidget(cbSetEditor); QLabel *tmp = new QLabel(this); tmp->setText(i18n("Important: The document has to be closed to make the console application continue")); lo->addWidget(tmp); reset(); lo->addStretch(); connect(cbAutoSyncronize, &QCheckBox::stateChanged, this, &KateKonsoleConfigPage::changed); connect(cbRemoveExtension, &QCheckBox::stateChanged, this, &KTextEditor::ConfigPage::changed); connect(lePrefix, &QLineEdit::textChanged, this, &KateKonsoleConfigPage::changed); connect(cbSetEditor, &QCheckBox::stateChanged, this, &KateKonsoleConfigPage::changed); } void KateKonsoleConfigPage::slotEnableRunWarning() { KMessageBox::enableMessage(QStringLiteral("Konsole: Run in Terminal Warning")); } QString KateKonsoleConfigPage::name() const { return i18n("Terminal"); } QString KateKonsoleConfigPage::fullName() const { return i18n("Terminal Settings"); } QIcon KateKonsoleConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("utilities-terminal")); } void KateKonsoleConfigPage::apply() { KConfigGroup config(KSharedConfig::openConfig(), "Konsole"); config.writeEntry("AutoSyncronize", cbAutoSyncronize->isChecked()); config.writeEntry("RemoveExtension", cbRemoveExtension->isChecked()); config.writeEntry("RunPrefix", lePrefix->text()); config.writeEntry("SetEditor", cbSetEditor->isChecked()); config.sync(); mPlugin->readConfig(); } void KateKonsoleConfigPage::reset() { KConfigGroup config(KSharedConfig::openConfig(), "Konsole"); cbAutoSyncronize->setChecked(config.readEntry("AutoSyncronize", true)); cbRemoveExtension->setChecked(config.readEntry("RemoveExtension", false)); lePrefix->setText(config.readEntry("RunPrefix", "")); cbSetEditor->setChecked(config.readEntry("SetEditor", false)); } #include "kateconsole.moc" // kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/preview/previewwidget.cpp b/addons/preview/previewwidget.cpp index 5c47f2f70..9f9248453 100644 --- a/addons/preview/previewwidget.cpp +++ b/addons/preview/previewwidget.cpp @@ -1,373 +1,373 @@ /* * Copyright (C) 2017 by Friedrich W. H. Kossebau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "previewwidget.h" #include "ktexteditorpreviewplugin.h" #include "kpartview.h" #include // KF #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qt #include #include #include #include #include #include #include using namespace KTextEditorPreview; PreviewWidget::PreviewWidget(KTextEditorPreviewPlugin *core, KTextEditor::MainWindow *mainWindow, QWidget *parent) : QStackedWidget(parent) , KXMLGUIBuilder(this) , m_core(core) , m_mainWindow(mainWindow) , m_xmlGuiFactory(new KXMLGUIFactory(this, this)) { m_lockAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("object-unlocked")), i18n("Lock Current Document"), this); m_lockAction->setToolTip(i18n("Lock preview to current document")); m_lockAction->setCheckedState(KGuiItem(i18n("Unlock Current View"), QIcon::fromTheme(QStringLiteral("object-locked")), i18n("Unlock current view"))); m_lockAction->setChecked(false); connect(m_lockAction, &QAction::triggered, this, &PreviewWidget::toggleDocumentLocking); addAction(m_lockAction); // TODO: better icon(s) const QIcon autoUpdateIcon = QIcon::fromTheme(QStringLiteral("media-playback-start")); m_autoUpdateAction = new KToggleAction(autoUpdateIcon, i18n("Automatically Update Preview"), this); m_autoUpdateAction->setToolTip(i18n("Enable automatic updates of the preview to the current document content")); m_autoUpdateAction->setCheckedState(KGuiItem(i18n("Manually Update Preview"), autoUpdateIcon, i18n("Disable automatic updates of the preview to the current document content"))); m_autoUpdateAction->setChecked(false); connect(m_autoUpdateAction, &QAction::triggered, this, &PreviewWidget::toggleAutoUpdating); addAction(m_autoUpdateAction); m_updateAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Update Preview"), this); m_updateAction->setToolTip(i18n("Update the preview to the current document content")); connect(m_updateAction, &QAction::triggered, this, &PreviewWidget::updatePreview); m_updateAction->setEnabled(false); addAction(m_updateAction); // manually prepare a proper dropdown menu button, because Qt itself does not do what one would expect // when adding a default menu->menuAction() to a QToolbar const auto kPartMenuIcon = QIcon::fromTheme(QStringLiteral("application-menu")); const auto kPartMenuText = i18n("View"); // m_kPartMenu may not be a child of this, because otherwise its XMLGUI-menu is deleted when switching views // and therefore closing the tool view, which is a QMainWindow in KDevelop (IdealController::addView). // see KXMLGUIBuilder::createContainer => tagName == d->tagMenu m_kPartMenu = new QMenu; QToolButton *toolButton = new QToolButton(); toolButton->setMenu(m_kPartMenu); toolButton->setIcon(kPartMenuIcon); toolButton->setText(kPartMenuText); toolButton->setPopupMode(QToolButton::InstantPopup); m_kPartMenuAction = new QWidgetAction(this); m_kPartMenuAction->setIcon(kPartMenuIcon); m_kPartMenuAction->setText(kPartMenuText); m_kPartMenuAction->setMenu(m_kPartMenu); m_kPartMenuAction->setDefaultWidget(toolButton); m_kPartMenuAction->setEnabled(false); addAction(m_kPartMenuAction); m_aboutKPartAction = new QAction(this); connect(m_aboutKPartAction, &QAction::triggered, this, &PreviewWidget::showAboutKPartPlugin); m_aboutKPartAction->setEnabled(false); auto label = new QLabel(i18n("No preview available."), this); label->setAlignment(Qt::AlignHCenter); addWidget(label); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &PreviewWidget::setTextEditorView); setTextEditorView(m_mainWindow->activeView()); } PreviewWidget::~PreviewWidget() { delete m_kPartMenu; } void PreviewWidget::readSessionConfig(const KConfigGroup &configGroup) { // TODO: also store document id/url and see to catch the same document on restoring config m_lockAction->setChecked(configGroup.readEntry("documentLocked", false)); m_autoUpdateAction->setChecked(configGroup.readEntry("automaticUpdate", false)); } void PreviewWidget::writeSessionConfig(KConfigGroup &configGroup) const { configGroup.writeEntry("documentLocked", m_lockAction->isChecked()); configGroup.writeEntry("automaticUpdate", m_autoUpdateAction->isChecked()); } void PreviewWidget::setTextEditorView(KTextEditor::View *view) { if ((view && view == m_previewedTextEditorView && view->document() == m_previewedTextEditorDocument && (!m_previewedTextEditorDocument || m_previewedTextEditorDocument->mode() == m_currentMode)) || !view || !isVisible() || m_lockAction->isChecked()) { return; } m_previewedTextEditorView = view; m_previewedTextEditorDocument = view ? view->document() : nullptr; resetTextEditorView(m_previewedTextEditorDocument); } void PreviewWidget::resetTextEditorView(KTextEditor::Document *document) { if (!isVisible() || m_previewedTextEditorDocument != document) { return; } KService::Ptr service; if (m_previewedTextEditorDocument) { // TODO: mimetype is not set for new documents which have not been saved yet. // Maybe retry to guess as soon as content is inserted. m_currentMode = m_previewedTextEditorDocument->mode(); // Get mimetypes assigned to the currently set mode. auto mimeTypes = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("katemoderc")), m_currentMode).readXdgListEntry("Mimetypes"); // Also try to guess from the content, if the above fails. mimeTypes << m_previewedTextEditorDocument->mimeType(); - foreach (const auto mimeType, mimeTypes) { + for (const auto &mimeType : qAsConst(mimeTypes)) { service = KMimeTypeTrader::self()->preferredService(mimeType, QStringLiteral("KParts/ReadOnlyPart")); if (service) { qCDebug(KTEPREVIEW) << "Found preferred kpart service named" << service->name() << "with library" << service->library() << "for mimetype" << mimeType; if (service->library().isEmpty()) { qCWarning(KTEPREVIEW) << "Discarding preferred kpart service due to empty library name:" << service->name(); service.reset(); } // no interest in kparts which also just display the text (like katepart itself) // TODO: what about parts which also support importing plain text and turning into richer format // and thus have it in their mimetypes list? // could that perhaps be solved by introducing the concept of "native" and "imported" mimetypes? // or making a distinction between source editors/viewers and final editors/viewers? // latter would also help other source editors/viewers like a hexeditor, which "supports" any mimetype if (service && service->mimeTypes().contains(QLatin1String("text/plain"))) { qCDebug(KTEPREVIEW) << "Blindly discarding preferred service as it also supports text/plain, to avoid useless plain/text preview."; service.reset(); } if (service) { break; } } } if (!service) { qCDebug(KTEPREVIEW) << "Found no preferred kpart service for mimetypes" << mimeTypes; } // Update if the mode is changed. The signal may also be emitted, when a new // url is loaded, therefore wait (QueuedConnection) for the document to load. connect(m_previewedTextEditorDocument, &KTextEditor::Document::modeChanged, this, &PreviewWidget::resetTextEditorView, static_cast(Qt::QueuedConnection | Qt::UniqueConnection)); // Explicitly clear the old document, which otherwise might be accessed in // m_partView->setDocument. connect(m_previewedTextEditorDocument, &KTextEditor::Document::aboutToClose, this, &PreviewWidget::unsetDocument, Qt::UniqueConnection); } else { m_currentMode.clear(); } // change of preview type? // TODO: find a better id than library? const QString serviceId = service ? service->library() : QString(); if (serviceId != m_currentServiceId) { if (m_partView) { clearMenu(); } m_currentServiceId = serviceId; if (service) { qCDebug(KTEPREVIEW) << "Creating new kpart service instance."; m_partView = new KPartView(service, this); const bool autoupdate = m_autoUpdateAction->isChecked(); m_partView->setAutoUpdating(autoupdate); int index = addWidget(m_partView->widget()); setCurrentIndex(index); // update kpart menu const auto kPart = m_partView->kPart(); if (kPart) { m_xmlGuiFactory->addClient(kPart); const auto kPartDisplayName = kPart->componentData().displayName(); m_aboutKPartAction->setText(i18n("About %1", kPartDisplayName)); m_aboutKPartAction->setEnabled(true); m_kPartMenu->addSeparator(); m_kPartMenu->addAction(m_aboutKPartAction); m_kPartMenuAction->setEnabled(true); } m_updateAction->setEnabled(!autoupdate); } else { m_partView = nullptr; } } else if (m_partView) { qCDebug(KTEPREVIEW) << "Reusing active kpart service instance."; } if (m_partView) { m_partView->setDocument(m_previewedTextEditorDocument); } } void PreviewWidget::unsetDocument(KTextEditor::Document *document) { if (!m_partView || m_previewedTextEditorDocument != document) { return; } m_partView->setDocument(nullptr); m_previewedTextEditorDocument = nullptr; // remove any current partview clearMenu(); m_partView = nullptr; m_currentServiceId.clear(); } void PreviewWidget::showEvent(QShowEvent *event) { Q_UNUSED(event); m_updateAction->setEnabled(m_partView && !m_autoUpdateAction->isChecked()); if (m_lockAction->isChecked()) { resetTextEditorView(m_previewedTextEditorDocument); } else { setTextEditorView(m_mainWindow->activeView()); } } void PreviewWidget::hideEvent(QHideEvent *event) { Q_UNUSED(event); // keep active part for reuse, but close preview document // TODO: we also get hide event in kdevelop when the view is changed, // need to find out how to filter this out or how to fix kdevelop // so currently keep the preview document // unsetDocument(m_previewedTextEditorDocument); m_updateAction->setEnabled(false); } void PreviewWidget::toggleDocumentLocking(bool locked) { if (!locked) { setTextEditorView(m_mainWindow->activeView()); } } void PreviewWidget::toggleAutoUpdating(bool autoRefreshing) { if (!m_partView) { // nothing to do return; } m_updateAction->setEnabled(!autoRefreshing && isVisible()); m_partView->setAutoUpdating(autoRefreshing); } void PreviewWidget::updatePreview() { if (m_partView && m_partView->document()) { m_partView->updatePreview(); } } QWidget *PreviewWidget::createContainer(QWidget *parent, int index, const QDomElement &element, QAction *&containerAction) { containerAction = nullptr; if (element.attribute(QStringLiteral("deleted")).toLower() == QLatin1String("true")) { return nullptr; } const QString tagName = element.tagName().toLower(); // filter out things we do not support // TODO: consider integrating the toolbars if (tagName == QLatin1String("mainwindow") || tagName == QLatin1String("toolbar") || tagName == QLatin1String("statusbar")) { return nullptr; } if (tagName == QLatin1String("menubar")) { return m_kPartMenu; } return KXMLGUIBuilder::createContainer(parent, index, element, containerAction); } void PreviewWidget::removeContainer(QWidget *container, QWidget *parent, QDomElement &element, QAction *containerAction) { if (container == m_kPartMenu) { return; } KXMLGUIBuilder::removeContainer(container, parent, element, containerAction); } void PreviewWidget::showAboutKPartPlugin() { if (m_partView && m_partView->kPart()) { QPointer aboutDialog = new KAboutApplicationDialog(m_partView->kPart()->componentData(), this); aboutDialog->exec(); delete aboutDialog; } } void PreviewWidget::clearMenu() { // clear kpart menu m_xmlGuiFactory->removeClient(m_partView->kPart()); m_kPartMenu->clear(); removeWidget(m_partView->widget()); delete m_partView; m_updateAction->setEnabled(false); m_kPartMenuAction->setEnabled(false); m_aboutKPartAction->setEnabled(false); } diff --git a/addons/project/kateprojectpluginview.cpp b/addons/project/kateprojectpluginview.cpp index ce1c50230..a3e3f416d 100644 --- a/addons/project/kateprojectpluginview.cpp +++ b/addons/project/kateprojectpluginview.cpp @@ -1,472 +1,472 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kateprojectpluginview.h" #include "kateprojectinfoviewindex.h" #include "fileutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KateProjectPluginFactory, "kateprojectplugin.json", registerPlugin();) KateProjectPluginView::KateProjectPluginView(KateProjectPlugin *plugin, KTextEditor::MainWindow *mainWin) : QObject(mainWin) , m_plugin(plugin) , m_mainWindow(mainWin) , m_toolView(nullptr) , m_toolInfoView(nullptr) , m_lookupAction(nullptr) { KXMLGUIClient::setComponentName(QStringLiteral("kateproject"), i18n("Kate Project Manager")); setXMLFile(QStringLiteral("ui.rc")); /** * create toolviews */ m_toolView = m_mainWindow->createToolView(m_plugin, QStringLiteral("kateproject"), KTextEditor::MainWindow::Left, QIcon::fromTheme(QStringLiteral("project-open")), i18n("Projects")); m_toolInfoView = m_mainWindow->createToolView(m_plugin, QStringLiteral("kateprojectinfo"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("view-choose")), i18n("Current Project")); /** * create the combo + buttons for the toolViews + stacked widgets */ m_projectsCombo = new QComboBox(m_toolView); m_projectsCombo->setFrame(false); m_reloadButton = new QToolButton(m_toolView); m_reloadButton->setAutoRaise(true); m_reloadButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); QHBoxLayout *layout = new QHBoxLayout(); layout->setSpacing(0); layout->addWidget(m_projectsCombo); layout->addWidget(m_reloadButton); m_toolView->layout()->addItem(layout); m_toolView->layout()->setSpacing(0); m_stackedProjectViews = new QStackedWidget(m_toolView); m_stackedProjectInfoViews = new QStackedWidget(m_toolInfoView); connect(m_projectsCombo, static_cast(&QComboBox::currentIndexChanged), this, &KateProjectPluginView::slotCurrentChanged); connect(m_reloadButton, &QToolButton::clicked, this, &KateProjectPluginView::slotProjectReload); /** * create views for all already existing projects * will create toolviews on demand! */ foreach (KateProject *project, m_plugin->projects()) viewForProject(project); /** * connect to important signals, e.g. for auto project view creation */ connect(m_plugin, &KateProjectPlugin::projectCreated, this, &KateProjectPluginView::viewForProject); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KateProjectPluginView::slotViewChanged); connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, &KateProjectPluginView::slotViewCreated); /** * connect for all already existing views */ foreach (KTextEditor::View *view, m_mainWindow->views()) slotViewCreated(view); /** * trigger once view change, to highlight right document */ slotViewChanged(); /** * back + forward */ auto a = actionCollection()->addAction(KStandardAction::Back, QStringLiteral("projects_prev_project"), this, SLOT(slotProjectPrev())); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Left)); a = actionCollection()->addAction(KStandardAction::Forward, QStringLiteral("projects_next_project"), this, SLOT(slotProjectNext())); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Right)); a = actionCollection()->addAction(KStandardAction::Goto, QStringLiteral("projects_goto_index"), this, SLOT(slotProjectIndex())); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_1)); // popup menu auto popup = new KActionMenu(i18n("Project"), this); actionCollection()->addAction(QStringLiteral("popup_project"), popup); m_lookupAction = popup->menu()->addAction(i18n("Lookup: %1", QString()), this, &KateProjectPluginView::slotProjectIndex); connect(popup->menu(), &QMenu::aboutToShow, this, &KateProjectPluginView::slotContextMenuAboutToShow); /** * add us to gui */ m_mainWindow->guiFactory()->addClient(this); } KateProjectPluginView::~KateProjectPluginView() { /** * cleanup for all views */ - foreach (QObject *view, m_textViews) { + for (QObject *view : qAsConst(m_textViews)) { KTextEditor::CodeCompletionInterface *cci = qobject_cast(view); if (cci) { cci->unregisterCompletionModel(m_plugin->completion()); } } /** * cu toolviews */ delete m_toolView; m_toolView = nullptr; delete m_toolInfoView; m_toolInfoView = nullptr; /** * cu gui client */ m_mainWindow->guiFactory()->removeClient(this); } QPair KateProjectPluginView::viewForProject(KateProject *project) { /** * needs valid project */ Q_ASSERT(project); /** * existing view? */ if (m_project2View.contains(project)) { return m_project2View.value(project); } /** * create new views */ KateProjectView *view = new KateProjectView(this, project); KateProjectInfoView *infoView = new KateProjectInfoView(this, project); /** * attach to toolboxes * first the views, then the combo, that triggers signals */ m_stackedProjectViews->addWidget(view); m_stackedProjectInfoViews->addWidget(infoView); m_projectsCombo->addItem(QIcon::fromTheme(QStringLiteral("project-open")), project->name(), project->fileName()); /** * remember and return it */ return (m_project2View[project] = QPair(view, infoView)); } QString KateProjectPluginView::projectFileName() const { QWidget *active = m_stackedProjectViews->currentWidget(); if (!active) { return QString(); } return static_cast(active)->project()->fileName(); } QString KateProjectPluginView::projectName() const { QWidget *active = m_stackedProjectViews->currentWidget(); if (!active) { return QString(); } return static_cast(active)->project()->name(); } QString KateProjectPluginView::projectBaseDir() const { QWidget *active = m_stackedProjectViews->currentWidget(); if (!active) { return QString(); } return static_cast(active)->project()->baseDir(); } QVariantMap KateProjectPluginView::projectMap() const { QWidget *active = m_stackedProjectViews->currentWidget(); if (!active) { return QVariantMap(); } return static_cast(active)->project()->projectMap(); } QStringList KateProjectPluginView::projectFiles() const { KateProjectView *active = static_cast(m_stackedProjectViews->currentWidget()); if (!active) { return QStringList(); } return active->project()->files(); } QString KateProjectPluginView::allProjectsCommonBaseDir() const { auto projects = m_plugin->projects(); if (projects.empty()) { return QString(); } if (projects.size() == 1) { return projects[0]->baseDir(); } QString commonParent1 = FileUtil::commonParent(projects[0]->baseDir(), projects[1]->baseDir()); for (int i = 2; i < projects.size(); i++) { commonParent1 = FileUtil::commonParent(commonParent1, projects[i]->baseDir()); } return commonParent1; } QStringList KateProjectPluginView::allProjectsFiles() const { QStringList fileList; foreach (auto project, m_plugin->projects()) { fileList.append(project->files()); } return fileList; } void KateProjectPluginView::slotViewChanged() { /** * get active view */ KTextEditor::View *activeView = m_mainWindow->activeView(); /** * update pointer, maybe disconnect before */ if (m_activeTextEditorView) { m_activeTextEditorView->document()->disconnect(this); } m_activeTextEditorView = activeView; /** * no current active view, return */ if (!m_activeTextEditorView) { return; } /** * connect to url changed, for auto load */ connect(m_activeTextEditorView->document(), &KTextEditor::Document::documentUrlChanged, this, &KateProjectPluginView::slotDocumentUrlChanged); /** * trigger slot once */ slotDocumentUrlChanged(m_activeTextEditorView->document()); } void KateProjectPluginView::slotCurrentChanged(int index) { // trigger change of stacked widgets m_stackedProjectViews->setCurrentIndex(index); m_stackedProjectInfoViews->setCurrentIndex(index); // update focus proxy + open currently selected document if (QWidget *current = m_stackedProjectViews->currentWidget()) { m_stackedProjectViews->setFocusProxy(current); static_cast(current)->openSelectedDocument(); } // update focus proxy if (QWidget *current = m_stackedProjectInfoViews->currentWidget()) { m_stackedProjectInfoViews->setFocusProxy(current); } // project file name might have changed emit projectFileNameChanged(); emit projectMapChanged(); } void KateProjectPluginView::slotDocumentUrlChanged(KTextEditor::Document *document) { /** * abort if empty url or no local path */ if (document->url().isEmpty() || !document->url().isLocalFile()) { return; } /** * search matching project */ KateProject *project = m_plugin->projectForUrl(document->url()); if (!project) { return; } /** * select the file FIRST */ m_project2View.value(project).first->selectFile(document->url().toLocalFile()); /** * get active project view and switch it, if it is for a different project * do this AFTER file selection */ KateProjectView *active = static_cast(m_stackedProjectViews->currentWidget()); if (active != m_project2View.value(project).first) { int index = m_projectsCombo->findData(project->fileName()); if (index >= 0) { m_projectsCombo->setCurrentIndex(index); } } } void KateProjectPluginView::slotViewCreated(KTextEditor::View *view) { /** * connect to destroyed */ connect(view, &KTextEditor::View::destroyed, this, &KateProjectPluginView::slotViewDestroyed); /** * add completion model if possible */ KTextEditor::CodeCompletionInterface *cci = qobject_cast(view); if (cci) { cci->registerCompletionModel(m_plugin->completion()); } /** * remember for this view we need to cleanup! */ m_textViews.insert(view); } void KateProjectPluginView::slotViewDestroyed(QObject *view) { /** * remove remembered views for which we need to cleanup on exit! */ m_textViews.remove(view); } void KateProjectPluginView::slotProjectPrev() { if (!m_projectsCombo->count()) { return; } if (m_projectsCombo->currentIndex() == 0) { m_projectsCombo->setCurrentIndex(m_projectsCombo->count() - 1); } else { m_projectsCombo->setCurrentIndex(m_projectsCombo->currentIndex() - 1); } } void KateProjectPluginView::slotProjectNext() { if (!m_projectsCombo->count()) { return; } if (m_projectsCombo->currentIndex() + 1 == m_projectsCombo->count()) { m_projectsCombo->setCurrentIndex(0); } else { m_projectsCombo->setCurrentIndex(m_projectsCombo->currentIndex() + 1); } } void KateProjectPluginView::slotProjectReload() { /** * force reload if any active project */ if (QWidget *current = m_stackedProjectViews->currentWidget()) { static_cast(current)->project()->reload(true); } } QString KateProjectPluginView::currentWord() const { KTextEditor::View *kv = m_activeTextEditorView; if (!kv) { return QString(); } if (kv->selection() && kv->selectionRange().onSingleLine()) { return kv->selectionText(); } return kv->document()->wordAt(kv->cursorPosition()); } void KateProjectPluginView::slotProjectIndex() { const QString word = currentWord(); if (!word.isEmpty()) { auto tabView = qobject_cast(m_stackedProjectInfoViews->currentWidget()); if (tabView) { if (auto codeIndex = tabView->findChild()) { tabView->setCurrentWidget(codeIndex); } } m_mainWindow->showToolView(m_toolInfoView); emit projectLookupWord(word); } } void KateProjectPluginView::slotContextMenuAboutToShow() { const QString word = currentWord(); if (word.isEmpty()) { return; } const QString squeezed = KStringHandler::csqueeze(word, 30); m_lookupAction->setText(i18n("Lookup: %1", squeezed)); } #include "kateprojectpluginview.moc" diff --git a/addons/project/kateprojecttreeviewcontextmenu.cpp b/addons/project/kateprojecttreeviewcontextmenu.cpp index 7bd1a93bf..522804813 100644 --- a/addons/project/kateprojecttreeviewcontextmenu.cpp +++ b/addons/project/kateprojecttreeviewcontextmenu.cpp @@ -1,155 +1,156 @@ /* This file is part of the Kate project. * * Copyright (C) 2013 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kateprojecttreeviewcontextmenu.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KateProjectTreeViewContextMenu::KateProjectTreeViewContextMenu() { } KateProjectTreeViewContextMenu::~KateProjectTreeViewContextMenu() { } static bool isGit(const QString &filename) { QFileInfo fi(filename); QDir dir(fi.absoluteDir()); QProcess git; git.setWorkingDirectory(dir.absolutePath()); QStringList args; // git ls-files -z results a bytearray where each entry is \0-terminated. // NOTE: Without -z, Umlauts such as "Der Bäcker/Das Brötchen.txt" do not work (#389415, #402213) args << QStringLiteral("ls-files") << QStringLiteral("-z") << fi.fileName(); git.start(QStringLiteral("git"), args); bool isGit = false; if (git.waitForStarted() && git.waitForFinished(-1)) { const QList byteArrayList = git.readAllStandardOutput().split('\0'); const QString fn = fi.fileName(); for (const QByteArray &byteArray : byteArrayList) { if (fn == QString::fromUtf8(byteArray)) { isGit = true; break; } } } return isGit; } void KateProjectTreeViewContextMenu::exec(const QString &filename, const QPoint &pos, QWidget *parent) { /** * Create context menu */ QMenu menu; /** * Copy Path */ QAction *copyAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy File Path")); /** * Handle "open with", * find correct mimetype to query for possible applications */ QMenu *openWithMenu = menu.addMenu(i18n("Open With")); QMimeType mimeType = QMimeDatabase().mimeTypeForFile(filename); const KService::List offers = KMimeTypeTrader::self()->query(mimeType.name(), QStringLiteral("Application")); // For each one, insert a menu item... for (const auto &service : offers) { if (service->name() == QLatin1String("Kate")) { continue; // omit Kate } QAction *action = openWithMenu->addAction(QIcon::fromTheme(service->icon()), service->name()); action->setData(service->entryPath()); } // Perhaps disable menu, if no entries openWithMenu->setEnabled(!openWithMenu->isEmpty()); /** * Open Containing folder */ auto openContaingFolderAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("&Open Containing Folder")); /** * File Properties Dialog */ auto filePropertiesAction = menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-object-properties")), i18n("Properties")); /** * Git menu */ KMoreToolsMenuFactory menuFactory(QStringLiteral("kate/addons/project/git-tools")); QMenu gitMenu; // must live as long as the maybe filled menu items should live if (isGit(filename)) { menuFactory.fillMenuFromGroupingNames(&gitMenu, {QLatin1String("git-clients-and-actions")}, QUrl::fromLocalFile(filename)); menu.addSection(i18n("Git:")); - Q_FOREACH (auto action, gitMenu.actions()) { + const auto gitActions = gitMenu.actions(); + for (auto action : gitActions) { menu.addAction(action); } } /** * run menu and handle the triggered action */ if (QAction *const action = menu.exec(pos)) { if (action == copyAction) { QApplication::clipboard()->setText(filename); } else if (action->parentWidget() == openWithMenu) { // handle "open with" const QString openWith = action->data().toString(); if (KService::Ptr app = KService::serviceByDesktopPath(openWith)) { QList list; list << QUrl::fromLocalFile(filename); KRun::runService(*app, list, parent); } } else if (action == openContaingFolderAction) { KIO::highlightInFileManager({QUrl::fromLocalFile(filename)}); } else if (action == filePropertiesAction) { // code copied and adapted from frameworks/kio/src/filewidgets/knewfilemenu.cpp KFileItem fileItem(QUrl::fromLocalFile(filename)); QDialog *dlg = new KPropertiesDialog(fileItem); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->show(); } else { // One of the git actions was triggered } } } diff --git a/addons/search/SearchDiskFiles.cpp b/addons/search/SearchDiskFiles.cpp index f20debd7d..b0f601f43 100644 --- a/addons/search/SearchDiskFiles.cpp +++ b/addons/search/SearchDiskFiles.cpp @@ -1,184 +1,184 @@ /* Kate search plugin * * Copyright (C) 2011-2013 by Kåre Särs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "SearchDiskFiles.h" #include #include #include SearchDiskFiles::SearchDiskFiles(QObject *parent) : QThread(parent) { } SearchDiskFiles::~SearchDiskFiles() { m_cancelSearch = true; wait(); } void SearchDiskFiles::startSearch(const QStringList &files, const QRegularExpression ®exp) { if (files.empty()) { emit searchDone(); return; } m_cancelSearch = false; m_files = files; m_regExp = regexp; m_matchCount = 0; m_statusTime.restart(); start(); } void SearchDiskFiles::run() { - foreach (QString fileName, m_files) { + for (const QString &fileName : qAsConst(m_files)) { if (m_cancelSearch) { break; } if (m_statusTime.elapsed() > 100) { m_statusTime.restart(); emit searching(fileName); } if (m_regExp.pattern().contains(QLatin1String("\\n"))) { searchMultiLineRegExp(fileName); } else { searchSingleLineRegExp(fileName); } } emit searchDone(); m_cancelSearch = true; } void SearchDiskFiles::cancelSearch() { m_cancelSearch = true; } bool SearchDiskFiles::searching() { return !m_cancelSearch; } void SearchDiskFiles::searchSingleLineRegExp(const QString &fileName) { QFile file(fileName); if (!file.open(QFile::ReadOnly)) { return; } QTextStream stream(&file); QString line; int i = 0; int column; QRegularExpressionMatch match; while (!(line = stream.readLine()).isNull()) { if (m_cancelSearch) break; match = m_regExp.match(line); column = match.capturedStart(); while (column != -1 && !match.captured().isEmpty()) { // limit line length if (line.length() > 1024) line = line.left(1024); QUrl fileUrl = QUrl::fromUserInput(fileName); emit matchFound(fileUrl.toString(), fileUrl.fileName(), line, match.capturedLength(), i, column, i, column + match.capturedLength()); match = m_regExp.match(line, column + match.capturedLength()); column = match.capturedStart(); m_matchCount++; // NOTE: This sleep is here so that the main thread will get a chance to // handle any stop button clicks if there are a lot of matches if (m_matchCount % 50) msleep(1); } i++; } } void SearchDiskFiles::searchMultiLineRegExp(const QString &fileName) { QFile file(fileName); int column = 0; int line = 0; static QString fullDoc; static QVector lineStart; QRegularExpression tmpRegExp = m_regExp; if (!file.open(QFile::ReadOnly)) { return; } QTextStream stream(&file); fullDoc = stream.readAll(); fullDoc.remove(QLatin1Char('\r')); lineStart.clear(); lineStart << 0; for (int i = 0; i < fullDoc.size() - 1; i++) { if (fullDoc[i] == QLatin1Char('\n')) { lineStart << i + 1; } } if (tmpRegExp.pattern().endsWith(QLatin1Char('$'))) { fullDoc += QLatin1Char('\n'); QString newPatern = tmpRegExp.pattern(); newPatern.replace(QStringLiteral("$"), QStringLiteral("(?=\\n)")); tmpRegExp.setPattern(newPatern); } QRegularExpressionMatch match; match = tmpRegExp.match(fullDoc); column = match.capturedStart(); while (column != -1 && !match.captured().isEmpty()) { if (m_cancelSearch) break; // search for the line number of the match int i; line = -1; for (i = 1; i < lineStart.size(); i++) { if (lineStart[i] > column) { line = i - 1; break; } } if (line == -1) { break; } QUrl fileUrl = QUrl::fromUserInput(fileName); int startColumn = (column - lineStart[line]); int endLine = line + match.captured().count(QLatin1Char('\n')); int lastNL = match.captured().lastIndexOf(QLatin1Char('\n')); int endColumn = lastNL == -1 ? startColumn + match.captured().length() : match.captured().length() - lastNL - 1; emit matchFound(fileUrl.toString(), fileUrl.fileName(), fullDoc.mid(lineStart[line], column - lineStart[line]) + match.captured(), match.capturedLength(), line, startColumn, endLine, endColumn); match = tmpRegExp.match(fullDoc, column + match.capturedLength()); column = match.capturedStart(); m_matchCount++; // NOTE: This sleep is here so that the main thread will get a chance to // handle any stop button clicks if there are a lot of matches if (m_matchCount % 50) msleep(1); } } diff --git a/addons/search/plugin_search.cpp b/addons/search/plugin_search.cpp index d31bf3057..9168f2efa 100644 --- a/addons/search/plugin_search.cpp +++ b/addons/search/plugin_search.cpp @@ -1,2324 +1,2325 @@ /* Kate search plugin * * Copyright (C) 2011-2013 by Kåre Särs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "plugin_search.h" #include "htmldelegate.h" #include #include #include #include #include #include #include #include #include "kacceleratormanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QUrl localFileDirUp(const QUrl &url) { if (!url.isLocalFile()) return url; // else go up return QUrl::fromLocalFile(QFileInfo(url.toLocalFile()).dir().absolutePath()); } static QAction *menuEntry(QMenu *menu, const QString &before, const QString &after, const QString &desc, QString menuBefore = QString(), QString menuAfter = QString()); /** * When the action is triggered the cursor will be placed between @p before and @p after. */ static QAction *menuEntry(QMenu *menu, const QString &before, const QString &after, const QString &desc, QString menuBefore, QString menuAfter) { if (menuBefore.isEmpty()) menuBefore = before; if (menuAfter.isEmpty()) menuAfter = after; QAction *const action = menu->addAction(menuBefore + menuAfter + QLatin1Char('\t') + desc); if (!action) return nullptr; action->setData(QString(before + QLatin1Char(' ') + after)); return action; } /** * adds items and separators for special chars in "replace" field */ static void addSpecialCharsHelperActionsForReplace(QSet *actionList, QMenu *menu) { QSet &actionPointers = *actionList; QString emptyQSTring; actionPointers << menuEntry(menu, QStringLiteral("\\n"), emptyQSTring, i18n("Line break")); actionPointers << menuEntry(menu, QStringLiteral("\\t"), emptyQSTring, i18n("Tab")); } /** * adds items and separators for regex in "search" field */ static void addRegexHelperActionsForSearch(QSet *actionList, QMenu *menu) { QSet &actionPointers = *actionList; QString emptyQSTring; actionPointers << menuEntry(menu, QStringLiteral("^"), emptyQSTring, i18n("Beginning of line")); actionPointers << menuEntry(menu, QStringLiteral("$"), emptyQSTring, i18n("End of line")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("."), emptyQSTring, i18n("Any single character (excluding line breaks)")); actionPointers << menuEntry(menu, QStringLiteral("[.]"), emptyQSTring, i18n("Literal dot")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("+"), emptyQSTring, i18n("One or more occurrences")); actionPointers << menuEntry(menu, QStringLiteral("*"), emptyQSTring, i18n("Zero or more occurrences")); actionPointers << menuEntry(menu, QStringLiteral("?"), emptyQSTring, i18n("Zero or one occurrences")); actionPointers << menuEntry(menu, QStringLiteral("{"), QStringLiteral(",}"), i18n(" through occurrences"), QStringLiteral("{a"), QStringLiteral(",b}")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("("), QStringLiteral(")"), i18n("Group, capturing")); actionPointers << menuEntry(menu, QStringLiteral("|"), emptyQSTring, i18n("Or")); actionPointers << menuEntry(menu, QStringLiteral("["), QStringLiteral("]"), i18n("Set of characters")); actionPointers << menuEntry(menu, QStringLiteral("[^"), QStringLiteral("]"), i18n("Negative set of characters")); actionPointers << menuEntry(menu, QStringLiteral("(?:"), QStringLiteral(")"), i18n("Group, non-capturing"), QStringLiteral("(?:E")); actionPointers << menuEntry(menu, QStringLiteral("(?="), QStringLiteral(")"), i18n("Lookahead"), QStringLiteral("(?=E")); actionPointers << menuEntry(menu, QStringLiteral("(?!"), QStringLiteral(")"), i18n("Negative lookahead"), QStringLiteral("(?!E")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("\\n"), emptyQSTring, i18n("Line break")); actionPointers << menuEntry(menu, QStringLiteral("\\t"), emptyQSTring, i18n("Tab")); actionPointers << menuEntry(menu, QStringLiteral("\\b"), emptyQSTring, i18n("Word boundary")); actionPointers << menuEntry(menu, QStringLiteral("\\B"), emptyQSTring, i18n("Not word boundary")); actionPointers << menuEntry(menu, QStringLiteral("\\d"), emptyQSTring, i18n("Digit")); actionPointers << menuEntry(menu, QStringLiteral("\\D"), emptyQSTring, i18n("Non-digit")); actionPointers << menuEntry(menu, QStringLiteral("\\s"), emptyQSTring, i18n("Whitespace (excluding line breaks)")); actionPointers << menuEntry(menu, QStringLiteral("\\S"), emptyQSTring, i18n("Non-whitespace (excluding line breaks)")); actionPointers << menuEntry(menu, QStringLiteral("\\w"), emptyQSTring, i18n("Word character (alphanumerics plus '_')")); actionPointers << menuEntry(menu, QStringLiteral("\\W"), emptyQSTring, i18n("Non-word character")); } /** * adds items and separators for regex in "replace" field */ static void addRegexHelperActionsForReplace(QSet *actionList, QMenu *menu) { QSet &actionPointers = *actionList; QString emptyQSTring; menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("\\0"), emptyQSTring, i18n("Regular expression capture 0 (whole match)")); actionPointers << menuEntry(menu, QStringLiteral("\\"), emptyQSTring, i18n("Regular expression capture 1-9"), QStringLiteral("\\#")); actionPointers << menuEntry(menu, QStringLiteral("\\{"), QStringLiteral("}"), i18n("Regular expression capture 0-999"), QStringLiteral("\\{#")); menu->addSeparator(); actionPointers << menuEntry(menu, QStringLiteral("\\U\\"), emptyQSTring, i18n("Upper-cased capture 0-9"), QStringLiteral("\\U\\#")); actionPointers << menuEntry(menu, QStringLiteral("\\U\\{"), QStringLiteral("}"), i18n("Upper-cased capture 0-999"), QStringLiteral("\\U\\{###")); actionPointers << menuEntry(menu, QStringLiteral("\\L\\"), emptyQSTring, i18n("Lower-cased capture 0-9"), QStringLiteral("\\L\\#")); actionPointers << menuEntry(menu, QStringLiteral("\\L\\{"), QStringLiteral("}"), i18n("Lower-cased capture 0-999"), QStringLiteral("\\L\\{###")); } /** * inserts text and sets cursor position */ static void regexHelperActOnAction(QAction *resultAction, const QSet &actionList, QLineEdit *lineEdit) { if (resultAction && actionList.contains(resultAction)) { const int cursorPos = lineEdit->cursorPosition(); QStringList beforeAfter = resultAction->data().toString().split(QLatin1Char(' ')); if (beforeAfter.size() != 2) return; lineEdit->insert(beforeAfter[0] + beforeAfter[1]); lineEdit->setCursorPosition(cursorPos + beforeAfter[0].count()); lineEdit->setFocus(); } } class TreeWidgetItem : public QTreeWidgetItem { public: TreeWidgetItem(QTreeWidget *parent) : QTreeWidgetItem(parent) { } TreeWidgetItem(QTreeWidget *parent, const QStringList &list) : QTreeWidgetItem(parent, list) { } TreeWidgetItem(QTreeWidgetItem *parent, const QStringList &list) : QTreeWidgetItem(parent, list) { } private: bool operator<(const QTreeWidgetItem &other) const override { if (childCount() == 0) { int line = data(0, ReplaceMatches::StartLineRole).toInt(); int column = data(0, ReplaceMatches::StartColumnRole).toInt(); int oLine = other.data(0, ReplaceMatches::StartLineRole).toInt(); int oColumn = other.data(0, ReplaceMatches::StartColumnRole).toInt(); if (line < oLine) { return true; } if ((line == oLine) && (column < oColumn)) { return true; } return false; } int sepCount = data(0, ReplaceMatches::FileUrlRole).toString().count(QDir::separator()); int oSepCount = other.data(0, ReplaceMatches::FileUrlRole).toString().count(QDir::separator()); if (sepCount < oSepCount) return true; if (sepCount > oSepCount) return false; return data(0, ReplaceMatches::FileUrlRole).toString().toLower() < other.data(0, ReplaceMatches::FileUrlRole).toString().toLower(); } }; Results::Results(QWidget *parent) : QWidget(parent) { setupUi(this); tree->setItemDelegate(new SPHtmlDelegate(tree)); } K_PLUGIN_FACTORY_WITH_JSON(KatePluginSearchFactory, "katesearch.json", registerPlugin();) KatePluginSearch::KatePluginSearch(QObject *parent, const QList &) : KTextEditor::Plugin(parent) { m_searchCommand = new KateSearchCommand(this); } KatePluginSearch::~KatePluginSearch() { delete m_searchCommand; } QObject *KatePluginSearch::createView(KTextEditor::MainWindow *mainWindow) { KatePluginSearchView *view = new KatePluginSearchView(this, mainWindow, KTextEditor::Editor::instance()->application()); connect(m_searchCommand, &KateSearchCommand::setSearchPlace, view, &KatePluginSearchView::setSearchPlace); connect(m_searchCommand, &KateSearchCommand::setCurrentFolder, view, &KatePluginSearchView::setCurrentFolder); connect(m_searchCommand, &KateSearchCommand::setSearchString, view, &KatePluginSearchView::setSearchString); connect(m_searchCommand, &KateSearchCommand::startSearch, view, &KatePluginSearchView::startSearch); connect(m_searchCommand, SIGNAL(newTab()), view, SLOT(addTab())); return view; } bool ContainerWidget::focusNextPrevChild(bool next) { QWidget *fw = focusWidget(); bool found = false; emit nextFocus(fw, &found, next); if (found) { return true; } return QWidget::focusNextPrevChild(next); } void KatePluginSearchView::nextFocus(QWidget *currentWidget, bool *found, bool next) { *found = false; if (!currentWidget) { return; } // we use the object names here because there can be multiple replaceButtons (on multiple result tabs) if (next) { if (currentWidget->objectName() == QLatin1String("tree") || currentWidget == m_ui.binaryCheckBox) { m_ui.newTabButton->setFocus(); *found = true; return; } if (currentWidget == m_ui.displayOptions) { if (m_ui.displayOptions->isChecked()) { m_ui.folderRequester->setFocus(); *found = true; return; } else { Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; } res->tree->setFocus(); *found = true; return; } } } else { if (currentWidget == m_ui.newTabButton) { if (m_ui.displayOptions->isChecked()) { m_ui.binaryCheckBox->setFocus(); } else { Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; } res->tree->setFocus(); } *found = true; return; } else { if (currentWidget->objectName() == QLatin1String("tree")) { m_ui.displayOptions->setFocus(); *found = true; return; } } } } KatePluginSearchView::KatePluginSearchView(KTextEditor::Plugin *plugin, KTextEditor::MainWindow *mainWin, KTextEditor::Application *application) : QObject(mainWin) , m_kateApp(application) , m_curResults(nullptr) , m_searchJustOpened(false) , m_switchToProjectModeWhenAvailable(false) , m_searchDiskFilesDone(true) , m_searchOpenFilesDone(true) , m_isSearchAsYouType(false) , m_isLeftRight(false) , m_projectPluginView(nullptr) , m_mainWindow(mainWin) { KXMLGUIClient::setComponentName(QStringLiteral("katesearch"), i18n("Kate Search & Replace")); setXMLFile(QStringLiteral("ui.rc")); m_toolView = mainWin->createToolView(plugin, QStringLiteral("kate_plugin_katesearch"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Search and Replace")); ContainerWidget *container = new ContainerWidget(m_toolView); m_ui.setupUi(container); container->setFocusProxy(m_ui.searchCombo); connect(container, &ContainerWidget::nextFocus, this, &KatePluginSearchView::nextFocus); QAction *a = actionCollection()->addAction(QStringLiteral("search_in_files")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_F)); a->setText(i18n("Search in Files")); connect(a, &QAction::triggered, this, &KatePluginSearchView::openSearchView); a = actionCollection()->addAction(QStringLiteral("search_in_files_new_tab")); a->setText(i18n("Search in Files (in new tab)")); // first add tab, then open search view, since open search view switches to show the search options connect(a, &QAction::triggered, this, &KatePluginSearchView::addTab); connect(a, &QAction::triggered, this, &KatePluginSearchView::openSearchView); a = actionCollection()->addAction(QStringLiteral("go_to_next_match")); a->setText(i18n("Go to Next Match")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::Key_F6)); connect(a, &QAction::triggered, this, &KatePluginSearchView::goToNextMatch); a = actionCollection()->addAction(QStringLiteral("go_to_prev_match")); a->setText(i18n("Go to Previous Match")); actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_F6)); connect(a, &QAction::triggered, this, &KatePluginSearchView::goToPreviousMatch); m_ui.resultTabWidget->tabBar()->setSelectionBehaviorOnRemove(QTabBar::SelectLeftTab); KAcceleratorManager::setNoAccel(m_ui.resultTabWidget); // Gnome does not seem to have all icons we want, so we use fall-back icons for those that are missing. QIcon dispOptIcon = QIcon::fromTheme(QStringLiteral("games-config-options"), QIcon::fromTheme(QStringLiteral("preferences-system"))); QIcon matchCaseIcon = QIcon::fromTheme(QStringLiteral("format-text-superscript"), QIcon::fromTheme(QStringLiteral("format-text-bold"))); QIcon useRegExpIcon = QIcon::fromTheme(QStringLiteral("code-context"), QIcon::fromTheme(QStringLiteral("edit-find-replace"))); QIcon expandResultsIcon = QIcon::fromTheme(QStringLiteral("view-list-tree"), QIcon::fromTheme(QStringLiteral("format-indent-more"))); m_ui.displayOptions->setIcon(dispOptIcon); m_ui.searchButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); m_ui.nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); m_ui.stopButton->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); m_ui.matchCase->setIcon(matchCaseIcon); m_ui.useRegExp->setIcon(useRegExpIcon); m_ui.expandResults->setIcon(expandResultsIcon); m_ui.searchPlaceCombo->setItemIcon(CurrentFile, QIcon::fromTheme(QStringLiteral("text-plain"))); m_ui.searchPlaceCombo->setItemIcon(OpenFiles, QIcon::fromTheme(QStringLiteral("text-plain"))); m_ui.searchPlaceCombo->setItemIcon(Folder, QIcon::fromTheme(QStringLiteral("folder"))); m_ui.folderUpButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); m_ui.currentFolderButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); m_ui.newTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-new"))); m_ui.filterCombo->setToolTip(i18n("Comma separated list of file types to search in. Example: \"*.cpp,*.h\"\n")); m_ui.excludeCombo->setToolTip(i18n("Comma separated list of files and directories to exclude from the search. Example: \"build*\"")); // the order here is important to get the tabBar hidden for only one tab addTab(); m_ui.resultTabWidget->tabBar()->hide(); // get url-requester's combo box and sanely initialize KComboBox *cmbUrl = m_ui.folderRequester->comboBox(); cmbUrl->setDuplicatesEnabled(false); cmbUrl->setEditable(true); m_ui.folderRequester->setMode(KFile::Directory | KFile::LocalOnly); KUrlCompletion *cmpl = new KUrlCompletion(KUrlCompletion::DirCompletion); cmbUrl->setCompletionObject(cmpl); cmbUrl->setAutoDeleteCompletionObject(true); connect(m_ui.newTabButton, &QToolButton::clicked, this, &KatePluginSearchView::addTab); connect(m_ui.resultTabWidget, &QTabWidget::tabCloseRequested, this, &KatePluginSearchView::tabCloseRequested); connect(m_ui.resultTabWidget, &QTabWidget::currentChanged, this, &KatePluginSearchView::resultTabChanged); connect(m_ui.folderUpButton, &QToolButton::clicked, this, &KatePluginSearchView::navigateFolderUp); connect(m_ui.currentFolderButton, &QToolButton::clicked, this, &KatePluginSearchView::setCurrentFolder); connect(m_ui.expandResults, &QToolButton::clicked, this, &KatePluginSearchView::expandResults); connect(m_ui.searchCombo, &QComboBox::editTextChanged, &m_changeTimer, static_cast(&QTimer::start)); connect(m_ui.matchCase, &QToolButton::toggled, &m_changeTimer, static_cast(&QTimer::start)); connect(m_ui.matchCase, &QToolButton::toggled, this, [=] { Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (res) { res->matchCase = m_ui.matchCase->isChecked(); } }); connect(m_ui.searchCombo->lineEdit(), &QLineEdit::returnPressed, this, &KatePluginSearchView::startSearch); // connecting to returnPressed() of the folderRequester doesn't work, I haven't found out why yet. But connecting to the linedit works: connect(m_ui.folderRequester->comboBox()->lineEdit(), &QLineEdit::returnPressed, this, &KatePluginSearchView::startSearch); connect(m_ui.filterCombo, static_cast(&KComboBox::returnPressed), this, &KatePluginSearchView::startSearch); connect(m_ui.excludeCombo, static_cast(&KComboBox::returnPressed), this, &KatePluginSearchView::startSearch); connect(m_ui.searchButton, &QPushButton::clicked, this, &KatePluginSearchView::startSearch); connect(m_ui.displayOptions, &QToolButton::toggled, this, &KatePluginSearchView::toggleOptions); connect(m_ui.searchPlaceCombo, static_cast(&QComboBox::currentIndexChanged), this, &KatePluginSearchView::searchPlaceChanged); connect(m_ui.searchPlaceCombo, static_cast(&QComboBox::currentIndexChanged), this, [this](int) { if (m_ui.searchPlaceCombo->currentIndex() == Folder) { m_ui.displayOptions->setChecked(true); } }); connect(m_ui.stopButton, &QPushButton::clicked, &m_searchOpenFiles, &SearchOpenFiles::cancelSearch); connect(m_ui.stopButton, &QPushButton::clicked, &m_searchDiskFiles, &SearchDiskFiles::cancelSearch); connect(m_ui.stopButton, &QPushButton::clicked, &m_folderFilesList, &FolderFilesList::cancelSearch); connect(m_ui.stopButton, &QPushButton::clicked, &m_replacer, &ReplaceMatches::cancelReplace); connect(m_ui.nextButton, &QToolButton::clicked, this, &KatePluginSearchView::goToNextMatch); connect(m_ui.replaceButton, &QPushButton::clicked, this, &KatePluginSearchView::replaceSingleMatch); connect(m_ui.replaceCheckedBtn, &QPushButton::clicked, this, &KatePluginSearchView::replaceChecked); connect(m_ui.replaceCombo->lineEdit(), &QLineEdit::returnPressed, this, &KatePluginSearchView::replaceChecked); m_ui.displayOptions->setChecked(true); connect(&m_searchOpenFiles, &SearchOpenFiles::matchFound, this, &KatePluginSearchView::matchFound); connect(&m_searchOpenFiles, &SearchOpenFiles::searchDone, this, &KatePluginSearchView::searchDone); connect(&m_searchOpenFiles, static_cast(&SearchOpenFiles::searching), this, &KatePluginSearchView::searching); connect(&m_folderFilesList, &FolderFilesList::finished, this, &KatePluginSearchView::folderFileListChanged); connect(&m_folderFilesList, &FolderFilesList::searching, this, &KatePluginSearchView::searching); connect(&m_searchDiskFiles, &SearchDiskFiles::matchFound, this, &KatePluginSearchView::matchFound); connect(&m_searchDiskFiles, &SearchDiskFiles::searchDone, this, &KatePluginSearchView::searchDone); connect(&m_searchDiskFiles, static_cast(&SearchDiskFiles::searching), this, &KatePluginSearchView::searching); connect(m_kateApp, &KTextEditor::Application::documentWillBeDeleted, &m_searchOpenFiles, &SearchOpenFiles::cancelSearch); connect(m_kateApp, &KTextEditor::Application::documentWillBeDeleted, &m_replacer, &ReplaceMatches::cancelReplace); connect(m_kateApp, &KTextEditor::Application::documentWillBeDeleted, this, &KatePluginSearchView::clearDocMarks); connect(&m_replacer, &ReplaceMatches::replaceStatus, this, &KatePluginSearchView::replaceStatus); // Hook into line edit context menus m_ui.searchCombo->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_ui.searchCombo, &QComboBox::customContextMenuRequested, this, &KatePluginSearchView::searchContextMenu); m_ui.searchCombo->completer()->setCompletionMode(QCompleter::PopupCompletion); m_ui.searchCombo->completer()->setCaseSensitivity(Qt::CaseSensitive); m_ui.searchCombo->setInsertPolicy(QComboBox::NoInsert); m_ui.searchCombo->lineEdit()->setClearButtonEnabled(true); m_ui.searchCombo->setMaxCount(25); QAction *searchComboActionForInsertRegexButton = m_ui.searchCombo->lineEdit()->addAction(QIcon::fromTheme(QStringLiteral("code-context"), QIcon::fromTheme(QStringLiteral("edit-find-replace"))), QLineEdit::TrailingPosition); connect(searchComboActionForInsertRegexButton, &QAction::triggered, this, [this]() { QMenu menu; QSet actionList; addRegexHelperActionsForSearch(&actionList, &menu); auto &&action = menu.exec(QCursor::pos()); regexHelperActOnAction(action, actionList, m_ui.searchCombo->lineEdit()); }); m_ui.replaceCombo->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_ui.replaceCombo, &QComboBox::customContextMenuRequested, this, &KatePluginSearchView::replaceContextMenu); m_ui.replaceCombo->completer()->setCompletionMode(QCompleter::PopupCompletion); m_ui.replaceCombo->completer()->setCaseSensitivity(Qt::CaseSensitive); m_ui.replaceCombo->setInsertPolicy(QComboBox::NoInsert); m_ui.replaceCombo->lineEdit()->setClearButtonEnabled(true); m_ui.replaceCombo->setMaxCount(25); QAction *replaceComboActionForInsertRegexButton = m_ui.replaceCombo->lineEdit()->addAction(QIcon::fromTheme(QStringLiteral("code-context")), QLineEdit::TrailingPosition); connect(replaceComboActionForInsertRegexButton, &QAction::triggered, this, [this]() { QMenu menu; QSet actionList; addRegexHelperActionsForReplace(&actionList, &menu); auto &&action = menu.exec(QCursor::pos()); regexHelperActOnAction(action, actionList, m_ui.replaceCombo->lineEdit()); }); QAction *replaceComboActionForInsertSpecialButton = m_ui.replaceCombo->lineEdit()->addAction(QIcon::fromTheme(QStringLiteral("insert-text")), QLineEdit::TrailingPosition); connect(replaceComboActionForInsertSpecialButton, &QAction::triggered, this, [this]() { QMenu menu; QSet actionList; addSpecialCharsHelperActionsForReplace(&actionList, &menu); auto &&action = menu.exec(QCursor::pos()); regexHelperActOnAction(action, actionList, m_ui.replaceCombo->lineEdit()); }); connect(m_ui.useRegExp, &QToolButton::toggled, &m_changeTimer, static_cast(&QTimer::start)); auto onRegexToggleChanged = [=] { Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (res) { bool useRegExp = m_ui.useRegExp->isChecked(); res->useRegExp = useRegExp; searchComboActionForInsertRegexButton->setVisible(useRegExp); replaceComboActionForInsertRegexButton->setVisible(useRegExp); } }; connect(m_ui.useRegExp, &QToolButton::toggled, this, onRegexToggleChanged); onRegexToggleChanged(); // invoke initially m_changeTimer.setInterval(300); m_changeTimer.setSingleShot(true); connect(&m_changeTimer, &QTimer::timeout, this, &KatePluginSearchView::startSearchWhileTyping); m_toolView->setMinimumHeight(container->sizeHint().height()); connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, this, &KatePluginSearchView::handleEsc); // watch for project plugin view creation/deletion connect(m_mainWindow, &KTextEditor::MainWindow::pluginViewCreated, this, &KatePluginSearchView::slotPluginViewCreated); connect(m_mainWindow, &KTextEditor::MainWindow::pluginViewDeleted, this, &KatePluginSearchView::slotPluginViewDeleted); connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KatePluginSearchView::docViewChanged); // Connect signals from project plugin to our slots m_projectPluginView = m_mainWindow->pluginView(QStringLiteral("kateprojectplugin")); slotPluginViewCreated(QStringLiteral("kateprojectplugin"), m_projectPluginView); m_replacer.setDocumentManager(m_kateApp); connect(&m_replacer, &ReplaceMatches::replaceDone, this, &KatePluginSearchView::replaceDone); searchPlaceChanged(); m_toolView->installEventFilter(this); m_mainWindow->guiFactory()->addClient(this); m_updateSumaryTimer.setInterval(1); m_updateSumaryTimer.setSingleShot(true); connect(&m_updateSumaryTimer, &QTimer::timeout, this, &KatePluginSearchView::updateResultsRootItem); } KatePluginSearchView::~KatePluginSearchView() { clearMarks(); m_mainWindow->guiFactory()->removeClient(this); delete m_toolView; } void KatePluginSearchView::navigateFolderUp() { // navigate one folder up m_ui.folderRequester->setUrl(localFileDirUp(m_ui.folderRequester->url())); } void KatePluginSearchView::setCurrentFolder() { if (!m_mainWindow) { return; } KTextEditor::View *editView = m_mainWindow->activeView(); if (editView && editView->document()) { // upUrl as we want the folder not the file m_ui.folderRequester->setUrl(localFileDirUp(editView->document()->url())); } m_ui.displayOptions->setChecked(true); } void KatePluginSearchView::openSearchView() { if (!m_mainWindow) { return; } if (!m_toolView->isVisible()) { m_mainWindow->showToolView(m_toolView); } m_ui.searchCombo->setFocus(Qt::OtherFocusReason); if (m_ui.searchPlaceCombo->currentIndex() == Folder) { m_ui.displayOptions->setChecked(true); } KTextEditor::View *editView = m_mainWindow->activeView(); if (editView && editView->document()) { if (m_ui.folderRequester->text().isEmpty()) { // upUrl as we want the folder not the file m_ui.folderRequester->setUrl(localFileDirUp(editView->document()->url())); } QString selection; if (editView->selection()) { selection = editView->selectionText(); // remove possible trailing '\n' if (selection.endsWith(QLatin1Char('\n'))) { selection = selection.left(selection.size() - 1); } } if (selection.isEmpty()) { selection = editView->document()->wordAt(editView->cursorPosition()); } if (!selection.isEmpty() && !selection.contains(QLatin1Char('\n'))) { m_ui.searchCombo->blockSignals(true); m_ui.searchCombo->lineEdit()->setText(selection); m_ui.searchCombo->blockSignals(false); } m_ui.searchCombo->lineEdit()->selectAll(); m_searchJustOpened = true; startSearchWhileTyping(); } } void KatePluginSearchView::handleEsc(QEvent *e) { if (!m_mainWindow) return; QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { static ulong lastTimeStamp; if (lastTimeStamp == k->timestamp()) { // Same as previous... This looks like a bug somewhere... return; } lastTimeStamp = k->timestamp(); if (!m_matchRanges.isEmpty()) { clearMarks(); } else if (m_toolView->isVisible()) { m_mainWindow->hideToolView(m_toolView); } // Remove check marks Results *curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!curResults) { qWarning() << "This is a bug"; return; } QTreeWidgetItemIterator it(curResults->tree); while (*it) { (*it)->setCheckState(0, Qt::Unchecked); ++it; } } } void KatePluginSearchView::setSearchString(const QString &pattern) { m_ui.searchCombo->lineEdit()->setText(pattern); } void KatePluginSearchView::toggleOptions(bool show) { m_ui.stackedWidget->setCurrentIndex((show) ? 1 : 0); } void KatePluginSearchView::setSearchPlace(int place) { m_ui.searchPlaceCombo->setCurrentIndex(place); } QStringList KatePluginSearchView::filterFiles(const QStringList &files) const { QString types = m_ui.filterCombo->currentText(); QString excludes = m_ui.excludeCombo->currentText(); if (((types.isEmpty() || types == QLatin1String("*"))) && (excludes.isEmpty())) { // shortcut for use all files return files; } QStringList tmpTypes = types.split(QLatin1Char(',')); QVector typeList(tmpTypes.size()); for (int i = 0; i < tmpTypes.size(); i++) { QRegExp rx(tmpTypes[i].trimmed()); rx.setPatternSyntax(QRegExp::Wildcard); typeList << rx; } QStringList tmpExcludes = excludes.split(QLatin1Char(',')); QVector excludeList(tmpExcludes.size()); for (int i = 0; i < tmpExcludes.size(); i++) { QRegExp rx(tmpExcludes[i].trimmed()); rx.setPatternSyntax(QRegExp::Wildcard); excludeList << rx; } QStringList filteredFiles; - foreach (QString fileName, files) { + for (const QString &fileName : files) { bool isInSubDir = fileName.startsWith(m_resultBaseDir); QString nameToCheck = fileName; if (isInSubDir) { nameToCheck = fileName.mid(m_resultBaseDir.size()); } bool skip = false; for (const auto ®ex : qAsConst(excludeList)) { if (regex.exactMatch(nameToCheck)) { skip = true; break; } } if (skip) { continue; } for (const auto ®ex : qAsConst(typeList)) { if (regex.exactMatch(nameToCheck)) { filteredFiles << fileName; break; } } } return filteredFiles; } void KatePluginSearchView::folderFileListChanged() { m_searchDiskFilesDone = false; m_searchOpenFilesDone = false; if (!m_curResults) { qWarning() << "This is a bug"; m_searchDiskFilesDone = true; m_searchOpenFilesDone = true; searchDone(); return; } QStringList fileList = m_folderFilesList.fileList(); QList openList; for (int i = 0; i < m_kateApp->documents().size(); i++) { int index = fileList.indexOf(m_kateApp->documents()[i]->url().toLocalFile()); if (index != -1) { openList << m_kateApp->documents()[i]; fileList.removeAt(index); } } // search order is important: Open files starts immediately and should finish // earliest after first event loop. // The DiskFile might finish immediately if (!openList.empty()) { m_searchOpenFiles.startSearch(openList, m_curResults->regExp); } else { m_searchOpenFilesDone = true; } m_searchDiskFiles.startSearch(fileList, m_curResults->regExp); } void KatePluginSearchView::searchPlaceChanged() { int searchPlace = m_ui.searchPlaceCombo->currentIndex(); const bool inFolder = (searchPlace == Folder); m_ui.filterCombo->setEnabled(searchPlace >= Folder); m_ui.excludeCombo->setEnabled(searchPlace >= Folder); m_ui.folderRequester->setEnabled(inFolder); m_ui.folderUpButton->setEnabled(inFolder); m_ui.currentFolderButton->setEnabled(inFolder); m_ui.recursiveCheckBox->setEnabled(inFolder); m_ui.hiddenCheckBox->setEnabled(inFolder); m_ui.symLinkCheckBox->setEnabled(inFolder); m_ui.binaryCheckBox->setEnabled(inFolder); if (inFolder && sender() == m_ui.searchPlaceCombo) { setCurrentFolder(); } // ... and the labels: m_ui.folderLabel->setEnabled(m_ui.folderRequester->isEnabled()); m_ui.filterLabel->setEnabled(m_ui.filterCombo->isEnabled()); m_ui.excludeLabel->setEnabled(m_ui.excludeCombo->isEnabled()); Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (res) { res->searchPlaceIndex = searchPlace; } } void KatePluginSearchView::addHeaderItem() { QTreeWidgetItem *item = new QTreeWidgetItem(m_curResults->tree, QStringList()); item->setCheckState(0, Qt::Checked); item->setFlags(item->flags() | Qt::ItemIsAutoTristate); m_curResults->tree->expandItem(item); } QTreeWidgetItem *KatePluginSearchView::rootFileItem(const QString &url, const QString &fName) { if (!m_curResults) { return nullptr; } QUrl fullUrl = QUrl::fromUserInput(url); QString path = fullUrl.isLocalFile() ? localFileDirUp(fullUrl).path() : fullUrl.url(); if (!path.isEmpty() && !path.endsWith(QLatin1Char('/'))) { path += QLatin1Char('/'); } path.remove(m_resultBaseDir); QString name = fullUrl.fileName(); if (url.isEmpty()) { name = fName; } // make sure we have a root item if (m_curResults->tree->topLevelItemCount() == 0) { addHeaderItem(); } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (m_isSearchAsYouType) { return root; } for (int i = 0; i < root->childCount(); i++) { // qDebug() << root->child(i)->data(0, ReplaceMatches::FileNameRole).toString() << fName; if ((root->child(i)->data(0, ReplaceMatches::FileUrlRole).toString() == url) && (root->child(i)->data(0, ReplaceMatches::FileNameRole).toString() == fName)) { int matches = root->child(i)->data(0, ReplaceMatches::StartLineRole).toInt() + 1; QString tmpUrl = QStringLiteral("%1%2: %3").arg(path, name).arg(matches); root->child(i)->setData(0, Qt::DisplayRole, tmpUrl); root->child(i)->setData(0, ReplaceMatches::StartLineRole, matches); return root->child(i); } } // file item not found create a new one QString tmpUrl = QStringLiteral("%1%2: %3").arg(path, name).arg(1); TreeWidgetItem *item = new TreeWidgetItem(root, QStringList(tmpUrl)); item->setData(0, ReplaceMatches::FileUrlRole, url); item->setData(0, ReplaceMatches::FileNameRole, fName); item->setData(0, ReplaceMatches::StartLineRole, 1); item->setCheckState(0, Qt::Checked); item->setFlags(item->flags() | Qt::ItemIsAutoTristate); return item; } void KatePluginSearchView::addMatchMark(KTextEditor::Document *doc, QTreeWidgetItem *item) { if (!doc || !item) { return; } KTextEditor::View *activeView = m_mainWindow->activeView(); KTextEditor::MovingInterface *miface = qobject_cast(doc); KTextEditor::ConfigInterface *ciface = qobject_cast(activeView); KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); int line = item->data(0, ReplaceMatches::StartLineRole).toInt(); int column = item->data(0, ReplaceMatches::StartColumnRole).toInt(); int endLine = item->data(0, ReplaceMatches::EndLineRole).toInt(); int endColumn = item->data(0, ReplaceMatches::EndColumnRole).toInt(); bool isReplaced = item->data(0, ReplaceMatches::ReplacedRole).toBool(); if (isReplaced) { QColor replaceColor(Qt::green); if (ciface) replaceColor = ciface->configValue(QStringLiteral("replace-highlight-color")).value(); attr->setBackground(replaceColor); if (activeView) { attr->setForeground(activeView->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color()); } } else { QColor searchColor(Qt::yellow); if (ciface) searchColor = ciface->configValue(QStringLiteral("search-highlight-color")).value(); attr->setBackground(searchColor); if (activeView) { attr->setForeground(activeView->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color()); } } KTextEditor::Range range(line, column, endLine, endColumn); // Check that the match still matches if (m_curResults) { if (!isReplaced) { // special handling for "(?=\\n)" in multi-line search QRegularExpression tmpReg = m_curResults->regExp; if (m_curResults->regExp.pattern().endsWith(QLatin1String("(?=\\n)"))) { QString newPatern = tmpReg.pattern(); newPatern.replace(QStringLiteral("(?=\\n)"), QStringLiteral("$")); tmpReg.setPattern(newPatern); } // Check that the match still matches ;) if (tmpReg.match(doc->text(range)).capturedStart() != 0) { qDebug() << doc->text(range) << "Does not match" << m_curResults->regExp.pattern(); return; } } else { if (doc->text(range) != item->data(0, ReplaceMatches::ReplacedTextRole).toString()) { qDebug() << doc->text(range) << "Does not match" << item->data(0, ReplaceMatches::ReplacedTextRole).toString(); return; } } } // Highlight the match KTextEditor::MovingRange *mr = miface->newMovingRange(range); mr->setAttribute(attr); mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection mr->setAttributeOnlyForViews(true); m_matchRanges.append(mr); // Add a match mark KTextEditor::MarkInterface *iface = qobject_cast(doc); if (!iface) return; iface->setMarkDescription(KTextEditor::MarkInterface::markType32, i18n("SearchHighLight")); iface->setMarkPixmap(KTextEditor::MarkInterface::markType32, QIcon().pixmap(0, 0)); iface->addMark(line, KTextEditor::MarkInterface::markType32); connect(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *)), this, SLOT(clearMarks()), Qt::UniqueConnection); } static const int contextLen = 70; void KatePluginSearchView::matchFound(const QString &url, const QString &fName, const QString &lineContent, int matchLen, int startLine, int startColumn, int endLine, int endColumn) { if (!m_curResults) { return; } int preLen = contextLen; int preStart = startColumn - preLen; if (preStart < 0) { preLen += preStart; preStart = 0; } QString pre; if (preLen == contextLen) { pre = QStringLiteral("..."); } pre += lineContent.mid(preStart, preLen).toHtmlEscaped(); QString match = lineContent.mid(startColumn, matchLen).toHtmlEscaped(); match.replace(QLatin1Char('\n'), QStringLiteral("\\n")); QString post = lineContent.mid(startColumn + matchLen, contextLen); if (post.size() >= contextLen) { post += QStringLiteral("..."); } post = post.toHtmlEscaped(); QStringList row; row << i18n("Line: %1 Column: %2: %3", startLine + 1, startColumn + 1, pre + QStringLiteral("") + match + QStringLiteral("") + post); TreeWidgetItem *item = new TreeWidgetItem(rootFileItem(url, fName), row); item->setData(0, ReplaceMatches::FileUrlRole, url); item->setData(0, Qt::ToolTipRole, url); item->setData(0, ReplaceMatches::FileNameRole, fName); item->setData(0, ReplaceMatches::StartLineRole, startLine); item->setData(0, ReplaceMatches::StartColumnRole, startColumn); item->setData(0, ReplaceMatches::MatchLenRole, matchLen); item->setData(0, ReplaceMatches::PreMatchRole, pre); item->setData(0, ReplaceMatches::MatchRole, match); item->setData(0, ReplaceMatches::PostMatchRole, post); item->setData(0, ReplaceMatches::EndLineRole, endLine); item->setData(0, ReplaceMatches::EndColumnRole, endColumn); item->setCheckState(0, Qt::Checked); m_curResults->matches++; } void KatePluginSearchView::clearMarks() { - foreach (KTextEditor::Document *doc, m_kateApp->documents()) { + const auto docs = m_kateApp->documents(); + for (KTextEditor::Document *doc : docs) { clearDocMarks(doc); } qDeleteAll(m_matchRanges); m_matchRanges.clear(); } void KatePluginSearchView::clearDocMarks(KTextEditor::Document *doc) { KTextEditor::MarkInterface *iface; iface = qobject_cast(doc); if (iface) { const QHash marks = iface->marks(); QHashIterator i(marks); while (i.hasNext()) { i.next(); if (i.value()->type & KTextEditor::MarkInterface::markType32) { iface->removeMark(i.value()->line, KTextEditor::MarkInterface::markType32); } } } int i = 0; while (i < m_matchRanges.size()) { if (m_matchRanges.at(i)->document() == doc) { delete m_matchRanges.at(i); m_matchRanges.removeAt(i); } else { i++; } } m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "This is a bug"; return; } } void KatePluginSearchView::startSearch() { m_changeTimer.stop(); // make sure not to start a "while you type" search now m_mainWindow->showToolView(m_toolView); // in case we are invoked from the command interface m_switchToProjectModeWhenAvailable = false; // now that we started, don't switch back automatically if (m_ui.searchCombo->currentText().isEmpty()) { // return pressed in the folder combo or filter combo return; } m_isSearchAsYouType = false; QString currentSearchText = m_ui.searchCombo->currentText(); m_ui.searchCombo->setItemText(0, QString()); // remove the text from index 0 on enter/search int index = m_ui.searchCombo->findText(currentSearchText); if (index > 0) { m_ui.searchCombo->removeItem(index); } m_ui.searchCombo->insertItem(1, currentSearchText); m_ui.searchCombo->setCurrentIndex(1); if (m_ui.filterCombo->findText(m_ui.filterCombo->currentText()) == -1) { m_ui.filterCombo->insertItem(0, m_ui.filterCombo->currentText()); m_ui.filterCombo->setCurrentIndex(0); } if (m_ui.excludeCombo->findText(m_ui.excludeCombo->currentText()) == -1) { m_ui.excludeCombo->insertItem(0, m_ui.excludeCombo->currentText()); m_ui.excludeCombo->setCurrentIndex(0); } if (m_ui.folderRequester->comboBox()->findText(m_ui.folderRequester->comboBox()->currentText()) == -1) { m_ui.folderRequester->comboBox()->insertItem(0, m_ui.folderRequester->comboBox()->currentText()); m_ui.folderRequester->comboBox()->setCurrentIndex(0); } m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "This is a bug"; return; } QRegularExpression::PatternOptions patternOptions = (m_ui.matchCase->isChecked() ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption); QString pattern = (m_ui.useRegExp->isChecked() ? currentSearchText : QRegularExpression::escape(currentSearchText)); QRegularExpression reg(pattern, patternOptions); if (!reg.isValid()) { // qDebug() << "invalid regexp"; indicateMatch(false); return; } m_curResults->regExp = reg; m_curResults->useRegExp = m_ui.useRegExp->isChecked(); m_curResults->matchCase = m_ui.matchCase->isChecked(); m_curResults->searchPlaceIndex = m_ui.searchPlaceCombo->currentIndex(); m_ui.newTabButton->setDisabled(true); m_ui.searchCombo->setDisabled(true); m_ui.searchButton->setDisabled(true); m_ui.displayOptions->setChecked(false); m_ui.displayOptions->setDisabled(true); m_ui.replaceCheckedBtn->setDisabled(true); m_ui.replaceButton->setDisabled(true); m_ui.stopAndNext->setCurrentWidget(m_ui.stopButton); m_ui.replaceCombo->setDisabled(true); m_ui.searchPlaceCombo->setDisabled(true); m_ui.useRegExp->setDisabled(true); m_ui.matchCase->setDisabled(true); m_ui.expandResults->setDisabled(true); m_ui.currentFolderButton->setDisabled(true); clearMarks(); m_curResults->tree->clear(); m_curResults->tree->setCurrentItem(nullptr); m_curResults->matches = 0; disconnect(m_curResults->tree, &QTreeWidget::itemChanged, &m_updateSumaryTimer, nullptr); m_ui.resultTabWidget->setTabText(m_ui.resultTabWidget->currentIndex(), m_ui.searchCombo->currentText()); m_toolView->setCursor(Qt::WaitCursor); m_searchDiskFilesDone = false; m_searchOpenFilesDone = false; const bool inCurrentProject = m_ui.searchPlaceCombo->currentIndex() == Project; const bool inAllOpenProjects = m_ui.searchPlaceCombo->currentIndex() == AllProjects; if (m_ui.searchPlaceCombo->currentIndex() == CurrentFile) { m_searchDiskFilesDone = true; m_resultBaseDir.clear(); QList documents; documents << m_mainWindow->activeView()->document(); addHeaderItem(); m_searchOpenFiles.startSearch(documents, reg); } else if (m_ui.searchPlaceCombo->currentIndex() == OpenFiles) { m_searchDiskFilesDone = true; m_resultBaseDir.clear(); const QList documents = m_kateApp->documents(); addHeaderItem(); m_searchOpenFiles.startSearch(documents, reg); } else if (m_ui.searchPlaceCombo->currentIndex() == Folder) { m_resultBaseDir = m_ui.folderRequester->url().path(); if (!m_resultBaseDir.isEmpty() && !m_resultBaseDir.endsWith(QLatin1Char('/'))) m_resultBaseDir += QLatin1Char('/'); addHeaderItem(); m_folderFilesList.generateList(m_ui.folderRequester->text(), m_ui.recursiveCheckBox->isChecked(), m_ui.hiddenCheckBox->isChecked(), m_ui.symLinkCheckBox->isChecked(), m_ui.binaryCheckBox->isChecked(), m_ui.filterCombo->currentText(), m_ui.excludeCombo->currentText()); // the file list will be ready when the thread returns (connected to folderFileListChanged) } else if (inCurrentProject || inAllOpenProjects) { /** * init search with file list from current project, if any */ m_resultBaseDir.clear(); QStringList files; if (m_projectPluginView) { if (inCurrentProject) { m_resultBaseDir = m_projectPluginView->property("projectBaseDir").toString(); } else { m_resultBaseDir = m_projectPluginView->property("allProjectsCommonBaseDir").toString(); } if (!m_resultBaseDir.endsWith(QLatin1Char('/'))) m_resultBaseDir += QLatin1Char('/'); QStringList projectFiles; if (inCurrentProject) { projectFiles = m_projectPluginView->property("projectFiles").toStringList(); } else { projectFiles = m_projectPluginView->property("allProjectsFiles").toStringList(); } files = filterFiles(projectFiles); } addHeaderItem(); QList openList; const auto docs = m_kateApp->documents(); for (const auto doc : docs) { // match project file's list toLocalFile() int index = files.indexOf(doc->url().toLocalFile()); if (index != -1) { openList << doc; files.removeAt(index); } } // search order is important: Open files starts immediately and should finish // earliest after first event loop. // The DiskFile might finish immediately if (!openList.empty()) { m_searchOpenFiles.startSearch(openList, m_curResults->regExp); } else { m_searchOpenFilesDone = true; } m_searchDiskFiles.startSearch(files, reg); } else { Q_ASSERT_X(false, "KatePluginSearchView::startSearch", "case not handled"); } } void KatePluginSearchView::startSearchWhileTyping() { if (!m_searchDiskFilesDone || !m_searchOpenFilesDone) { return; } m_isSearchAsYouType = true; QString currentSearchText = m_ui.searchCombo->currentText(); m_ui.searchButton->setDisabled(currentSearchText.isEmpty()); // Do not clear the search results if you press up by mistake if (currentSearchText.isEmpty()) return; if (!m_mainWindow->activeView()) return; KTextEditor::Document *doc = m_mainWindow->activeView()->document(); if (!doc) return; m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "This is a bug"; return; } // check if we typed something or just changed combobox index // changing index should not trigger a search-as-you-type if (m_ui.searchCombo->currentIndex() > 0 && currentSearchText == m_ui.searchCombo->itemText(m_ui.searchCombo->currentIndex())) { return; } // Now we should have a true typed text change QRegularExpression::PatternOptions patternOptions = (m_ui.matchCase->isChecked() ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption); QString pattern = (m_ui.useRegExp->isChecked() ? currentSearchText : QRegularExpression::escape(currentSearchText)); QRegularExpression reg(pattern, patternOptions); if (!reg.isValid()) { // qDebug() << "invalid regexp"; indicateMatch(false); return; } disconnect(m_curResults->tree, &QTreeWidget::itemChanged, &m_updateSumaryTimer, nullptr); m_curResults->regExp = reg; m_curResults->useRegExp = m_ui.useRegExp->isChecked(); m_ui.replaceCheckedBtn->setDisabled(true); m_ui.replaceButton->setDisabled(true); m_ui.nextButton->setDisabled(true); int cursorPosition = m_ui.searchCombo->lineEdit()->cursorPosition(); bool hasSelected = m_ui.searchCombo->lineEdit()->hasSelectedText(); m_ui.searchCombo->blockSignals(true); m_ui.searchCombo->setItemText(0, currentSearchText); m_ui.searchCombo->setCurrentIndex(0); m_ui.searchCombo->lineEdit()->setCursorPosition(cursorPosition); if (hasSelected) { // This restores the select all from invoking openSearchView // This selects too much if we have a partial selection and toggle match-case/regexp m_ui.searchCombo->lineEdit()->selectAll(); } m_ui.searchCombo->blockSignals(false); // Prepare for the new search content clearMarks(); m_resultBaseDir.clear(); m_curResults->tree->clear(); m_curResults->tree->setCurrentItem(nullptr); m_curResults->matches = 0; // Add the search-as-you-type header item TreeWidgetItem *item = new TreeWidgetItem(m_curResults->tree, QStringList()); item->setData(0, ReplaceMatches::FileUrlRole, doc->url().toString()); item->setData(0, ReplaceMatches::FileNameRole, doc->documentName()); item->setData(0, ReplaceMatches::StartLineRole, 0); item->setCheckState(0, Qt::Checked); item->setFlags(item->flags() | Qt::ItemIsAutoTristate); // Do the search int searchStoppedAt = m_searchOpenFiles.searchOpenFile(doc, reg, 0); searchWhileTypingDone(); if (searchStoppedAt != 0) { delete m_infoMessage; const QString msg = i18n("Searching while you type was interrupted. It would have taken too long."); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Warning); m_infoMessage->setPosition(KTextEditor::Message::TopInView); m_infoMessage->setAutoHide(3000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } } void KatePluginSearchView::searchDone() { m_changeTimer.stop(); // avoid "while you type" search directly after if (sender() == &m_searchDiskFiles) { m_searchDiskFilesDone = true; } if (sender() == &m_searchOpenFiles) { m_searchOpenFilesDone = true; } if (!m_searchDiskFilesDone || !m_searchOpenFilesDone) { return; } QWidget *fw = QApplication::focusWidget(); // NOTE: we take the focus widget here before the enabling/disabling // moves the focus around. m_ui.newTabButton->setDisabled(false); m_ui.searchCombo->setDisabled(false); m_ui.searchButton->setDisabled(false); m_ui.stopAndNext->setCurrentWidget(m_ui.nextButton); m_ui.displayOptions->setDisabled(false); m_ui.replaceCombo->setDisabled(false); m_ui.searchPlaceCombo->setDisabled(false); m_ui.useRegExp->setDisabled(false); m_ui.matchCase->setDisabled(false); m_ui.expandResults->setDisabled(false); m_ui.currentFolderButton->setDisabled(false); if (!m_curResults) { return; } m_ui.replaceCheckedBtn->setDisabled(m_curResults->matches < 1); m_ui.replaceButton->setDisabled(m_curResults->matches < 1); m_ui.nextButton->setDisabled(m_curResults->matches < 1); m_curResults->tree->sortItems(0, Qt::AscendingOrder); m_curResults->tree->expandAll(); m_curResults->tree->resizeColumnToContents(0); if (m_curResults->tree->columnWidth(0) < m_curResults->tree->width() - 30) { m_curResults->tree->setColumnWidth(0, m_curResults->tree->width() - 30); } // expand the "header item " to display all files and all results if configured expandResults(); updateResultsRootItem(); connect(m_curResults->tree, &QTreeWidget::itemChanged, &m_updateSumaryTimer, static_cast(&QTimer::start)); indicateMatch(m_curResults->matches > 0); m_curResults = nullptr; m_toolView->unsetCursor(); if (fw == m_ui.stopButton) { m_ui.searchCombo->setFocus(); } m_searchJustOpened = false; } void KatePluginSearchView::searchWhileTypingDone() { if (!m_curResults) { return; } bool popupVisible = m_ui.searchCombo->lineEdit()->completer()->popup()->isVisible(); m_ui.replaceCheckedBtn->setDisabled(m_curResults->matches < 1); m_ui.replaceButton->setDisabled(m_curResults->matches < 1); m_ui.nextButton->setDisabled(m_curResults->matches < 1); m_curResults->tree->expandAll(); m_curResults->tree->resizeColumnToContents(0); if (m_curResults->tree->columnWidth(0) < m_curResults->tree->width() - 30) { m_curResults->tree->setColumnWidth(0, m_curResults->tree->width() - 30); } QWidget *focusObject = nullptr; QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { QTreeWidgetItem *child = root->child(0); if (!m_searchJustOpened) { focusObject = qobject_cast(QGuiApplication::focusObject()); } indicateMatch(child); updateResultsRootItem(); connect(m_curResults->tree, &QTreeWidget::itemChanged, &m_updateSumaryTimer, static_cast(&QTimer::start)); } m_curResults = nullptr; if (focusObject) { focusObject->setFocus(); } if (popupVisible) { m_ui.searchCombo->lineEdit()->completer()->complete(); } m_searchJustOpened = false; } void KatePluginSearchView::searching(const QString &file) { if (!m_curResults) { return; } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { if (file.size() > 70) { root->setData(0, Qt::DisplayRole, i18n("Searching: ...%1", file.right(70))); } else { root->setData(0, Qt::DisplayRole, i18n("Searching: %1", file)); } } } void KatePluginSearchView::indicateMatch(bool hasMatch) { QLineEdit *const lineEdit = m_ui.searchCombo->lineEdit(); QPalette background(lineEdit->palette()); if (hasMatch) { // Green background for line edit KColorScheme::adjustBackground(background, KColorScheme::PositiveBackground); } else { // Reset background of line edit background = QPalette(); } // Red background for line edit // KColorScheme::adjustBackground(background, KColorScheme::NegativeBackground); // Neutral background // KColorScheme::adjustBackground(background, KColorScheme::NeutralBackground); lineEdit->setPalette(background); } void KatePluginSearchView::replaceSingleMatch() { // Save the search text if (m_ui.searchCombo->findText(m_ui.searchCombo->currentText()) == -1) { m_ui.searchCombo->insertItem(1, m_ui.searchCombo->currentText()); m_ui.searchCombo->setCurrentIndex(1); } // Save the replace text if (m_ui.replaceCombo->findText(m_ui.replaceCombo->currentText()) == -1) { m_ui.replaceCombo->insertItem(1, m_ui.replaceCombo->currentText()); m_ui.replaceCombo->setCurrentIndex(1); } // Check if the cursor is at the current item if not jump there Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; // Security measure } QTreeWidgetItem *item = res->tree->currentItem(); if (!item || !item->parent()) { // Nothing was selected goToNextMatch(); return; } if (!m_mainWindow->activeView() || !m_mainWindow->activeView()->cursorPosition().isValid()) { itemSelected(item); // Correct any bad cursor positions return; } int cursorLine = m_mainWindow->activeView()->cursorPosition().line(); int cursorColumn = m_mainWindow->activeView()->cursorPosition().column(); int startLine = item->data(0, ReplaceMatches::StartLineRole).toInt(); int startColumn = item->data(0, ReplaceMatches::StartColumnRole).toInt(); if ((cursorLine != startLine) || (cursorColumn != startColumn)) { itemSelected(item); return; } KTextEditor::Document *doc = m_mainWindow->activeView()->document(); // Find the corresponding range int i; for (i = 0; i < m_matchRanges.size(); i++) { if (m_matchRanges[i]->document() != doc) continue; if (m_matchRanges[i]->start().line() != startLine) continue; if (m_matchRanges[i]->start().column() != startColumn) continue; break; } if (i >= m_matchRanges.size()) { goToNextMatch(); return; } m_replacer.replaceSingleMatch(doc, item, res->regExp, m_ui.replaceCombo->currentText()); goToNextMatch(); } void KatePluginSearchView::replaceChecked() { if (m_ui.searchCombo->findText(m_ui.searchCombo->currentText()) == -1) { m_ui.searchCombo->insertItem(1, m_ui.searchCombo->currentText()); m_ui.searchCombo->setCurrentIndex(1); } if (m_ui.replaceCombo->findText(m_ui.replaceCombo->currentText()) == -1) { m_ui.replaceCombo->insertItem(1, m_ui.replaceCombo->currentText()); m_ui.replaceCombo->setCurrentIndex(1); } m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "Results not found"; return; } m_ui.stopAndNext->setCurrentWidget(m_ui.stopButton); m_ui.displayOptions->setChecked(false); m_ui.displayOptions->setDisabled(true); m_ui.newTabButton->setDisabled(true); m_ui.searchCombo->setDisabled(true); m_ui.searchButton->setDisabled(true); m_ui.replaceCheckedBtn->setDisabled(true); m_ui.replaceButton->setDisabled(true); m_ui.replaceCombo->setDisabled(true); m_ui.searchPlaceCombo->setDisabled(true); m_ui.useRegExp->setDisabled(true); m_ui.matchCase->setDisabled(true); m_ui.expandResults->setDisabled(true); m_ui.currentFolderButton->setDisabled(true); m_curResults->replaceStr = m_ui.replaceCombo->currentText(); QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { m_curResults->treeRootText = root->data(0, Qt::DisplayRole).toString(); } m_replacer.replaceChecked(m_curResults->tree, m_curResults->regExp, m_curResults->replaceStr); } void KatePluginSearchView::replaceStatus(const QUrl &url, int replacedInFile, int matchesInFile) { if (!m_curResults) { qDebug() << "m_curResults == nullptr"; return; } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { QString file = url.toString(QUrl::PreferLocalFile); if (file.size() > 70) { root->setData(0, Qt::DisplayRole, i18n("Processed %1 of %2 matches in: ...%3", replacedInFile, matchesInFile, file.right(70))); } else { root->setData(0, Qt::DisplayRole, i18n("Processed %1 of %2 matches in: %3", replacedInFile, matchesInFile, file)); } } } void KatePluginSearchView::replaceDone() { m_ui.stopAndNext->setCurrentWidget(m_ui.nextButton); m_ui.replaceCombo->setDisabled(false); m_ui.newTabButton->setDisabled(false); m_ui.searchCombo->setDisabled(false); m_ui.searchButton->setDisabled(false); m_ui.replaceCheckedBtn->setDisabled(false); m_ui.replaceButton->setDisabled(false); m_ui.displayOptions->setDisabled(false); m_ui.searchPlaceCombo->setDisabled(false); m_ui.useRegExp->setDisabled(false); m_ui.matchCase->setDisabled(false); m_ui.expandResults->setDisabled(false); m_ui.currentFolderButton->setDisabled(false); if (!m_curResults) { qDebug() << "m_curResults == nullptr"; return; } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (root) { root->setData(0, Qt::DisplayRole, m_curResults->treeRootText); } } void KatePluginSearchView::docViewChanged() { if (!m_mainWindow->activeView()) { return; } Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { qDebug() << "No res"; return; } m_curResults = res; // add the marks if it is not already open KTextEditor::Document *doc = m_mainWindow->activeView()->document(); if (doc && res->tree->topLevelItemCount() > 0) { // There is always one root item with match count // and X children with files or matches in case of search while typing QTreeWidgetItem *rootItem = res->tree->topLevelItem(0); QTreeWidgetItem *fileItem = nullptr; for (int i = 0; i < rootItem->childCount(); i++) { QString url = rootItem->child(i)->data(0, ReplaceMatches::FileUrlRole).toString(); QString fName = rootItem->child(i)->data(0, ReplaceMatches::FileNameRole).toString(); if (url == doc->url().toString() && fName == doc->documentName()) { fileItem = rootItem->child(i); break; } } if (fileItem) { clearDocMarks(doc); if (m_isSearchAsYouType) { fileItem = fileItem->parent(); } for (int i = 0; i < fileItem->childCount(); i++) { if (fileItem->child(i)->checkState(0) == Qt::Unchecked) { continue; } addMatchMark(doc, fileItem->child(i)); } } // Re-add the highlighting on document reload connect(doc, &KTextEditor::Document::reloaded, this, &KatePluginSearchView::docViewChanged, Qt::UniqueConnection); } } void KatePluginSearchView::expandResults() { m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { qWarning() << "Results not found"; return; } if (m_ui.expandResults->isChecked()) { m_curResults->tree->expandAll(); } else { QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); m_curResults->tree->expandItem(root); if (root && (root->childCount() > 1)) { for (int i = 0; i < root->childCount(); i++) { m_curResults->tree->collapseItem(root->child(i)); } } } } void KatePluginSearchView::updateResultsRootItem() { m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { return; } QTreeWidgetItem *root = m_curResults->tree->topLevelItem(0); if (!root) { // nothing to update return; } int checkedItemCount = 0; if (m_curResults->matches > 0) { for (QTreeWidgetItemIterator it(m_curResults->tree, QTreeWidgetItemIterator::Checked | QTreeWidgetItemIterator::NoChildren); *it; ++it) { checkedItemCount++; } } QString checkedStr = i18np("One checked", "%1 checked", checkedItemCount); int searchPlace = m_ui.searchPlaceCombo->currentIndex(); if (m_isSearchAsYouType) { searchPlace = CurrentFile; } switch (searchPlace) { case CurrentFile: root->setData(0, Qt::DisplayRole, i18np("One match (%2) found in file", "%1 matches (%2) found in file", m_curResults->matches, checkedStr)); break; case OpenFiles: root->setData(0, Qt::DisplayRole, i18np("One match (%2) found in open files", "%1 matches (%2) found in open files", m_curResults->matches, checkedStr)); break; case Folder: root->setData(0, Qt::DisplayRole, i18np("One match (%3) found in folder %2", "%1 matches (%3) found in folder %2", m_curResults->matches, m_resultBaseDir, checkedStr)); break; case Project: { QString projectName; if (m_projectPluginView) { projectName = m_projectPluginView->property("projectName").toString(); } root->setData(0, Qt::DisplayRole, i18np("One match (%4) found in project %2 (%3)", "%1 matches (%4) found in project %2 (%3)", m_curResults->matches, projectName, m_resultBaseDir, checkedStr)); break; } case AllProjects: // "in Open Projects" root->setData( 0, Qt::DisplayRole, i18np("One match (%3) found in all open projects (common parent: %2)", "%1 matches (%3) found in all open projects (common parent: %2)", m_curResults->matches, m_resultBaseDir, checkedStr)); break; } docViewChanged(); } void KatePluginSearchView::itemSelected(QTreeWidgetItem *item) { if (!item) return; m_curResults = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!m_curResults) { return; } while (item->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { item->treeWidget()->expandItem(item); item = item->child(0); if (!item) return; } item->treeWidget()->setCurrentItem(item); // get stuff int toLine = item->data(0, ReplaceMatches::StartLineRole).toInt(); int toColumn = item->data(0, ReplaceMatches::StartColumnRole).toInt(); KTextEditor::Document *doc; QString url = item->data(0, ReplaceMatches::FileUrlRole).toString(); if (!url.isEmpty()) { doc = m_kateApp->findUrl(QUrl::fromUserInput(url)); } else { doc = m_replacer.findNamed(item->data(0, ReplaceMatches::FileNameRole).toString()); } // add the marks to the document if it is not already open if (!doc) { doc = m_kateApp->openUrl(QUrl::fromUserInput(url)); } if (!doc) return; // open the right view... m_mainWindow->activateView(doc); // any view active? if (!m_mainWindow->activeView()) { return; } // set the cursor to the correct position m_mainWindow->activeView()->setCursorPosition(KTextEditor::Cursor(toLine, toColumn)); m_mainWindow->activeView()->setFocus(); } void KatePluginSearchView::goToNextMatch() { bool wrapFromFirst = false; bool startFromFirst = false; bool startFromCursor = false; Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; } QTreeWidgetItem *curr = res->tree->currentItem(); bool focusInView = m_mainWindow->activeView() && m_mainWindow->activeView()->hasFocus(); if (!curr && focusInView) { // no item has been visited && focus is not in searchCombo (probably in the view) -> // jump to the closest match after current cursor position // check if current file is in the file list curr = res->tree->topLevelItem(0); while (curr && curr->data(0, ReplaceMatches::FileUrlRole).toString() != m_mainWindow->activeView()->document()->url().toString()) { curr = res->tree->itemBelow(curr); } // now we are either in this file or !curr if (curr) { QTreeWidgetItem *fileBefore = curr; res->tree->expandItem(curr); int lineNr = 0; int columnNr = 0; if (m_mainWindow->activeView()->cursorPosition().isValid()) { lineNr = m_mainWindow->activeView()->cursorPosition().line(); columnNr = m_mainWindow->activeView()->cursorPosition().column(); } if (!curr->data(0, ReplaceMatches::StartColumnRole).isValid()) { curr = res->tree->itemBelow(curr); }; while (curr && curr->data(0, ReplaceMatches::StartLineRole).toInt() <= lineNr && curr->data(0, ReplaceMatches::FileUrlRole).toString() == m_mainWindow->activeView()->document()->url().toString()) { if (curr->data(0, ReplaceMatches::StartLineRole).toInt() == lineNr && curr->data(0, ReplaceMatches::StartColumnRole).toInt() >= columnNr - curr->data(0, ReplaceMatches::MatchLenRole).toInt()) { break; } fileBefore = curr; curr = res->tree->itemBelow(curr); } curr = fileBefore; startFromCursor = true; } } if (!curr) { curr = res->tree->topLevelItem(0); startFromFirst = true; } if (!curr) return; if (!curr->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { curr = res->tree->itemBelow(curr); if (!curr) { wrapFromFirst = true; curr = res->tree->topLevelItem(0); } } itemSelected(curr); if (startFromFirst) { delete m_infoMessage; const QString msg = i18n("Starting from first match"); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_infoMessage->setPosition(KTextEditor::Message::TopInView); m_infoMessage->setAutoHide(2000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } else if (startFromCursor) { delete m_infoMessage; const QString msg = i18n("Next from cursor"); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(2000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } else if (wrapFromFirst) { delete m_infoMessage; const QString msg = i18n("Continuing from first match"); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_infoMessage->setPosition(KTextEditor::Message::TopInView); m_infoMessage->setAutoHide(2000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } } void KatePluginSearchView::goToPreviousMatch() { bool fromLast = false; Results *res = qobject_cast(m_ui.resultTabWidget->currentWidget()); if (!res) { return; } if (res->tree->topLevelItemCount() == 0) { return; } QTreeWidgetItem *curr = res->tree->currentItem(); if (!curr) { // no item has been visited -> jump to the closest match before current cursor position // check if current file is in the file curr = res->tree->topLevelItem(0); while (curr && curr->data(0, ReplaceMatches::FileUrlRole).toString() != m_mainWindow->activeView()->document()->url().toString()) { curr = res->tree->itemBelow(curr); } // now we are either in this file or !curr if (curr) { res->tree->expandItem(curr); int lineNr = 0; int columnNr = 0; if (m_mainWindow->activeView()->cursorPosition().isValid()) { lineNr = m_mainWindow->activeView()->cursorPosition().line(); columnNr = m_mainWindow->activeView()->cursorPosition().column() - 1; } if (!curr->data(0, ReplaceMatches::StartColumnRole).isValid()) { curr = res->tree->itemBelow(curr); }; while (curr && curr->data(0, ReplaceMatches::StartLineRole).toInt() <= lineNr && curr->data(0, ReplaceMatches::FileUrlRole).toString() == m_mainWindow->activeView()->document()->url().toString()) { if (curr->data(0, ReplaceMatches::StartLineRole).toInt() == lineNr && curr->data(0, ReplaceMatches::StartColumnRole).toInt() > columnNr) { break; } curr = res->tree->itemBelow(curr); } } } QTreeWidgetItem *startChild = curr; // go to the item above. (curr == null is not a problem) curr = res->tree->itemAbove(curr); // expand the items above if needed if (curr && curr->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { res->tree->expandItem(curr); // probably this file item curr = res->tree->itemAbove(curr); if (curr && curr->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { res->tree->expandItem(curr); // probably file above if this is reached } curr = res->tree->itemAbove(startChild); } // skip file name items and the root item while (curr && curr->data(0, ReplaceMatches::StartColumnRole).toString().isEmpty()) { curr = res->tree->itemAbove(curr); } if (!curr) { // select the last child of the last next-to-top-level item QTreeWidgetItem *root = res->tree->topLevelItem(0); // select the last "root item" if (!root || (root->childCount() < 1)) return; root = root->child(root->childCount() - 1); // select the last match of the "root item" if (!root || (root->childCount() < 1)) return; curr = root->child(root->childCount() - 1); fromLast = true; } itemSelected(curr); if (fromLast) { delete m_infoMessage; const QString msg = i18n("Continuing from last match"); m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(2000); m_infoMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_infoMessage->setView(m_mainWindow->activeView()); m_mainWindow->activeView()->document()->postMessage(m_infoMessage); } } void KatePluginSearchView::readSessionConfig(const KConfigGroup &cg) { m_ui.searchCombo->clear(); m_ui.searchCombo->addItem(QString()); // Add empty Item m_ui.searchCombo->addItems(cg.readEntry("Search", QStringList())); m_ui.replaceCombo->clear(); m_ui.replaceCombo->addItem(QString()); // Add empty Item m_ui.replaceCombo->addItems(cg.readEntry("Replaces", QStringList())); m_ui.matchCase->setChecked(cg.readEntry("MatchCase", false)); m_ui.useRegExp->setChecked(cg.readEntry("UseRegExp", false)); m_ui.expandResults->setChecked(cg.readEntry("ExpandSearchResults", false)); int searchPlaceIndex = cg.readEntry("Place", 1); if (searchPlaceIndex < 0) { searchPlaceIndex = Folder; // for the case we happen to read -1 as Place } if ((searchPlaceIndex == Project) && (searchPlaceIndex >= m_ui.searchPlaceCombo->count())) { // handle the case that project mode was selected, but not yet available m_switchToProjectModeWhenAvailable = true; searchPlaceIndex = Folder; } m_ui.searchPlaceCombo->setCurrentIndex(searchPlaceIndex); m_ui.recursiveCheckBox->setChecked(cg.readEntry("Recursive", true)); m_ui.hiddenCheckBox->setChecked(cg.readEntry("HiddenFiles", false)); m_ui.symLinkCheckBox->setChecked(cg.readEntry("FollowSymLink", false)); m_ui.binaryCheckBox->setChecked(cg.readEntry("BinaryFiles", false)); m_ui.folderRequester->comboBox()->clear(); m_ui.folderRequester->comboBox()->addItems(cg.readEntry("SearchDiskFiless", QStringList())); m_ui.folderRequester->setText(cg.readEntry("SearchDiskFiles", QString())); m_ui.filterCombo->clear(); m_ui.filterCombo->addItems(cg.readEntry("Filters", QStringList())); m_ui.filterCombo->setCurrentIndex(cg.readEntry("CurrentFilter", -1)); m_ui.excludeCombo->clear(); m_ui.excludeCombo->addItems(cg.readEntry("ExcludeFilters", QStringList())); m_ui.excludeCombo->setCurrentIndex(cg.readEntry("CurrentExcludeFilter", -1)); m_ui.displayOptions->setChecked(searchPlaceIndex == Folder); } void KatePluginSearchView::writeSessionConfig(KConfigGroup &cg) { QStringList searchHistoy; for (int i = 1; i < m_ui.searchCombo->count(); i++) { searchHistoy << m_ui.searchCombo->itemText(i); } cg.writeEntry("Search", searchHistoy); QStringList replaceHistoy; for (int i = 1; i < m_ui.replaceCombo->count(); i++) { replaceHistoy << m_ui.replaceCombo->itemText(i); } cg.writeEntry("Replaces", replaceHistoy); cg.writeEntry("MatchCase", m_ui.matchCase->isChecked()); cg.writeEntry("UseRegExp", m_ui.useRegExp->isChecked()); cg.writeEntry("ExpandSearchResults", m_ui.expandResults->isChecked()); cg.writeEntry("Place", m_ui.searchPlaceCombo->currentIndex()); cg.writeEntry("Recursive", m_ui.recursiveCheckBox->isChecked()); cg.writeEntry("HiddenFiles", m_ui.hiddenCheckBox->isChecked()); cg.writeEntry("FollowSymLink", m_ui.symLinkCheckBox->isChecked()); cg.writeEntry("BinaryFiles", m_ui.binaryCheckBox->isChecked()); QStringList folders; for (int i = 0; i < qMin(m_ui.folderRequester->comboBox()->count(), 10); i++) { folders << m_ui.folderRequester->comboBox()->itemText(i); } cg.writeEntry("SearchDiskFiless", folders); cg.writeEntry("SearchDiskFiles", m_ui.folderRequester->text()); QStringList filterItems; for (int i = 0; i < qMin(m_ui.filterCombo->count(), 10); i++) { filterItems << m_ui.filterCombo->itemText(i); } cg.writeEntry("Filters", filterItems); cg.writeEntry("CurrentFilter", m_ui.filterCombo->findText(m_ui.filterCombo->currentText())); QStringList excludeFilterItems; for (int i = 0; i < qMin(m_ui.excludeCombo->count(), 10); i++) { excludeFilterItems << m_ui.excludeCombo->itemText(i); } cg.writeEntry("ExcludeFilters", excludeFilterItems); cg.writeEntry("CurrentExcludeFilter", m_ui.excludeCombo->findText(m_ui.excludeCombo->currentText())); } void KatePluginSearchView::addTab() { if ((sender() != m_ui.newTabButton) && (m_ui.resultTabWidget->count() > 0) && m_ui.resultTabWidget->tabText(m_ui.resultTabWidget->currentIndex()).isEmpty()) { return; } Results *res = new Results(); res->tree->setRootIsDecorated(false); connect(res->tree, &QTreeWidget::itemDoubleClicked, this, &KatePluginSearchView::itemSelected, Qt::UniqueConnection); res->searchPlaceIndex = m_ui.searchPlaceCombo->currentIndex(); res->useRegExp = m_ui.useRegExp->isChecked(); res->matchCase = m_ui.matchCase->isChecked(); m_ui.resultTabWidget->addTab(res, QString()); m_ui.resultTabWidget->setCurrentIndex(m_ui.resultTabWidget->count() - 1); m_ui.stackedWidget->setCurrentIndex(0); m_ui.resultTabWidget->tabBar()->show(); m_ui.displayOptions->setChecked(false); res->tree->installEventFilter(this); } void KatePluginSearchView::tabCloseRequested(int index) { Results *tmp = qobject_cast(m_ui.resultTabWidget->widget(index)); if (m_curResults == tmp) { m_searchOpenFiles.cancelSearch(); m_searchDiskFiles.cancelSearch(); } if (m_ui.resultTabWidget->count() > 1) { delete tmp; // remove the tab m_curResults = nullptr; } if (m_ui.resultTabWidget->count() == 1) { m_ui.resultTabWidget->tabBar()->hide(); } } void KatePluginSearchView::resultTabChanged(int index) { if (index < 0) { return; } Results *res = qobject_cast(m_ui.resultTabWidget->widget(index)); if (!res) { qDebug() << "No res found"; return; } m_ui.searchCombo->blockSignals(true); m_ui.matchCase->blockSignals(true); m_ui.useRegExp->blockSignals(true); m_ui.searchPlaceCombo->blockSignals(true); m_ui.searchCombo->lineEdit()->setText(m_ui.resultTabWidget->tabText(index)); m_ui.useRegExp->setChecked(res->useRegExp); m_ui.matchCase->setChecked(res->matchCase); m_ui.searchPlaceCombo->setCurrentIndex(res->searchPlaceIndex); m_ui.searchCombo->blockSignals(false); m_ui.matchCase->blockSignals(false); m_ui.useRegExp->blockSignals(false); m_ui.searchPlaceCombo->blockSignals(false); searchPlaceChanged(); } void KatePluginSearchView::onResize(const QSize &size) { bool vertical = size.width() < size.height(); if (!m_isLeftRight && vertical) { m_isLeftRight = true; m_ui.gridLayout->addWidget(m_ui.searchCombo, 0, 1, 1, 8); m_ui.gridLayout->addWidget(m_ui.findLabel, 0, 0); m_ui.gridLayout->addWidget(m_ui.searchButton, 1, 0, 1, 2); m_ui.gridLayout->addWidget(m_ui.stopAndNext, 1, 2); m_ui.gridLayout->addWidget(m_ui.searchPlaceCombo, 1, 3, 1, 3); m_ui.gridLayout->addWidget(m_ui.displayOptions, 1, 6); m_ui.gridLayout->addWidget(m_ui.matchCase, 1, 7); m_ui.gridLayout->addWidget(m_ui.useRegExp, 1, 8); m_ui.gridLayout->addWidget(m_ui.replaceCombo, 2, 1, 1, 8); m_ui.gridLayout->addWidget(m_ui.replaceLabel, 2, 0); m_ui.gridLayout->addWidget(m_ui.replaceButton, 3, 0, 1, 2); m_ui.gridLayout->addWidget(m_ui.replaceCheckedBtn, 3, 2); m_ui.gridLayout->addWidget(m_ui.expandResults, 3, 7); m_ui.gridLayout->addWidget(m_ui.newTabButton, 3, 8); m_ui.gridLayout->setColumnStretch(4, 2); m_ui.gridLayout->setColumnStretch(2, 0); } else if (m_isLeftRight && !vertical) { m_isLeftRight = false; m_ui.gridLayout->addWidget(m_ui.searchCombo, 0, 2); m_ui.gridLayout->addWidget(m_ui.findLabel, 0, 1); m_ui.gridLayout->addWidget(m_ui.searchButton, 0, 3); m_ui.gridLayout->addWidget(m_ui.stopAndNext, 0, 4); m_ui.gridLayout->addWidget(m_ui.searchPlaceCombo, 0, 5, 1, 4); m_ui.gridLayout->addWidget(m_ui.matchCase, 1, 5); m_ui.gridLayout->addWidget(m_ui.useRegExp, 1, 6); m_ui.gridLayout->addWidget(m_ui.replaceCombo, 1, 2); m_ui.gridLayout->addWidget(m_ui.replaceLabel, 1, 1); m_ui.gridLayout->addWidget(m_ui.replaceButton, 1, 3); m_ui.gridLayout->addWidget(m_ui.replaceCheckedBtn, 1, 4); m_ui.gridLayout->addWidget(m_ui.expandResults, 1, 8); m_ui.gridLayout->addWidget(m_ui.newTabButton, 0, 0); m_ui.gridLayout->addWidget(m_ui.displayOptions, 1, 0); m_ui.gridLayout->setColumnStretch(4, 0); m_ui.gridLayout->setColumnStretch(2, 2); m_ui.findLabel->setAlignment(Qt::AlignRight); m_ui.replaceLabel->setAlignment(Qt::AlignRight); } } bool KatePluginSearchView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); QTreeWidget *tree = qobject_cast(obj); if (tree) { if (ke->matches(QKeySequence::Copy)) { // user pressed ctrl+c -> copy full URL to the clipboard QVariant variant = tree->currentItem()->data(0, ReplaceMatches::FileUrlRole); QApplication::clipboard()->setText(variant.toString()); event->accept(); return true; } if (ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return) { if (tree->currentItem()) { itemSelected(tree->currentItem()); event->accept(); return true; } } } // NOTE: Qt::Key_Escape is handled by handleEsc } if (event->type() == QEvent::Resize) { QResizeEvent *re = static_cast(event); if (obj == m_toolView) { onResize(re->size()); } } return QObject::eventFilter(obj, event); } void KatePluginSearchView::searchContextMenu(const QPoint &pos) { QSet actionPointers; QMenu *const contextMenu = m_ui.searchCombo->lineEdit()->createStandardContextMenu(); if (!contextMenu) return; if (m_ui.useRegExp->isChecked()) { QMenu *menu = contextMenu->addMenu(i18n("Add...")); if (!menu) return; menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); addRegexHelperActionsForSearch(&actionPointers, menu); } // Show menu and act QAction *const result = contextMenu->exec(m_ui.searchCombo->mapToGlobal(pos)); regexHelperActOnAction(result, actionPointers, m_ui.searchCombo->lineEdit()); } void KatePluginSearchView::replaceContextMenu(const QPoint &pos) { QMenu *const contextMenu = m_ui.replaceCombo->lineEdit()->createStandardContextMenu(); if (!contextMenu) return; QMenu *menu = contextMenu->addMenu(i18n("Add...")); if (!menu) return; menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); QSet actionPointers; addSpecialCharsHelperActionsForReplace(&actionPointers, menu); if (m_ui.useRegExp->isChecked()) { addRegexHelperActionsForReplace(&actionPointers, menu); } // Show menu and act QAction *const result = contextMenu->exec(m_ui.replaceCombo->mapToGlobal(pos)); regexHelperActOnAction(result, actionPointers, m_ui.replaceCombo->lineEdit()); } void KatePluginSearchView::slotPluginViewCreated(const QString &name, QObject *pluginView) { // add view if (pluginView && name == QLatin1String("kateprojectplugin")) { m_projectPluginView = pluginView; slotProjectFileNameChanged(); connect(pluginView, SIGNAL(projectFileNameChanged()), this, SLOT(slotProjectFileNameChanged())); } } void KatePluginSearchView::slotPluginViewDeleted(const QString &name, QObject *) { // remove view if (name == QLatin1String("kateprojectplugin")) { m_projectPluginView = nullptr; slotProjectFileNameChanged(); } } void KatePluginSearchView::slotProjectFileNameChanged() { // query new project file name QString projectFileName; if (m_projectPluginView) { projectFileName = m_projectPluginView->property("projectFileName").toString(); } // have project, enable gui for it if (!projectFileName.isEmpty()) { if (m_ui.searchPlaceCombo->count() <= Project) { // add "in Project" m_ui.searchPlaceCombo->addItem(QIcon::fromTheme(QStringLiteral("project-open")), i18n("In Current Project")); if (m_switchToProjectModeWhenAvailable) { // switch to search "in Project" m_switchToProjectModeWhenAvailable = false; setSearchPlace(Project); } // add "in Open Projects" m_ui.searchPlaceCombo->addItem(QIcon::fromTheme(QStringLiteral("project-open")), i18n("In All Open Projects")); } } // else: disable gui for it else { if (m_ui.searchPlaceCombo->count() >= Project) { // switch to search "in Open files", if "in Project" is active if (m_ui.searchPlaceCombo->currentIndex() >= Project) { setSearchPlace(OpenFiles); } // remove "in Project" and "in all projects" while (m_ui.searchPlaceCombo->count() > Project) { m_ui.searchPlaceCombo->removeItem(m_ui.searchPlaceCombo->count() - 1); } } } } KateSearchCommand::KateSearchCommand(QObject *parent) : KTextEditor::Command(QStringList() << QStringLiteral("grep") << QStringLiteral("newGrep") << QStringLiteral("search") << QStringLiteral("newSearch") << QStringLiteral("pgrep") << QStringLiteral("newPGrep"), parent) { } bool KateSearchCommand::exec(KTextEditor::View * /*view*/, const QString &cmd, QString & /*msg*/, const KTextEditor::Range &) { // create a list of args QStringList args(cmd.split(QLatin1Char(' '), QString::KeepEmptyParts)); QString command = args.takeFirst(); QString searchText = args.join(QLatin1Char(' ')); if (command == QLatin1String("grep") || command == QLatin1String("newGrep")) { emit setSearchPlace(KatePluginSearchView::Folder); emit setCurrentFolder(); if (command == QLatin1String("newGrep")) emit newTab(); } else if (command == QLatin1String("search") || command == QLatin1String("newSearch")) { emit setSearchPlace(KatePluginSearchView::OpenFiles); if (command == QLatin1String("newSearch")) emit newTab(); } else if (command == QLatin1String("pgrep") || command == QLatin1String("newPGrep")) { emit setSearchPlace(KatePluginSearchView::Project); if (command == QLatin1String("newPGrep")) emit newTab(); } emit setSearchString(searchText); emit startSearch(); return true; } bool KateSearchCommand::help(KTextEditor::View * /*view*/, const QString &cmd, QString &msg) { if (cmd.startsWith(QLatin1String("grep"))) { msg = i18n("Usage: grep [pattern to search for in folder]"); } else if (cmd.startsWith(QLatin1String("newGrep"))) { msg = i18n("Usage: newGrep [pattern to search for in folder]"); } else if (cmd.startsWith(QLatin1String("search"))) { msg = i18n("Usage: search [pattern to search for in open files]"); } else if (cmd.startsWith(QLatin1String("newSearch"))) { msg = i18n("Usage: search [pattern to search for in open files]"); } else if (cmd.startsWith(QLatin1String("pgrep"))) { msg = i18n("Usage: pgrep [pattern to search for in current project]"); } else if (cmd.startsWith(QLatin1String("newPGrep"))) { msg = i18n("Usage: newPGrep [pattern to search for in current project]"); } return true; } #include "plugin_search.moc" // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/search/replace_matches.cpp b/addons/search/replace_matches.cpp index f80a51146..6def9d1f6 100644 --- a/addons/search/replace_matches.cpp +++ b/addons/search/replace_matches.cpp @@ -1,339 +1,339 @@ /* Kate search plugin * * Copyright (C) 2011-2013 by Kåre Särs * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program in a file called COPYING; if not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ #include "replace_matches.h" #include #include #include ReplaceMatches::ReplaceMatches(QObject *parent) : QObject(parent) { } void ReplaceMatches::replaceChecked(QTreeWidget *tree, const QRegularExpression ®exp, const QString &replace) { if (m_manager == nullptr) return; if (m_rootIndex != -1) return; // already replacing m_tree = tree; m_rootIndex = 0; m_childStartIndex = 0; m_regExp = regexp; m_replaceText = replace; m_cancelReplace = false; m_progressTime.restart(); doReplaceNextMatch(); } void ReplaceMatches::setDocumentManager(KTextEditor::Application *manager) { m_manager = manager; } void ReplaceMatches::cancelReplace() { m_cancelReplace = true; } KTextEditor::Document *ReplaceMatches::findNamed(const QString &name) { const QList docs = m_manager->documents(); - foreach (KTextEditor::Document *it, docs) { + for (KTextEditor::Document *it : docs) { if (it->documentName() == name) { return it; } } return nullptr; } bool ReplaceMatches::replaceMatch(KTextEditor::Document *doc, QTreeWidgetItem *item, const KTextEditor::Range &range, const QRegularExpression ®Exp, const QString &replaceTxt) { if (!doc || !item) { return false; } // don't replace an already replaced item if (item->data(0, ReplaceMatches::ReplacedRole).toBool()) { // qDebug() << "not replacing already replaced item"; return false; } // Check that the text has not been modified and still matches + get captures for the replace QString matchLines = doc->text(range); QRegularExpressionMatch match = regExp.match(matchLines); if (match.capturedStart() != 0) { qDebug() << matchLines << "Does not match" << regExp.pattern(); return false; } // Modify the replace string according to this match QString replaceText = replaceTxt; replaceText.replace(QLatin1String("\\\\"), QLatin1String("¤Search&Replace¤")); // allow captures \0 .. \9 for (int j = qMin(9, match.lastCapturedIndex()); j >= 0; --j) { QString captureLX = QStringLiteral("\\L\\%1").arg(j); QString captureUX = QStringLiteral("\\U\\%1").arg(j); QString captureX = QStringLiteral("\\%1").arg(j); replaceText.replace(captureLX, match.captured(j).toLower()); replaceText.replace(captureUX, match.captured(j).toUpper()); replaceText.replace(captureX, match.captured(j)); } // allow captures \{0} .. \{9999999}... for (int j = match.lastCapturedIndex(); j >= 0; --j) { QString captureLX = QStringLiteral("\\L\\{%1}").arg(j); QString captureUX = QStringLiteral("\\U\\{%1}").arg(j); QString captureX = QStringLiteral("\\{%1}").arg(j); replaceText.replace(captureLX, match.captured(j).toLower()); replaceText.replace(captureUX, match.captured(j).toUpper()); replaceText.replace(captureX, match.captured(j)); } replaceText.replace(QLatin1String("\\n"), QLatin1String("\n")); replaceText.replace(QLatin1String("\\t"), QLatin1String("\t")); replaceText.replace(QLatin1String("¤Search&Replace¤"), QLatin1String("\\")); doc->replaceText(range, replaceText); int newEndLine = range.start().line() + replaceText.count(QLatin1Char('\n')); int lastNL = replaceText.lastIndexOf(QLatin1Char('\n')); int newEndColumn = lastNL == -1 ? range.start().column() + replaceText.length() : replaceText.length() - lastNL - 1; item->setData(0, ReplaceMatches::ReplacedRole, true); item->setData(0, ReplaceMatches::StartLineRole, range.start().line()); item->setData(0, ReplaceMatches::StartColumnRole, range.start().column()); item->setData(0, ReplaceMatches::EndLineRole, newEndLine); item->setData(0, ReplaceMatches::EndColumnRole, newEndColumn); item->setData(0, ReplaceMatches::ReplacedTextRole, replaceText); // Convert replace text back to "html" replaceText.replace(QLatin1Char('\n'), QStringLiteral("\\n")); replaceText.replace(QLatin1Char('\t'), QStringLiteral("\\t")); QString html = item->data(0, ReplaceMatches::PreMatchRole).toString(); html += QLatin1String("") + item->data(0, ReplaceMatches::MatchRole).toString() + QLatin1String(" "); html += QLatin1String("") + replaceText + QLatin1String(""); html += item->data(0, ReplaceMatches::PostMatchRole).toString(); item->setData(0, Qt::DisplayRole, i18n("Line: %1: %2", range.start().line() + 1, html)); return true; } bool ReplaceMatches::replaceSingleMatch(KTextEditor::Document *doc, QTreeWidgetItem *item, const QRegularExpression ®Exp, const QString &replaceTxt) { if (!doc || !item) { return false; } QTreeWidgetItem *rootItem = item->parent(); if (!rootItem) { return false; } // Create a vector of moving ranges for updating the tree-view after replace QVector matches; QVector replaced; KTextEditor::MovingInterface *miface = qobject_cast(doc); int i = 0; // Only add items after "item" for (; i < rootItem->childCount(); i++) { if (item == rootItem->child(i)) break; } for (int j = i; j < rootItem->childCount(); j++) { QTreeWidgetItem *tmp = rootItem->child(j); int startLine = tmp->data(0, ReplaceMatches::StartLineRole).toInt(); int startColumn = tmp->data(0, ReplaceMatches::StartColumnRole).toInt(); int endLine = tmp->data(0, ReplaceMatches::EndLineRole).toInt(); int endColumn = tmp->data(0, ReplaceMatches::EndColumnRole).toInt(); KTextEditor::Range range(startLine, startColumn, endLine, endColumn); KTextEditor::MovingRange *mr = miface->newMovingRange(range); matches.append(mr); } if (matches.isEmpty()) { return false; } // The first range in the vector is for this match if (!replaceMatch(doc, item, matches[0]->toRange(), regExp, replaceTxt)) { return false; } delete matches.takeFirst(); // Update the remaining tree-view-items for (int j = i + 1; j < rootItem->childCount() && !matches.isEmpty(); j++) { QTreeWidgetItem *tmp = rootItem->child(j); tmp->setData(0, ReplaceMatches::StartLineRole, matches.first()->start().line()); tmp->setData(0, ReplaceMatches::StartColumnRole, matches.first()->start().column()); tmp->setData(0, ReplaceMatches::EndLineRole, matches.first()->end().line()); tmp->setData(0, ReplaceMatches::EndColumnRole, matches.first()->end().column()); delete matches.takeFirst(); } qDeleteAll(matches); return true; } void ReplaceMatches::doReplaceNextMatch() { if (!m_manager || m_tree->topLevelItemCount() != 1) { updateTreeViewItems(nullptr); m_rootIndex = -1; emit replaceDone(); return; } // NOTE The document managers signal documentWillBeDeleted() must be connected to // cancelReplace(). A closed file could lead to a crash if it is not handled. // Open the file QTreeWidgetItem *fileItem = m_tree->topLevelItem(0)->child(m_rootIndex); if (!fileItem) { updateTreeViewItems(nullptr); m_rootIndex = -1; emit replaceDone(); return; } bool isSearchAsYouType = false; if (!fileItem->data(0, StartColumnRole).toString().isEmpty()) { // this is a search as you type replace fileItem = m_tree->topLevelItem(0); isSearchAsYouType = true; } if (m_cancelReplace) { updateTreeViewItems(fileItem); m_rootIndex = -1; emit replaceDone(); return; } if (fileItem->checkState(0) == Qt::Unchecked) { updateTreeViewItems(fileItem); QTimer::singleShot(0, this, &ReplaceMatches::doReplaceNextMatch); return; } KTextEditor::Document *doc; QString docUrl = fileItem->data(0, FileUrlRole).toString(); if (docUrl.isEmpty()) { doc = findNamed(fileItem->data(0, FileNameRole).toString()); } else { doc = m_manager->findUrl(QUrl::fromUserInput(docUrl)); if (!doc) { doc = m_manager->openUrl(QUrl::fromUserInput(fileItem->data(0, FileUrlRole).toString())); } } if (!doc) { updateTreeViewItems(fileItem); QTimer::singleShot(0, this, &ReplaceMatches::doReplaceNextMatch); return; } if (m_progressTime.elapsed() > 100) { m_progressTime.restart(); if (m_currentMatches.isEmpty()) { emit replaceStatus(doc->url(), 0, 0); } else { emit replaceStatus(doc->url(), m_childStartIndex, m_currentMatches.count()); } } if (m_childStartIndex == 0) { // Create a vector of moving ranges for updating the tree-view after replace QVector replaced; KTextEditor::MovingInterface *miface = qobject_cast(doc); for (int j = 0; j < fileItem->childCount(); ++j) { QTreeWidgetItem *item = fileItem->child(j); int startLine = item->data(0, ReplaceMatches::StartLineRole).toInt(); int startColumn = item->data(0, ReplaceMatches::StartColumnRole).toInt(); int endLine = item->data(0, ReplaceMatches::EndLineRole).toInt(); int endColumn = item->data(0, ReplaceMatches::EndColumnRole).toInt(); KTextEditor::Range range(startLine, startColumn, endLine, endColumn); KTextEditor::MovingRange *mr = miface->newMovingRange(range); m_currentMatches.append(mr); m_currentReplaced << false; } } // Make one transaction for the whole replace to speed up things // and get all replacements in one "undo" KTextEditor::Document::EditingTransaction transaction(doc); // now do the replaces int i = m_childStartIndex; for (; i < fileItem->childCount(); ++i) { if (m_progressTime.elapsed() > 100) { break; } QTreeWidgetItem *item = fileItem->child(i); if (item->checkState(0) == Qt::Checked) { m_currentReplaced[i] = replaceMatch(doc, item, m_currentMatches[i]->toRange(), m_regExp, m_replaceText); item->setCheckState(0, Qt::PartiallyChecked); } } if (i == fileItem->childCount()) { updateTreeViewItems(fileItem); if (isSearchAsYouType) { m_rootIndex = -1; emit replaceDone(); return; } } else { m_childStartIndex = i; } QTimer::singleShot(0, this, &ReplaceMatches::doReplaceNextMatch); } void ReplaceMatches::updateTreeViewItems(QTreeWidgetItem *fileItem) { if (fileItem && m_currentReplaced.size() == m_currentMatches.size() && m_currentReplaced.size() == fileItem->childCount()) { for (int j = 0; j < m_currentReplaced.size() && j < m_currentMatches.size(); ++j) { QTreeWidgetItem *item = fileItem->child(j); if (!m_currentReplaced[j] && item) { item->setData(0, ReplaceMatches::StartLineRole, m_currentMatches[j]->start().line()); item->setData(0, ReplaceMatches::StartColumnRole, m_currentMatches[j]->start().column()); item->setData(0, ReplaceMatches::EndLineRole, m_currentMatches[j]->end().line()); item->setData(0, ReplaceMatches::EndColumnRole, m_currentMatches[j]->end().column()); } } } qDeleteAll(m_currentMatches); m_rootIndex++; m_childStartIndex = 0; m_currentMatches.clear(); m_currentReplaced.clear(); }