diff --git a/addons/externaltools/externaltoolsplugin.cpp b/addons/externaltools/externaltoolsplugin.cpp index 9b3585db3..2ed22284a 100644 --- a/addons/externaltools/externaltoolsplugin.cpp +++ b/addons/externaltools/externaltoolsplugin.cpp @@ -1,283 +1,293 @@ /* This file is part of the KDE project * * Copyright 2019 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 "externaltoolsplugin.h" #include "kateexternaltoolsview.h" #include "kateexternaltool.h" #include "kateexternaltoolscommand.h" #include "katemacroexpander.h" #include "katetoolrunner.h" #include "kateexternaltoolsconfigwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KateExternalToolsFactory, "externaltoolsplugin.json", registerPlugin();) KateExternalToolsPlugin::KateExternalToolsPlugin(QObject* parent, const QList&) : KTextEditor::Plugin(parent) { reload(); } KateExternalToolsPlugin::~KateExternalToolsPlugin() { delete m_command; m_command = nullptr; } QObject* KateExternalToolsPlugin::createView(KTextEditor::MainWindow* mainWindow) { KateExternalToolsPluginView* view = new KateExternalToolsPluginView(mainWindow, this); connect(this, &KateExternalToolsPlugin::externalToolsChanged, view, &KateExternalToolsPluginView::rebuildMenu); return view; } void KateExternalToolsPlugin::reload() { delete m_command; m_command = nullptr; m_commands.clear(); qDeleteAll(m_tools); m_tools.clear(); KConfig _config(QStringLiteral("externaltools"), KConfig::NoGlobals, QStandardPaths::ApplicationsLocation); KConfigGroup config(&_config, "Global"); const int toolCount = config.readEntry("tools", 0); for (int i = 0; i < toolCount; ++i) { config = KConfigGroup(&_config, QStringLiteral("Tool %1").arg(i)); auto t = new KateExternalTool(); t->load(config); m_tools.push_back(t); // FIXME test for a command name first! if (t->hasexec && (!t->cmdname.isEmpty())) { m_commands.push_back(t->cmdname); } } if (KAuthorized::authorizeAction(QStringLiteral("shell_access"))) { m_command = new KateExternalToolsCommand(this); } Q_EMIT externalToolsChanged(); } QStringList KateExternalToolsPlugin::commands() const { return m_commands; } const KateExternalTool* KateExternalToolsPlugin::toolForCommand(const QString& cmd) const { for (auto tool : m_tools) { if (tool->cmdname == cmd) { return tool; } } return nullptr; } const QVector & KateExternalToolsPlugin::tools() const { return m_tools; } void KateExternalToolsPlugin::runTool(const KateExternalTool& tool, KTextEditor::View* view) { // expand the macros in command if any, // and construct a command with an absolute path auto mw = view->mainWindow(); // save documents if requested if (tool.saveMode == KateExternalTool::SaveMode::CurrentDocument) { // only save if modified, to avoid unnecessary recompiles if (view->document()->isModified()) { view->document()->save(); } } else if (tool.saveMode == KateExternalTool::SaveMode::AllDocuments) { foreach (KXMLGUIClient* client, mw->guiFactory()->clients()) { if (QAction* a = client->actionCollection()->action(QStringLiteral("file_save_all"))) { a->trigger(); break; } } } // clear previous toolview data auto pluginView = viewForMainWindow(mw); pluginView->clearToolView(); // copy tool std::unique_ptr copy(new KateExternalTool(tool)); MacroExpander macroExpander(view); if (!macroExpander.expandMacrosShellQuote(copy->executable)) { - pluginView->reportToolError(i18n("Failed to expand executable '%1'.", copy->executable), copy.get()); + pluginView->showToolView(); + pluginView->addToolStatus(i18n("Failed to expand executable '%1'.", copy->executable), copy.get()); return; } if (!macroExpander.expandMacrosShellQuote(copy->arguments)) { - pluginView->reportToolError(i18n("Failed to expand argument '%1'.", copy->arguments), copy.get()); + pluginView->showToolView(); + pluginView->addToolStatus(i18n("Failed to expand argument '%1'.", copy->arguments), copy.get()); return; } if (!macroExpander.expandMacrosShellQuote(copy->workingDir)) { - pluginView->reportToolError(i18n("Failed to expand the working directory '%1'.", copy->workingDir), copy.get()); + pluginView->showToolView(); + pluginView->addToolStatus(i18n("Failed to expand working directory '%1'.", copy->workingDir), copy.get()); return; } if (!macroExpander.expandMacrosShellQuote(copy->input)) { - pluginView->reportToolError(i18n("Failed to expand the input '%1'.", copy->input), copy.get()); + pluginView->showToolView(); + pluginView->addToolStatus(i18n("Failed to expand input '%1'.", copy->input), copy.get()); return; } // Allocate runner on heap such that it lives as long as the child // process is running and does not block the main thread. auto runner = new KateToolRunner(std::move(copy), view, this); // use QueuedConnection, since handleToolFinished deletes the runner connect(runner, &KateToolRunner::toolFinished, this, &KateExternalToolsPlugin::handleToolFinished, Qt::QueuedConnection); runner->run(); } void KateExternalToolsPlugin::handleToolFinished(KateToolRunner* runner, int exitCode, bool crashed) { - if (exitCode != 0) { - qWarning() << i18n("External tool %1 finished with non-zero exit code: %2", runner->tool()->name, exitCode); + if (exitCode != 0 && runner->view()) { + auto pluginView = viewForMainWindow(runner->view()->mainWindow()); + pluginView->createToolView(); + pluginView->addToolStatus(i18n("External tool %1 finished with non-zero exit code: %2", runner->tool()->name, exitCode), runner->tool()); + pluginView->showToolView(); } - if (crashed) { - qWarning() << i18n("External tool crashed: %1", runner->tool()->name); + if (crashed && runner->view()) { + auto pluginView = viewForMainWindow(runner->view()->mainWindow()); + pluginView->createToolView(); + pluginView->addToolStatus(i18n("External tool crashed: %1", runner->tool()->name), runner->tool()); + pluginView->showToolView(); } auto view = runner->view(); if (crashed && view) { - viewForMainWindow(view->mainWindow())->reportToolError(i18n("External tool crashed: %1", runner->tool()->name), runner->tool()); + viewForMainWindow(view->mainWindow())->addToolStatus(i18n("External tool crashed: %1", runner->tool()->name), runner->tool()); } if (view && !runner->outputData().isEmpty()) { switch (runner->tool()->outputMode) { case KateExternalTool::OutputMode::InsertAtCursor: { KTextEditor::Document::EditingTransaction transaction(view->document()); view->removeSelection(); view->insertText(runner->outputData()); break; } case KateExternalTool::OutputMode::ReplaceSelectedText: { KTextEditor::Document::EditingTransaction transaction(view->document()); view->removeSelectionText(); view->insertText(runner->outputData()); break; } case KateExternalTool::OutputMode::ReplaceCurrentDocument: { KTextEditor::Document::EditingTransaction transaction(view->document()); view->document()->clear(); view->insertText(runner->outputData()); break; } case KateExternalTool::OutputMode::AppendToCurrentDocument: { view->document()->insertText(view->document()->documentEnd(), runner->outputData()); break; } case KateExternalTool::OutputMode::InsertInNewDocument: { auto mainWindow = view->mainWindow(); auto newView = mainWindow->openUrl({}); newView->insertText(runner->outputData()); mainWindow->activateView(newView->document()); break; } default: break; } } if (view && runner->tool()->reload) { // updates-enabled trick: avoid some flicker const bool wereUpdatesEnabled = view->updatesEnabled(); view->setUpdatesEnabled(false); view->document()->documentReload(); view->setUpdatesEnabled(wereUpdatesEnabled); } if (runner->tool()->outputMode == KateExternalTool::OutputMode::DisplayInPane) { auto pluginView = viewForMainWindow(view->mainWindow()); pluginView->createToolView(); pluginView->setOutputData(runner->outputData()); pluginView->showToolView(); } delete runner; } int KateExternalToolsPlugin::configPages() const { return 1; } KTextEditor::ConfigPage* KateExternalToolsPlugin::configPage(int number, QWidget* parent) { if (number == 0) { return new KateExternalToolsConfigWidget(parent, this); } return nullptr; } void KateExternalToolsPlugin::registerPluginView(KateExternalToolsPluginView * view) { Q_ASSERT(!m_views.contains(view)); m_views.push_back(view); } void KateExternalToolsPlugin::unregisterPluginView(KateExternalToolsPluginView * view) { Q_ASSERT(m_views.contains(view)); m_views.removeAll(view); } KateExternalToolsPluginView* KateExternalToolsPlugin::viewForMainWindow(KTextEditor::MainWindow* mainWindow) const { for (auto view : m_views) { if (view->mainWindow() == mainWindow) { return view; } } return nullptr; } #include "externaltoolsplugin.moc" // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltoolsview.cpp b/addons/externaltools/kateexternaltoolsview.cpp index 17f8ca809..4f70c5e0f 100644 --- a/addons/externaltools/kateexternaltoolsview.cpp +++ b/addons/externaltools/kateexternaltoolsview.cpp @@ -1,240 +1,254 @@ /* This file is part of the KDE project * * Copyright 2019 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 "kateexternaltoolsview.h" #include "externaltoolsplugin.h" #include "kateexternaltool.h" #include "ui_toolview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // BEGIN KateExternalToolsMenuAction KateExternalToolsMenuAction::KateExternalToolsMenuAction(const QString& text, KActionCollection* collection, KateExternalToolsPlugin* plugin, KTextEditor::MainWindow* mw) : KActionMenu(text, mw) , m_plugin(plugin) , m_mainwindow(mw) , m_actionCollection(collection) { reload(); // track active view to adapt enabled tool actions connect(mw, &KTextEditor::MainWindow::viewChanged, this, &KateExternalToolsMenuAction::slotViewChanged); } KateExternalToolsMenuAction::~KateExternalToolsMenuAction() = default; void KateExternalToolsMenuAction::reload() { // clear action collection bool needs_readd = (m_actionCollection->takeAction(this) != nullptr); m_actionCollection->clear(); if (needs_readd) m_actionCollection->addAction(QStringLiteral("tools_external"), this); menu()->clear(); // create tool actions std::map categories; std::vector uncategorizedActions; // first add categorized actions, such that the submenus appear at the top for (auto tool : m_plugin->tools()) { if (tool->hasexec) { auto a = new QAction(tool->name, this); a->setIcon(QIcon::fromTheme(tool->icon)); a->setData(QVariant::fromValue(tool)); connect(a, &QAction::triggered, [this, a]() { m_plugin->runTool(*a->data().value(), m_mainwindow->activeView()); }); m_actionCollection->addAction(tool->actionName, a); if (!tool->category.isEmpty()) { auto categoryMenu = categories[tool->category]; if (!categoryMenu) { categoryMenu = new KActionMenu(tool->category, this); categories[tool->category] = categoryMenu; addAction(categoryMenu); } categoryMenu->addAction(a); } else { uncategorizedActions.push_back(a); } } } // now add uncategorized actions below for (auto uncategorizedAction : uncategorizedActions) { addAction(uncategorizedAction); } // load shortcuts KSharedConfig::Ptr pConfig = KSharedConfig::openConfig(QStringLiteral("externaltools"), KConfig::NoGlobals, QStandardPaths::ApplicationsLocation); KConfigGroup config(pConfig, "Global"); config = KConfigGroup(pConfig, "Shortcuts"); m_actionCollection->readSettings(&config); slotViewChanged(m_mainwindow->activeView()); } void KateExternalToolsMenuAction::slotViewChanged(KTextEditor::View* view) { // no active view, oh oh if (!view) { return; } // try to enable/disable to match current mime type const QString mimeType = view->document()->mimeType(); foreach (QAction* action, m_actionCollection->actions()) { if (action && action->data().value()) { auto tool = action->data().value(); action->setEnabled(tool->matchesMimetype(mimeType)); } } } // END KateExternalToolsMenuAction // BEGIN KateExternalToolsPluginView KateExternalToolsPluginView::KateExternalToolsPluginView(KTextEditor::MainWindow* mainWindow, KateExternalToolsPlugin* plugin) : QObject(mainWindow) , m_plugin(plugin) , m_mainWindow(mainWindow) { m_plugin->registerPluginView(this); KXMLGUIClient::setComponentName(QLatin1String("externaltools"), i18n("External Tools")); setXMLFile(QLatin1String("ui.rc")); if (KAuthorized::authorizeAction(QStringLiteral("shell_access"))) { m_externalToolsMenu = new KateExternalToolsMenuAction(i18n("External Tools"), actionCollection(), plugin, mainWindow); actionCollection()->addAction(QStringLiteral("tools_external"), m_externalToolsMenu); m_externalToolsMenu->setWhatsThis(i18n("Launch external helper applications")); } mainWindow->guiFactory()->addClient(this); // ESC should close & hide ToolView connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, [this](QEvent* event) { auto keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Escape && keyEvent->modifiers() == Qt::NoModifier) { deleteToolView(); } }); showToolView(); } KateExternalToolsPluginView::~KateExternalToolsPluginView() { m_plugin->unregisterPluginView(this); m_mainWindow->guiFactory()->removeClient(this); deleteToolView(); delete m_externalToolsMenu; m_externalToolsMenu = nullptr; } void KateExternalToolsPluginView::rebuildMenu() { if (m_externalToolsMenu) { KXMLGUIFactory* f = factory(); f->removeClient(this); reloadXML(); m_externalToolsMenu->reload(); f->addClient(this); } } KTextEditor::MainWindow* KateExternalToolsPluginView::mainWindow() const { return m_mainWindow; } -void KateExternalToolsPluginView::showToolView() +void KateExternalToolsPluginView::createToolView() { if (!m_toolView) { m_toolView = mainWindow()->createToolView(m_plugin, QStringLiteral("ktexteditor_plugin_externaltools"), KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("system-run")), i18n("External Tools")); m_ui = new Ui::ToolView(); m_ui->setupUi(m_toolView); + // close button to delete tool view auto btnClose = new QToolButton(); btnClose->setAutoRaise(true); btnClose->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); connect(btnClose, &QToolButton::clicked, this, &KateExternalToolsPluginView::deleteToolView); m_ui->tabWidget->setCornerWidget(btnClose); } } +void KateExternalToolsPluginView::showToolView() +{ + createToolView(); + mainWindow()->showToolView(m_toolView); +} + void KateExternalToolsPluginView::clearToolView() { - if (m_toolView) { + if (m_ui) { + m_ui->teOutput->clear(); + m_ui->teStatus->clear(); } } -void KateExternalToolsPluginView::reportToolError(const QString& message, KateExternalTool* tool) +void KateExternalToolsPluginView::addToolStatus(const QString& message, KateExternalTool* tool) { - showToolView(); - m_ui->teErrors->setText(message); - m_ui->tabWidget->setCurrentWidget(m_ui->tabErrors); + m_ui->teStatus->setText(message); + m_ui->tabWidget->setCurrentWidget(m_ui->tabStatus); +} - mainWindow()->showToolView(m_toolView); +void KateExternalToolsPluginView::setOutputData(const QString& data) +{ + if (m_ui) { + m_ui->teOutput->setText(data); + m_ui->tabWidget->setCurrentWidget(m_ui->tabOutput); + } } void KateExternalToolsPluginView::deleteToolView() { if (m_toolView) { delete m_ui; m_ui = nullptr; delete m_toolView; m_toolView = nullptr; } } // END KateExternalToolsPluginView // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltoolsview.h b/addons/externaltools/kateexternaltoolsview.h index dc847fd16..d5dc56e13 100644 --- a/addons/externaltools/kateexternaltoolsview.h +++ b/addons/externaltools/kateexternaltoolsview.h @@ -1,137 +1,147 @@ /* This file is part of the KDE project * * Copyright 2019 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. */ #ifndef KTEXTEDITOR_EXTERNALTOOLS_H #define KTEXTEDITOR_EXTERNALTOOLS_H namespace KTextEditor { class MainWindow; } namespace KTextEditor { class View; } #include #include #include class KActionCollection; class KateExternalToolsPlugin; class KateExternalTool; namespace Ui { class ToolView; } /** * The external tools action * This action creates a menu, in which each item will launch a process * with the provided arguments, which may include the following macros: * - %URLS: the URLs of all open documents. * - %URL: The URL of the active document. * - %filedir: The directory of the current document, if that is a local file. * - %selection: The selection of the active document. * - %text: The text of the active document. * - %line: The line number of the cursor in the active view. * - %column: The column of the cursor in the active view. * * Each item has the following properties: * - Name: The friendly text for the menu * - Exec: The command to execute, including arguments. * - TryExec: the name of the executable, if not available, the * item will not be displayed. * - MimeTypes: An optional list of mimetypes. The item will be disabled or * hidden if the current file is not of one of the indicated types. * */ class KateExternalToolsMenuAction : public KActionMenu { Q_OBJECT public: KateExternalToolsMenuAction(const QString& text, KActionCollection* collection, KateExternalToolsPlugin* plugin, class KTextEditor::MainWindow* mw = nullptr); virtual ~KateExternalToolsMenuAction(); /** * This will load all the configured services. */ void reload(); KActionCollection* actionCollection() { return m_actionCollection; } private Q_SLOTS: void slotViewChanged(KTextEditor::View* view); private: KateExternalToolsPlugin* m_plugin; KTextEditor::MainWindow* m_mainwindow; // for the actions to access view/doc managers KActionCollection* m_actionCollection; }; class KateExternalToolsPluginView : public QObject, public KXMLGUIClient { Q_OBJECT public: /** * Constructor. */ KateExternalToolsPluginView(KTextEditor::MainWindow* mainWindow, KateExternalToolsPlugin* plugin); /** * Virtual destructor. */ ~KateExternalToolsPluginView(); /** * Returns the associated mainWindow */ KTextEditor::MainWindow* mainWindow() const; public Q_SLOTS: /** * Called by the plugin view to reload the menu */ void rebuildMenu(); + /** + * Creates the tool view. If already existing, does nothing. + */ + void createToolView(); + /** * Shows the tool view. The toolview will be created, if not yet existing. */ void showToolView(); /** * Clears the toolview data. If no toolview is around, nothing happens. */ void clearToolView(); /** - * Shows the External Tools toolview and porints the error message along with + * Shows the External Tools toolview and points the error message along with * some more info about the tool. */ - void reportToolError(const QString& message, KateExternalTool* tool); + void addToolStatus(const QString& message, KateExternalTool* tool); + + /** + * Sets the output data to data; + */ + void setOutputData(const QString& data); /** * Deletes the tool view, if existing. */ void deleteToolView(); private: KateExternalToolsPlugin* m_plugin; KTextEditor::MainWindow* m_mainWindow; KateExternalToolsMenuAction* m_externalToolsMenu = nullptr; QWidget* m_toolView = nullptr; Ui::ToolView* m_ui = nullptr; }; #endif // KTEXTEDITOR_EXTERNALTOOLS_H // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/toolview.ui b/addons/externaltools/toolview.ui index 871b686a0..ef0bea341 100644 --- a/addons/externaltools/toolview.ui +++ b/addons/externaltools/toolview.ui @@ -1,63 +1,63 @@ ToolView 0 0 678 295 6 6 451 240 0 Output true Displays output from the external tool - + - Errors + Status - + true No errors detected