diff --git a/addons/externaltools/externaltools b/addons/externaltools/externaltools index 01dfb7b0b..1e6107de5 100644 --- a/addons/externaltools/externaltools +++ b/addons/externaltools/externaltools @@ -1,163 +1,153 @@ [Global] tools=10 version=1 [Tool 0] actionName=externaltool_RunShellScript arguments=-e sh -c "cd %{CurrentDocument:Path} && pwd && chmod -vc a+x %{CurrentDocument:FileName} && ./%{CurrentDocument:FileName} ; echo Press any key to continue. && read -n 1" category= cmdname=run-script executable=konsole icon=system-run -includeStderr=false input= mimetypes= name=Run Shell Script output=Ignore reload=false save=CurrentDocument workingDir=%{CurrentDocument:Path} [Tool 1] actionName=externaltool_GoogleSelectedText arguments="https://www.google.com/search?q=%{CurrentDocument:Selection:Text}" category= cmdname=google executable=xdg-open icon=globe -includeStderr=false input= mimetypes= name=Google Selected Text output=Ignore reload=false save=None workingDir= [Tool 2] actionName=externaltool_gitcola arguments=-r %{CurrentDocument:Path} category=Git cmdname=git-cola executable=git-cola icon=git-cola -includeStderr=false input= mimetypes= name=git-cola output=Ignore reload=false save=None workingDir= [Tool 3] actionName=externaltool_gitk arguments= category=Git cmdname=gitk executable=gitk icon=git-gui -includeStderr=false input= mimetypes= name=gitk output=Ignore reload=false save=None workingDir=%{CurrentDocument:Path} [Tool 4] actionName=externaltool_gitblame arguments=gui blame %{CurrentDocument:FileName} category=Git cmdname=git-blame executable=git icon= -includeStderr=false input= mimetypes= name=git blame output=Ignore reload=false save=CurrentDocument workingDir=%{CurrentDocument:Path} [Tool 5] actionName=externaltool_QtQuick2Previewqmlscene arguments=%{CurrentDocument:FileName} category=Tools cmdname=qml-preview executable=qmlscene icon= -includeStderr=false input= mimetypes=text/x-qml name=Qt Quick 2 Preview (qmlscene) output=Ignore reload=false save=CurrentDocument workingDir=%{CurrentDocument:Path} [Tool 6] actionName=externaltool_InsertUUID arguments=%{UUID} category=Tools cmdname=uuid executable=echo icon= -includeStderr=false input= mimetypes= name=Insert UUID output=InsertAtCursor reload=false save=None workingDir= [Tool 7] actionName=externaltool_ClangFormatFullFile arguments=-i %{CurrentDocument:FileName} category=Tools cmdname=clang-format-file executable=clang-format icon= -includeStderr=false input= mimetypes= name=Clang Format Full File output=Ignore reload=true save=CurrentDocument workingDir=%{CurrentDocument:Path} [Tool 8] actionName=externaltool_ClangFormatSelectedText arguments=-assume-filename=%{CurrentDocument:FileName} category=Tools cmdname=clang-format-selection executable=clang-format icon= -includeStderr=false input=\s%{CurrentDocument:Selection:Text} mimetypes= name=Clang Format Selected Text output=ReplaceSelectedText reload=false save=None workingDir=%{CurrentDocument:Path} [Tool 9] actionName=externaltool_perl arguments=asdfasdfasdf category=Tools cmdname=perl executable=ls icon= -includeStderr=true input= mimetypes= name=perl output=InsertAtCursor reload=false save=None workingDir= diff --git a/addons/externaltools/externaltoolsplugin.cpp b/addons/externaltools/externaltoolsplugin.cpp index 2ed22284a..a2accda50 100644 --- a/addons/externaltools/externaltoolsplugin.cpp +++ b/addons/externaltools/externaltoolsplugin.cpp @@ -1,293 +1,300 @@ /* 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->showToolView(); pluginView->addToolStatus(i18n("Failed to expand executable '%1'.", copy->executable), copy.get()); return; } if (!macroExpander.expandMacrosShellQuote(copy->arguments)) { pluginView->showToolView(); pluginView->addToolStatus(i18n("Failed to expand argument '%1'.", copy->arguments), copy.get()); return; } if (!macroExpander.expandMacrosShellQuote(copy->workingDir)) { pluginView->showToolView(); pluginView->addToolStatus(i18n("Failed to expand working directory '%1'.", copy->workingDir), copy.get()); return; } if (!macroExpander.expandMacrosShellQuote(copy->input)) { 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 && 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 && 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())->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(); } + if (!runner->errorData().isEmpty()) { + auto pluginView = viewForMainWindow(view->mainWindow()); + pluginView->createToolView(); + pluginView->addToolStatus(i18n("Data written to stderr:\n%1", runner->errorData()), runner->tool()); + 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/kateexternaltool.cpp b/addons/externaltools/kateexternaltool.cpp index 24f25286e..2674db17e 100644 --- a/addons/externaltools/kateexternaltool.cpp +++ b/addons/externaltools/kateexternaltool.cpp @@ -1,139 +1,136 @@ /* 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 "kateexternaltool.h" #include #include namespace { QString toString(KateExternalTool::SaveMode saveMode) { switch (saveMode) { case KateExternalTool::SaveMode::None: return QStringLiteral("None"); case KateExternalTool::SaveMode::CurrentDocument: return QStringLiteral("CurrentDocument"); case KateExternalTool::SaveMode::AllDocuments: return QStringLiteral("AllDocuments"); } Q_ASSERT(false); // yout forgot a case above return QStringLiteral("None"); } KateExternalTool::SaveMode toSaveMode(const QString & mode) { if (mode == QStringLiteral("None")) return KateExternalTool::SaveMode::None; if (mode == QStringLiteral("CurrentDocument")) return KateExternalTool::SaveMode::CurrentDocument; if (mode == QStringLiteral("AllDocuments")) return KateExternalTool::SaveMode::AllDocuments; return KateExternalTool::SaveMode::None; } QString toString(KateExternalTool::OutputMode outputMode) { switch (outputMode) { case KateExternalTool::OutputMode::Ignore: return QStringLiteral("Ignore"); case KateExternalTool::OutputMode::InsertAtCursor: return QStringLiteral("InsertAtCursor"); case KateExternalTool::OutputMode::ReplaceSelectedText: return QStringLiteral("ReplaceSelectedText"); case KateExternalTool::OutputMode::ReplaceCurrentDocument: return QStringLiteral("ReplaceCurrentDocument"); case KateExternalTool::OutputMode::AppendToCurrentDocument: return QStringLiteral("AppendToCurrentDocument"); case KateExternalTool::OutputMode::InsertInNewDocument: return QStringLiteral("InsertInNewDocument"); case KateExternalTool::OutputMode::DisplayInPane: return QStringLiteral("DisplayInPane"); } Q_ASSERT(false); // yout forgot a case above return QStringLiteral("Ignore"); } KateExternalTool::OutputMode toOutputMode(const QString & mode) { if (mode == QStringLiteral("Ignore")) return KateExternalTool::OutputMode::Ignore; if (mode == QStringLiteral("InsertAtCursor")) return KateExternalTool::OutputMode::InsertAtCursor; if (mode == QStringLiteral("ReplaceSelectedText")) return KateExternalTool::OutputMode::ReplaceSelectedText; if (mode == QStringLiteral("ReplaceCurrentDocument")) return KateExternalTool::OutputMode::ReplaceCurrentDocument; if (mode == QStringLiteral("AppendToCurrentDocument")) return KateExternalTool::OutputMode::AppendToCurrentDocument; if (mode == QStringLiteral("InsertInNewDocument")) return KateExternalTool::OutputMode::InsertInNewDocument; if (mode == QStringLiteral("DisplayInPane")) return KateExternalTool::OutputMode::DisplayInPane; return KateExternalTool::OutputMode::Ignore; } } bool KateExternalTool::checkExec() const { return !QStandardPaths::findExecutable(executable).isEmpty(); } bool KateExternalTool::matchesMimetype(const QString& mt) const { return mimetypes.isEmpty() || mimetypes.contains(mt); } void KateExternalTool::load(const KConfigGroup& cg) { category = cg.readEntry("category", ""); name = cg.readEntry("name", ""); icon = cg.readEntry("icon", ""); executable = cg.readEntry("executable", ""); arguments = cg.readEntry("arguments", ""); input = cg.readEntry("input", ""); workingDir = cg.readEntry("workingDir", ""); mimetypes = cg.readEntry("mimetypes", QStringList()); actionName = cg.readEntry("actionName"); cmdname = cg.readEntry("cmdname"); saveMode = toSaveMode(cg.readEntry("save", "None")); reload = cg.readEntry("reload", false); outputMode = toOutputMode(cg.readEntry("output", "Ignore")); - includeStderr = cg.readEntry("includeStderr", false); hasexec = checkExec(); } void KateExternalTool::save(KConfigGroup& cg) const { cg.writeEntry("category", category); cg.writeEntry("name", name); cg.writeEntry("icon", icon); cg.writeEntry("executable", executable); cg.writeEntry("arguments", arguments); cg.writeEntry("input", input); cg.writeEntry("workingDir", workingDir); cg.writeEntry("mimetypes", mimetypes); cg.writeEntry("actionName", actionName); cg.writeEntry("cmdname", cmdname); cg.writeEntry("save", toString(saveMode)); cg.writeEntry("reload", reload); cg.writeEntry("output", toString(outputMode)); - cg.writeEntry("includeStderr", includeStderr); } bool operator==(const KateExternalTool & lhs, const KateExternalTool & rhs) { return lhs.category == rhs.category && lhs.name == rhs.name && lhs.icon == rhs.icon && lhs.executable == rhs.executable && lhs.arguments == rhs.arguments && lhs.input == rhs.input && lhs.workingDir == rhs.workingDir && lhs.mimetypes == rhs.mimetypes && lhs.actionName == rhs.actionName && lhs.cmdname == rhs.cmdname && lhs.saveMode == rhs.saveMode && lhs.reload == rhs.reload - && lhs.outputMode == rhs.outputMode - && lhs.includeStderr == rhs.includeStderr; + && lhs.outputMode == rhs.outputMode; } // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltool.h b/addons/externaltools/kateexternaltool.h index f49487822..5bb7976b7 100644 --- a/addons/externaltools/kateexternaltool.h +++ b/addons/externaltools/kateexternaltool.h @@ -1,128 +1,126 @@ /* 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_KATE_EXTERNALTOOL_H #define KTEXTEDITOR_KATE_EXTERNALTOOL_H #include #include #include class KConfigGroup; /** * This class defines a single external tool. */ class KateExternalTool { public: /** * Defines whether any document should be saved before running the tool. */ enum class SaveMode { //! Do not save any document. None, //! Save current document. CurrentDocument, //! Save all documents AllDocuments }; /** * Defines where to redirect stdout from the tool. */ enum class OutputMode { Ignore, InsertAtCursor, ReplaceSelectedText, ReplaceCurrentDocument, AppendToCurrentDocument, InsertInNewDocument, DisplayInPane }; public: /// The category used in the menu to categorize the tool. QString category; /// The name used in the menu. QString name; /// the icon to use in the menu. QString icon; /// The name or path of the executable. QString executable; /// The command line arguments. QString arguments; /// The stdin input. QString input; /// The working directory, if specified. QString workingDir; /// Optional list of mimetypes for which this action is valid. QStringList mimetypes; /// The name for the action. This is generated first time the /// action is is created. QString actionName; /// The name for the commandline. QString cmdname; /// Possibly save documents prior to activating the tool command. SaveMode saveMode = SaveMode::None; /// Reload current document after execution bool reload = false; /// Defines where to redirect the tool's output OutputMode outputMode = OutputMode::Ignore; - /// Include stderr output when running the tool. - bool includeStderr = false; public: /// This is set when loading the Tool from disk. bool hasexec = false; /** * @return true if mimetypes is empty, or the @p mimetype matches. */ bool matchesMimetype(const QString& mimetype) const; /** * @return true if "executable" exists and has the executable bit set, or is * empty. * This is run at least once, and the tool is disabled if it fails. */ bool checkExec() const; /** * Load tool data from the config group @p cg. */ void load(const KConfigGroup& cg); /** * Save tool data to the config group @p cg. */ void save(KConfigGroup& cg) const; }; /** * Compares for equality. All fields have to match. */ bool operator==(const KateExternalTool & lhs, const KateExternalTool & rhs); // for use in QVariant (QAction::setData() and QAction::data()) Q_DECLARE_METATYPE(KateExternalTool*) #endif // KTEXTEDITOR_KATE_EXTERNALTOOL_H // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltoolsconfigwidget.cpp b/addons/externaltools/kateexternaltoolsconfigwidget.cpp index c1f188e25..dc1c44c9d 100644 --- a/addons/externaltools/kateexternaltoolsconfigwidget.cpp +++ b/addons/externaltools/kateexternaltoolsconfigwidget.cpp @@ -1,454 +1,452 @@ /* 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. */ // TODO // Icons // Direct shortcut setting #include "kateexternaltoolsconfigwidget.h" #include "externaltoolsplugin.h" #include "kateexternaltool.h" #include "katemacroexpander.h" #include "katetoolrunner.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr int ToolRole = Qt::UserRole + 1; /** * Helper function to create a QStandardItem that internally stores a pointer to a KateExternalTool. */ QStandardItem * newToolItem(const QPixmap& icon, KateExternalTool* tool) { auto item = new QStandardItem(icon, tool->name); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled); item->setData(QVariant::fromValue(reinterpret_cast(tool)), ToolRole); return item; } /** * Helper function to return an internally stored KateExternalTool for a QStandardItem. * If a nullptr is returned, it means the QStandardItem is a category. */ KateExternalTool* toolForItem(QStandardItem* item) { return item ? reinterpret_cast(item->data(ToolRole).value()) : nullptr; } } // BEGIN KateExternalToolServiceEditor KateExternalToolServiceEditor::KateExternalToolServiceEditor(KateExternalTool* tool, QWidget* parent) : QDialog(parent) , m_tool(tool) { setWindowTitle(i18n("Edit External Tool")); setWindowIcon(QIcon::fromTheme(QStringLiteral("system-run"))); ui = new Ui::ToolDialog(); ui->setupUi(this); ui->btnIcon->setIconSize(KIconLoader::SizeSmall); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &KateExternalToolServiceEditor::slotOKClicked); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(ui->btnMimeType, &QToolButton::clicked, this, &KateExternalToolServiceEditor::showMTDlg); Q_ASSERT(m_tool != nullptr); ui->edtName->setText(m_tool->name); if (!m_tool->icon.isEmpty()) ui->btnIcon->setIcon(m_tool->icon); ui->edtExecutable->setText(m_tool->executable); ui->edtArgs->setText(m_tool->arguments); ui->edtInput->setText(m_tool->input); ui->edtWorkingDir->setText(m_tool->workingDir); ui->edtMimeType->setText(m_tool->mimetypes.join(QStringLiteral("; "))); ui->cmbSave->setCurrentIndex(static_cast(m_tool->saveMode)); ui->chkReload->setChecked(m_tool->reload); ui->cmbOutput->setCurrentIndex(static_cast(m_tool->outputMode)); - ui->chkIncludeStderr->setChecked(m_tool->includeStderr); ui->edtCommand->setText(m_tool->cmdname); } void KateExternalToolServiceEditor::slotOKClicked() { if (ui->edtName->text().isEmpty() || ui->edtExecutable->text().isEmpty()) { QMessageBox::information(this, i18n("External Tool"), i18n("You must specify at least a name and an executable")); return; } accept(); } void KateExternalToolServiceEditor::showMTDlg() { QString text = i18n("Select the MimeTypes for which to enable this tool."); QStringList list = ui->edtMimeType->text().split(QRegularExpression(QStringLiteral("\\s*;\\s*")), QString::SkipEmptyParts); KMimeTypeChooserDialog d(i18n("Select Mime Types"), text, list, QStringLiteral("text"), this); if (d.exec() == QDialog::Accepted) { ui->edtMimeType->setText(d.chooser()->mimeTypes().join(QStringLiteral(";"))); } } // END KateExternalToolServiceEditor static std::vector childItems(const QStandardItem * item) { // collect all KateExternalTool items std::vector children; for (int i = 0; i < item->rowCount(); ++i) { children.push_back(item->child(i)); } return children; } static std::vector collectTools(const QStandardItemModel & model) { std::vector tools; for (auto categoryItem : childItems(model.invisibleRootItem())) { for (auto child : childItems(categoryItem)) { auto tool = toolForItem(child); Q_ASSERT(tool != nullptr); tools.push_back(tool); } } return tools; } // BEGIN KateExternalToolsConfigWidget KateExternalToolsConfigWidget::KateExternalToolsConfigWidget(QWidget* parent, KateExternalToolsPlugin* plugin) : KTextEditor::ConfigPage(parent) , m_plugin(plugin) { setupUi(this); layout()->setMargin(0); lbTools->setModel(&m_toolsModel); lbTools->setSelectionMode(QAbstractItemView::SingleSelection); lbTools->setDragEnabled(true); lbTools->setAcceptDrops(true); lbTools->setDefaultDropAction(Qt::MoveAction); lbTools->setDropIndicatorShown(true); lbTools->setDragDropOverwriteMode(false); lbTools->setDragDropMode(QAbstractItemView::InternalMove); // Add... button popup menu auto addMenu = new QMenu(); auto addToolAction = addMenu->addAction(i18n("Add Tool...")); auto addCategoryAction = addMenu->addAction(i18n("Add Category")); btnAdd->setMenu(addMenu); connect(addCategoryAction, &QAction::triggered, this, &KateExternalToolsConfigWidget::slotAddCategory); connect(addToolAction, &QAction::triggered, this, &KateExternalToolsConfigWidget::slotAddTool); connect(btnRemove, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotRemove); connect(btnEdit, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotEdit); connect(lbTools->selectionModel(), &QItemSelectionModel::currentChanged, [this](){ slotSelectionChanged(); }); connect(lbTools, &QTreeView::doubleClicked, this, &KateExternalToolsConfigWidget::slotEdit); m_config = new KConfig(QStringLiteral("externaltools"), KConfig::NoGlobals, QStandardPaths::ApplicationsLocation); // reset triggers a reload of the existing tools reset(); slotSelectionChanged(); connect(&m_toolsModel, &QStandardItemModel::itemChanged, [this](){ m_changed = true; Q_EMIT changed(); }); } KateExternalToolsConfigWidget::~KateExternalToolsConfigWidget() { clearTools(); delete m_config; } QString KateExternalToolsConfigWidget::name() const { return i18n("External Tools"); } QString KateExternalToolsConfigWidget::fullName() const { return i18n("External Tools"); } QIcon KateExternalToolsConfigWidget::icon() const { return QIcon::fromTheme(QStringLiteral("system-run")); } void KateExternalToolsConfigWidget::reset() { clearTools(); m_toolsModel.invisibleRootItem()->setFlags(Qt::NoItemFlags); // the "Uncategorized" category always exists m_noCategory = addCategory(i18n("Uncategorized")); m_noCategory->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled); // create other tools and categories const auto tools = m_plugin->tools(); for (auto tool : tools) { auto clone = new KateExternalTool(*tool); auto item = newToolItem(clone->icon.isEmpty() ? blankIcon() : SmallIcon(clone->icon), clone); auto category = clone->category.isEmpty() ? m_noCategory : addCategory(clone->category); category->appendRow(item); } lbTools->expandAll(); m_changed = false; } QPixmap KateExternalToolsConfigWidget::blankIcon() { QPixmap pm(KIconLoader::SizeSmall, KIconLoader::SizeSmall); pm.fill(); pm.setMask(pm.createHeuristicMask()); return pm; } void KateExternalToolsConfigWidget::apply() { if (!m_changed) return; m_changed = false; // collect all KateExternalTool items std::vector tools; for (auto categoryItem : childItems(m_toolsModel.invisibleRootItem())) { const QString category = (categoryItem == m_noCategory) ? QString() : categoryItem->text(); for (auto child : childItems(categoryItem)) { auto tool = toolForItem(child); Q_ASSERT(tool != nullptr); // at this point, we have to overwrite the category, since it may have changed (and we never tracked this) tool->category = category; tools.push_back(tool); } } // write tool configuration to disk m_config->group("Global").writeEntry("tools", static_cast(tools.size())); for (size_t i = 0; i < tools.size(); i++) { const QString section = QStringLiteral("Tool ") + QString::number(i); KConfigGroup cg(m_config, section); tools[i]->save(cg); } m_config->sync(); m_plugin->reload(); } void KateExternalToolsConfigWidget::slotSelectionChanged() { // update button state auto item = m_toolsModel.itemFromIndex(lbTools->currentIndex()); const bool isToolItem = toolForItem(item) != nullptr; const bool isCategory = item && !isToolItem; btnEdit->setEnabled(isToolItem || isCategory); btnRemove->setEnabled(isToolItem); } bool KateExternalToolsConfigWidget::editTool(KateExternalTool* tool) { bool changed = false; KateExternalToolServiceEditor editor(tool, this); editor.resize(m_config->group("Editor").readEntry("Size", QSize())); if (editor.exec() == QDialog::Accepted) { tool->name = editor.ui->edtName->text(); tool->icon = editor.ui->btnIcon->icon(); tool->executable = editor.ui->edtExecutable->text(); tool->arguments = editor.ui->edtArgs->text(); tool->input = editor.ui->edtInput->toPlainText(); tool->workingDir = editor.ui->edtWorkingDir->text(); tool->mimetypes = editor.ui->edtMimeType->text().split(QRegularExpression(QStringLiteral("\\s*;\\s*")), QString::SkipEmptyParts); tool->saveMode = static_cast(editor.ui->cmbSave->currentIndex()); tool->reload = editor.ui->chkReload->isChecked(); tool->outputMode = static_cast(editor.ui->cmbOutput->currentIndex()); - tool->includeStderr = editor.ui->chkIncludeStderr->isChecked(); tool->cmdname = editor.ui->edtCommand->text(); // sticky action collection name, never changes again, so that shortcuts stay tool->actionName = QStringLiteral("externaltool_") + QString(tool->name).remove(QRegularExpression(QStringLiteral("\\W+"))); changed = true; } m_config->group("Editor").writeEntry("Size", editor.size()); m_config->sync(); return changed; } QStandardItem * KateExternalToolsConfigWidget::addCategory(const QString & category) { // searach for existing category auto items = m_toolsModel.findItems(category); if (!items.empty()) { return items.front(); } // ...otherwise, create it auto item = new QStandardItem(category); // for now, categories are not movable, otherwise, the use can move a // category into another category, which is not supported right now item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled | Qt::ItemIsEditable); m_toolsModel.appendRow(item); return item; } QStandardItem * KateExternalToolsConfigWidget::currentCategory() const { auto index = lbTools->currentIndex(); if (!index.isValid()) { return m_noCategory; } auto item = m_toolsModel.itemFromIndex(index); auto tool = toolForItem(item); if (tool) { // the parent of a ToolItem is always a category return item->parent(); } // item is no ToolItem, so we must have a category at hand return item; } void KateExternalToolsConfigWidget::clearTools() { // collect all KateExternalTool items and delete them, since they are copies std::vector tools = collectTools(m_toolsModel); qDeleteAll(tools); tools.clear(); m_toolsModel.clear(); } void KateExternalToolsConfigWidget::slotAddCategory() { // find unique name QString name = i18n("New Category"); int i = 1; while (!m_toolsModel.findItems(name, Qt::MatchFixedString).isEmpty()) { name = (i18n("New Category %1", i++)); } // add category and switch to edit mode auto item = addCategory(name); lbTools->edit(item->index()); } //! Helper that ensures that tool->actionName is unique static void makeActionNameUnique(KateExternalTool* tool, const std::vector & tools) { QString name = tool->actionName; int i = 1; bool notUnique = true; while (notUnique) { auto it = std::find_if(tools.cbegin(), tools.cend(), [&name](const KateExternalTool* tool) { return tool->actionName == name; }); if (it == tools.cend()) { break; } name = tool->actionName + QString::number(i); ++i; } tool->actionName = name; } void KateExternalToolsConfigWidget::slotAddTool() { auto t = new KateExternalTool(); if (editTool(t)) { makeActionNameUnique(t, collectTools(m_toolsModel)); auto item = newToolItem(t->icon.isEmpty() ? blankIcon() : SmallIcon(t->icon), t); auto category = currentCategory(); category->appendRow(item); lbTools->setCurrentIndex(item->index()); Q_EMIT changed(); m_changed = true; } else { delete t; } } void KateExternalToolsConfigWidget::slotRemove() { auto item = m_toolsModel.itemFromIndex(lbTools->currentIndex()); auto tool = toolForItem(item); if (tool) { item->parent()->removeRow(item->index().row()); delete tool; Q_EMIT changed(); m_changed = true; } } void KateExternalToolsConfigWidget::slotEdit() { auto item = m_toolsModel.itemFromIndex(lbTools->currentIndex()); auto tool = toolForItem(item); if (!tool) { if (item) { lbTools->edit(item->index()); } return; } // show the item in an editor if (editTool(tool)) { // renew the icon and name item->setText(tool->name); item->setIcon(tool->icon.isEmpty() ? blankIcon() : SmallIcon(tool->icon)); Q_EMIT changed(); m_changed = true; } } // END KateExternalToolsConfigWidget // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/katetoolrunner.cpp b/addons/externaltools/katetoolrunner.cpp index b8b62a23a..769c95146 100644 --- a/addons/externaltools/katetoolrunner.cpp +++ b/addons/externaltools/katetoolrunner.cpp @@ -1,104 +1,103 @@ /* 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 "katetoolrunner.h" #include "kateexternaltool.h" #include #include #include KateToolRunner::KateToolRunner(std::unique_ptr tool, KTextEditor::View * view, QObject* parent) : QObject(parent) , m_view(view) , m_tool(std::move(tool)) , m_process(new QProcess()) { + m_process->setProcessChannelMode(QProcess::SeparateChannels); } KateToolRunner::~KateToolRunner() { } KTextEditor::View* KateToolRunner::view() const { return m_view; } KateExternalTool* KateToolRunner::tool() const { return m_tool.get(); } void KateToolRunner::run() { - if (m_tool->includeStderr) { - m_process->setProcessChannelMode(QProcess::MergedChannels); - } - if (!m_tool->workingDir.isEmpty()) { m_process->setWorkingDirectory(m_tool->workingDir); } else if (m_view) { // if nothing is set, use the current document's directory const auto url = m_view->document()->url(); if (url.isValid()) { const QString path = m_view->document()->url().toString(QUrl::RemoveScheme | QUrl::RemoveFilename); m_process->setWorkingDirectory(path); } } - QObject::connect(m_process.get(), &QProcess::readyRead, this, &KateToolRunner::slotReadyRead); - QObject::connect(m_process.get(), static_cast(&QProcess::finished), this, - &KateToolRunner::handleToolFinished); + QObject::connect(m_process.get(), &QProcess::readyReadStandardOutput, [this]() { + m_stdout += m_process->readAllStandardOutput(); + }); + QObject::connect(m_process.get(), &QProcess::readyReadStandardError, [this]() { + m_stderr += m_process->readAllStandardError(); + }); + QObject::connect(m_process.get(), static_cast(&QProcess::finished), + [this](int exitCode, QProcess::ExitStatus exitStatus) { + Q_EMIT toolFinished(this, exitCode, exitStatus == QProcess::CrashExit); + }); // Write stdin to process, if applicable, then close write channel QObject::connect(m_process.get(), &QProcess::started, [this]() { if (!m_tool->input.isEmpty()) { m_process->write(m_tool->input.toLocal8Bit()); } m_process->closeWriteChannel(); }); const QStringList args = KShell::splitArgs(m_tool->arguments); m_process->start(m_tool->executable, args); } void KateToolRunner::waitForFinished() { m_process->waitForFinished(); } QString KateToolRunner::outputData() const { - return QString::fromLocal8Bit(m_output); -} - -void KateToolRunner::slotReadyRead() -{ - m_output += m_process->readAll(); + return QString::fromLocal8Bit(m_stdout); } -void KateToolRunner::handleToolFinished(int exitCode, QProcess::ExitStatus exitStatus) +QString KateToolRunner::errorData() const { - Q_EMIT toolFinished(this, exitCode, exitStatus == QProcess::CrashExit); + return QString::fromLocal8Bit(m_stderr); } // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/katetoolrunner.h b/addons/externaltools/katetoolrunner.h index 9b969895a..24dcfa3d1 100644 --- a/addons/externaltools/katetoolrunner.h +++ b/addons/externaltools/katetoolrunner.h @@ -1,120 +1,114 @@ /* 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_EXTERNALTOOLRUNNER_H #define KTEXTEDITOR_EXTERNALTOOLRUNNER_H #include #include #include #include #include #include class KateExternalTool; class QProcess; namespace KTextEditor { class View; } /** * Helper class to run a KateExternalTool. */ class KateToolRunner : public QObject { Q_OBJECT public: /** * Constructor that will run @p tool in the run() method. * The @p view can later be retrieved again with view() to process the data when the tool is finished. */ KateToolRunner(std::unique_ptr tool, KTextEditor::View * view, QObject* parent = nullptr); KateToolRunner(const KateToolRunner&) = delete; void operator=(const KateToolRunner&) = delete; ~KateToolRunner(); /** * Returns the view that was active when running the tool. * @warning May be a nullptr, since the view could have been closed in the meantime. */ KTextEditor::View* view() const; /** * Returns the tool that was passed in the constructor. */ KateExternalTool* tool() const; /** * Starts a child process that executes the tool. */ void run(); /** * Blocking call that waits until the tool is finised. * Used internally for unit testing. */ void waitForFinished(); /** * Returns the data that was collected on stdout. - * stderr is also included if includeStderr was set. */ QString outputData() const; -Q_SIGNALS: - /** - * This signal is emitted when the tool is finished. - */ - void toolFinished(KateToolRunner* runner, int exitCode, bool crashed); - -private Q_SLOTS: /** - * More tool output is available + * Returns the data that was collected on stderr. */ - void slotReadyRead(); + QString errorData() const; +Q_SIGNALS: /** - * Analysis finished - * @param exitCode analyzer process exit code - * @param exitStatus analyzer process exit status + * This signal is emitted when the tool is finished. */ - void handleToolFinished(int exitCode, QProcess::ExitStatus exitStatus); + void toolFinished(KateToolRunner* runner, int exitCode, bool crashed); private: //! Use QPointer here, since the View may be closed in the meantime. QPointer m_view; //! We are the owner of the tool (it was copied) std::unique_ptr m_tool; //! Child process that runs the tool std::unique_ptr m_process; - //! Collect stdout, and optionally also stderr - QByteArray m_output; + //! Collect stdout + QByteArray m_stdout; + + //! Collect stderr + QByteArray m_stderr; }; #endif // KTEXTEDITOR_EXTERNALTOOLRUNNER_H // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/tooldialog.ui b/addons/externaltools/tooldialog.ui index 598990b83..a51ca39c3 100644 --- a/addons/externaltools/tooldialog.ui +++ b/addons/externaltools/tooldialog.ui @@ -1,365 +1,357 @@ ToolDialog 0 0 470 - 510 + 532 Edit Tool Na&me: edtName The name will be displayed in the 'Tools->External Tools' menu. Short name of the tool E&xecutable: edtExecutable The executable used by the command. This is used to check if a tool should be displayed; if not set, the first word of <em>command</em> will be used. Application or interpreter ../../../../../../../../.designer/backup../../../../../../../../.designer/backup QToolButton::InstantPopup Ar&guments: edtArgs + + + + + + Command line arguments + + + + + + + + ../../../../../../../../.designer/backup../../../../../../../../.designer/backup + + + QToolButton::InstantPopup + + + + + &Input: edtInput <p>The script to execute to invoke the tool. The script is passed to /bin/sh for execution. The following macros will be expanded:</p><ul><li><code>%URL</code> - the URL of the current document.</li><li><code>%URLs</code> - a list of the URLs of all open documents.</li><li><code>%directory</code> - the URL of the directory containing the current document.</li><li><code>%filename</code> - the filename of the current document.</li><li><code>%line</code> - the current line of the text cursor in the current view.</li><li><code>%column</code> - the column of the text cursor in the current view.</li><li><code>%selection</code> - the selected text in the current view.</li><li><code>%text</code> - the text of the current document.</li></ul> Optional standard input ../../../../../../../../.designer/backup../../../../../../../../.designer/backup QToolButton::InstantPopup + + + + Working &directory: + + + edtWorkingDir + + + + + + + + + Uses current document path if empty + + + + + + + + ../../../../../../../../.designer/backup../../../../../../../../.designer/backup + + + QToolButton::InstantPopup + + + + + Mime &types: edtMimeType A semicolon-separated list of mime types for which this tool should be available; if this is left empty, the tool is always available. To choose from known mimetypes, press the button on the right. Show tool only for given mime types Click for a dialog that can help you create a list of mimetypes. ../../../../../../../../.designer/backup../../../../../../../../.designer/backup Save: cmbSave You can choose to save the current or all [modified] documents prior to running the command. This is helpful if you want to pass URLs to an application like, for example, an FTP client. None Current Document All Documents Reload current document after execution O&utput: cmbOutput Ignore Insert at Cursor Position Replace Selected Text Replace Current Document Append to Current Document Insert in New Document Display in Pane - + Editor command: edtCommand - + If you specify a name here, you can invoke the command from the view command line with exttool-the_name_you_specified_here. Please do not use spaces or tabs in the name. Optional command bar name - + QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - Working &directory: - - - edtWorkingDir - - - - - - - - - Uses current document path if empty - - - - - - - - ../../../../../../../../.designer/backup../../../../../../../../.designer/backup - - - QToolButton::InstantPopup - - - - - - - - - - - Command line arguments - - - - - - - - ../../../../../../../../.designer/backup../../../../../../../../.designer/backup - - - QToolButton::InstantPopup - - - - - - - - - Include output from stderr - - - KIconButton QPushButton
kiconbutton.h
edtName btnIcon edtExecutable btnExecutable edtArgs btnArgs edtInput btnInput edtWorkingDir btnWorkingDir edtMimeType btnMimeType cmbSave chkReload cmbOutput - chkIncludeStderr edtCommand