diff --git a/addons/CMakeLists.txt b/addons/CMakeLists.txt --- a/addons/CMakeLists.txt +++ b/addons/CMakeLists.txt @@ -49,6 +49,9 @@ # pipe text through some external command ecm_optional_add_subdirectory (textfilter) +# external tools +ecm_optional_add_subdirectory (externaltools) + # Rust complection plugin ecm_optional_add_subdirectory (rustcompletion) diff --git a/addons/externaltools/CMakeLists.txt b/addons/externaltools/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/addons/externaltools/CMakeLists.txt @@ -0,0 +1,42 @@ +project(externaltoolsplugin) +add_definitions(-DTRANSLATION_DOMAIN=\"externaltoolsplugin\") + +include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) + +set(externaltoolsplugin_PART_SRCS + externaltoolsplugin.cpp + externaltools.cpp + katetoolrunner.cpp + kateexternaltool.cpp + katemacroexpander.cpp + kateexternaltoolscommand.cpp + kateexternaltoolsconfigwidget.cpp +) + +# resource for ui file and stuff +qt5_add_resources(externaltoolsplugin_PART_SRCS plugin.qrc) + +set(externaltoolsplugin_PART_UI + configwidget.ui + tooldialog.ui +) +ki18n_wrap_ui(externaltoolsplugin_PART_SRCS ${externaltoolsplugin_PART_UI} ) +add_library(externaltoolsplugin MODULE ${externaltoolsplugin_PART_SRCS}) + +# we compile in the .desktop file +kcoreaddons_desktop_to_json (externaltoolsplugin externaltoolsplugin.desktop) + +target_link_libraries(externaltoolsplugin + KF5::CoreAddons + KF5::IconThemes + KF5::TextEditor + KF5::I18n +) + +########### install files ############### +install(TARGETS externaltoolsplugin DESTINATION ${PLUGIN_INSTALL_DIR}/ktexteditor ) + +############# unit tests ################ +if (BUILD_TESTING) + add_subdirectory(autotests) +endif() diff --git a/addons/externaltools/Messages.sh b/addons/externaltools/Messages.sh new file mode 100644 --- /dev/null +++ b/addons/externaltools/Messages.sh @@ -0,0 +1,3 @@ +#! /bin/sh +$EXTRACTRC *.rc *.ui >> rc.cpp +$XGETTEXT *.cpp -o $podir/externaltoolsplugin.pot diff --git a/addons/externaltools/autotests/CMakeLists.txt b/addons/externaltools/autotests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/addons/externaltools/autotests/CMakeLists.txt @@ -0,0 +1,12 @@ +include(ECMMarkAsTest) + +# Project Plugin +add_executable(externaltools_test + externaltooltest.cpp + ../kateexternaltool.cpp + ../katetoolrunner.cpp + ../katemacroexpander.cpp +) +add_test(plugin-externaltools_test externaltools_test) +target_link_libraries(externaltools_test kdeinit_kate Qt5::Test) +ecm_mark_as_test(externaltools_test) diff --git a/addons/externaltools/autotests/externaltooltest.h b/addons/externaltools/autotests/externaltooltest.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/autotests/externaltooltest.h @@ -0,0 +1,42 @@ +/* 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 KATE_TOOLRUNNER_TEST_H +#define KATE_TOOLRUNNER_TEST_H + +#include + +class ExternalToolTest : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + +private Q_SLOTS: + void testLoadSave(); + void testRunListDirectory(); + void testRunTac(); +}; + +#endif + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/autotests/externaltooltest.cpp b/addons/externaltools/autotests/externaltooltest.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/autotests/externaltooltest.cpp @@ -0,0 +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. + */ + +#include "externaltooltest.h" +#include "../kateexternaltool.h" +#include "../katetoolrunner.h" + +#include +#include + +#include +#include + +QTEST_MAIN(ExternalToolTest) + +void ExternalToolTest::initTestCase() +{ +} + +void ExternalToolTest::cleanupTestCase() +{ +} + +void ExternalToolTest::testLoadSave() +{ + KConfig config; + KConfigGroup cg(&config, "tool"); + + KateExternalTool tool; + tool.name = QStringLiteral("git cola"); + tool.icon = QStringLiteral("git-cola"); + tool.executable = QStringLiteral("git-cola"); + tool.arguments = QStringLiteral("none"); + tool.input = QStringLiteral("in"); + tool.workingDir = QStringLiteral("/usr/bin"); + tool.mimetypes = QStringList{ QStringLiteral("everything") }; + tool.hasexec = true; + tool.actionName = QStringLiteral("asdf"); + tool.cmdname = QStringLiteral("git-cola"); + tool.saveMode = KateExternalTool::SaveMode::None; + + tool.save(cg); + + KateExternalTool clonedTool; + clonedTool.load(cg); + QCOMPARE(tool.name, clonedTool.name); +} + +void ExternalToolTest::testRunListDirectory() +{ + auto tool = new KateExternalTool(); + tool->name = QStringLiteral("ls"); + tool->icon = QStringLiteral("none"); + tool->executable = QStringLiteral("ls"); + tool->arguments = QStringLiteral("/usr"); + tool->workingDir = QStringLiteral("/tmp"); + tool->mimetypes = QStringList{}; + tool->hasexec = true; + tool->actionName = QStringLiteral("ls"); + tool->cmdname = QStringLiteral("ls"); + tool->saveMode = KateExternalTool::SaveMode::None; + + // 1. /tmp $ ls /usr + KateToolRunner runner1(tool); + runner1.run(); + runner1.waitForFinished(); + QVERIFY(runner1.outputData().contains(QStringLiteral("bin"))); + + // 2. /usr $ ls + auto tool2 = new KateExternalTool(*tool); + tool2->arguments.clear(); + tool2->workingDir = QStringLiteral("/usr"); + KateToolRunner runner2(tool2); + runner2.run(); + runner2.waitForFinished(); + QVERIFY(runner2.outputData().contains(QStringLiteral("bin"))); + + // 1. and 2. must give the same result + QCOMPARE(runner1.outputData(), runner2.outputData()); +} + +void ExternalToolTest::testRunTac() +{ + auto tool = new KateExternalTool(); + tool->name = QStringLiteral("tac"); + tool->executable = QStringLiteral("tac"); + tool->input = QStringLiteral("a\nb\nc\n"); + tool->saveMode = KateExternalTool::SaveMode::None; + + // run tac to reverse order + KateToolRunner runner(tool); + runner.run(); + runner.waitForFinished(); + QCOMPARE(runner.outputData(), QStringLiteral("c\nb\na\n")); +} + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/configwidget.ui b/addons/externaltools/configwidget.ui new file mode 100644 --- /dev/null +++ b/addons/externaltools/configwidget.ui @@ -0,0 +1,118 @@ + + + ExternalToolsConfigWidget + + + + 0 + 0 + 504 + 296 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Qt::Vertical + + + + 20 + 98 + + + + + + + + Qt::Vertical + + + + 17 + 108 + + + + + + + + + + &New... + + + + + + + Edit&... + + + + + + + &Remove + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + New &Category + + + + + + + + + + + + + + + This list shows all the configured tools, represented by their menu text. + + + + + + + + + + diff --git a/addons/externaltools/externaltools b/addons/externaltools/externaltools new file mode 100644 --- /dev/null +++ b/addons/externaltools/externaltools @@ -0,0 +1,23 @@ +[Global] +tools=Tool 0,---,Tool 2 +version=1 + +[Tool 0] +actionName=externaltool_GoogleSelection +cmdname=google-selection +command=[ -n "%selection" ] && kfmclient openURL "gg:%selection" +executable=kfmclient +icon=globe +mimetypes= +name=Google Selection +save=0 + +[Tool 2] +actionName=externaltool_RunScript +cmdname=runscript +command=konsole -e sh -c "cd %directory && pwd && chmod -vc a+x %filename && ./%filename ; echo Press any key to continue. && read -n 1" +executable=konsole +icon=run +mimetypes= +name=Run Script +save=1 diff --git a/addons/externaltools/externaltools.h b/addons/externaltools/externaltools.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/externaltools.h @@ -0,0 +1,81 @@ +/* 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; + +/** + * 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; +}; + +#endif // KTEXTEDITOR_EXTERNALTOOLS_H + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/externaltools.cpp b/addons/externaltools/externaltools.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/externaltools.cpp @@ -0,0 +1,107 @@ +/* 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 "externaltools.h" +#include "externaltoolsplugin.h" +#include "kateexternaltool.h" + +#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() {} + +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 + 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); + addAction(a); + } + } + + // 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(); + const bool toolActive = tool->mimetypes.isEmpty() || tool->mimetypes.contains(mimeType); + action->setEnabled(toolActive); + } + } +} +// END KateExternalToolsMenuAction + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/externaltoolsplugin.h b/addons/externaltools/externaltoolsplugin.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/externaltoolsplugin.h @@ -0,0 +1,140 @@ +/* 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_PLUGIN_H +#define KTEXTEDITOR_EXTERNALTOOLS_PLUGIN_H + +#include + +#include +#include + +namespace KTextEditor { class View; } + +class KateExternalToolsMenuAction; +class KateExternalToolsPluginView; +class KateExternalToolsCommand; +class KateExternalTool; +class KateToolRunner; + +class KateExternalToolsPlugin : public KTextEditor::Plugin +{ + Q_OBJECT + +public: + explicit KateExternalToolsPlugin(QObject* parent = nullptr, const QList& = QList()); + virtual ~KateExternalToolsPlugin(); + + /** + * Reimplemented to return the number of config pages, in this case 1. + */ + int configPages() const override; + + /** + * Reimplemented to return the KateExternalToolConfigWidget for number==0. + */ + KTextEditor::ConfigPage* configPage(int number = 0, QWidget* parent = nullptr) override; + + /** + * Reimplemented to instanciate a PluginView for each MainWindow. + */ + QObject* createView(KTextEditor::MainWindow* mainWindow) override; + + /** + * Reloads the external tools from disk. + */ + void reload(); + + /** + * Returns a list of KTextEDitor::Command strings. This is needed by + * the KateExternalToolsCommand constructor to pass the list of commands to + * the KTextEditor::Editor. + */ + QStringList commands() const; + + /** + * Returns the KateExternalTool for a specific command line command 'cmd. + */ + const KateExternalTool* toolForCommand(const QString& cmd) const; + + /** + * Returns a list of all existing external tools. + */ + const QVector tools() const; + + /** + * Executes the tool based on the view as current document. + */ + void runTool(const KateExternalTool& tool, KTextEditor::View* view); + +Q_SIGNALS: + /** + * This signal is emitted whenever the external tools change. + * This is typically the case when external tools were modified, + * added, or removed via the config page. + */ + void externalToolsChanged(); + +private: + QVector m_tools; + QStringList m_commands; + KateExternalToolsCommand* m_command = nullptr; + +private Q_SLOT: + /** + * Called whenever an external tool is done. + */ + void handleToolFinished(KateToolRunner* runner); +}; + +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(); + +private: + KateExternalToolsPlugin* m_plugin; + KTextEditor::MainWindow* m_mainWindow; + KateExternalToolsMenuAction* m_externalToolsMenu = nullptr; +}; + +#endif + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/externaltoolsplugin.cpp b/addons/externaltools/externaltoolsplugin.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/externaltoolsplugin.cpp @@ -0,0 +1,219 @@ +/* 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 "kateexternaltool.h" +#include "kateexternaltoolscommand.h" +#include "katemacroexpander.h" +#include "katetoolrunner.h" +#include "kateexternaltoolsconfigwidget.h" +#include "externaltools.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() +{ + m_commands.clear(); + + KConfig _config(QStringLiteral("externaltools"), KConfig::NoGlobals, QStandardPaths::ApplicationsLocation); + KConfigGroup config(&_config, "Global"); + const QStringList tools = config.readEntry("tools", QStringList()); + + for (QStringList::const_iterator it = tools.begin(); it != tools.end(); ++it) { + config = KConfigGroup(&_config, *it); + + 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"))) { + delete m_command; + 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) { + 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; + } + } + } + + // copy tool + auto copy = new KateExternalTool(tool); + + MacroExpander macroExpander(view); + if (!macroExpander.expandMacrosShellQuote(copy->arguments)) { + KMessageBox::sorry(view, i18n("Failed to expand the arguments '%1'.", copy->arguments), + i18n("Kate External Tools")); + return; + } + + if (!macroExpander.expandMacrosShellQuote(copy->workingDir)) { + KMessageBox::sorry(view, i18n("Failed to expand the working directory '%1'.", copy->workingDir), + i18n("Kate External Tools")); + 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(copy, this); + connect(runner, &KateToolRunner::toolFinished, this, &KateExternalToolsPlugin::handleToolFinished); + runner->run(); +} + +void KateExternalToolsPlugin::handleToolFinished(KateToolRunner* runner) +{ + runner->deleteLater(); +} + +int KateExternalToolsPlugin::configPages() const +{ + return 1; +} + +KTextEditor::ConfigPage* KateExternalToolsPlugin::configPage(int number, QWidget* parent) +{ + if (number == 0) { + return new KateExternalToolsConfigWidget(parent, this); + } + return nullptr; +} + +KateExternalToolsPluginView::KateExternalToolsPluginView(KTextEditor::MainWindow* mainWindow, + KateExternalToolsPlugin* plugin) + : QObject(mainWindow) + , m_plugin(plugin) + , m_mainWindow(mainWindow) +{ + 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); +} + +void KateExternalToolsPluginView::rebuildMenu() +{ + if (m_externalToolsMenu) { + KXMLGUIFactory* f = factory(); + f->removeClient(this); + reloadXML(); + m_externalToolsMenu->reload(); + f->addClient(this); + } +} + +KateExternalToolsPluginView::~KateExternalToolsPluginView() +{ + m_mainWindow->guiFactory()->removeClient(this); + + delete m_externalToolsMenu; + m_externalToolsMenu = nullptr; +} + +KTextEditor::MainWindow* KateExternalToolsPluginView::mainWindow() const +{ + return m_mainWindow; +} + +#include "externaltoolsplugin.moc" + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/externaltoolsplugin.desktop b/addons/externaltools/externaltoolsplugin.desktop new file mode 100644 --- /dev/null +++ b/addons/externaltools/externaltoolsplugin.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Service +ServiceTypes=KTextEditor/Plugin +X-KDE-Library=externaltoolsplugin +Name=External Tools +Comment=External Tools diff --git a/addons/externaltools/kateexternaltool.h b/addons/externaltools/kateexternaltool.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltool.h @@ -0,0 +1,128 @@ +/* + This file is part of the Kate text editor of the KDE project. + It describes a "external tools" action for kate and provides a dialog + page to configure that. + + 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. + + --- + Copyright (C) 2004 Anders Lund + Copyright (C) 2019 Dominik Haumann +*/ + +#ifndef KTEXTEDITOR_KATE_EXTERNALTOOL_H +#define KTEXTEDITOR_KATE_EXTERNALTOOL_H + +#include +#include +#include + +class KConfigGroup; + +/** + * This class defines a single external tool. + */ +class KateExternalTool +{ + Q_GADGET + +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 + }; + Q_ENUM(SaveMode) + + /** + * Defines where to redirect stdout from the tool. + */ + // enum class OutputMode { + // Ignore, + // InsertAtCursor, + // ReplaceSelectedText, + // AppendToCurrentDocument, + // InsertInNewDocument, + // DisplayInPane + // } + // Q_ENUM(OutputMode) + +public: + /// 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; + /// This is set by the constructor by calling checkExec(), if a + /// value is present. + bool hasexec; + /// 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; + + /// Possibly redirect the stdout output of the tool. + // OutputMode outputMode; + /// Include stderr output when running the tool. + bool includeStderr = false; + + /** + * @return true if mimetypes is empty, or the @p mimetype matches. + */ + bool valid(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(); + + /** + * 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); + +private: + QString m_exec; ///< The fully qualified path of the executable. +}; + +Q_DECLARE_METATYPE(KateExternalTool*) + +#endif // KTEXTEDITOR_KATE_EXTERNALTOOL_H + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltool.cpp b/addons/externaltools/kateexternaltool.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltool.cpp @@ -0,0 +1,69 @@ +/* + This file is part of the Kate text editor of the KDE project. + + 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. + + --- + Copyright (C) 2019 Dominik Haumann +*/ +#include "kateexternaltool.h" + +#include +#include + +bool KateExternalTool::checkExec() +{ + m_exec = QStandardPaths::findExecutable(executable); + return !m_exec.isEmpty(); +} + +bool KateExternalTool::valid(const QString& mt) const +{ + return mimetypes.isEmpty() || mimetypes.contains(mt); +} + +void KateExternalTool::load(const KConfigGroup& cg) +{ + 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 = static_cast(cg.readEntry("save", 0)); + includeStderr = cg.readEntry("includeStderr", false); + + hasexec = checkExec(); +} + +void KateExternalTool::save(KConfigGroup& cg) +{ + 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", static_cast(saveMode)); + cg.writeEntry("includeStderr", includeStderr); +} + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltoolscommand.h b/addons/externaltools/kateexternaltoolscommand.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltoolscommand.h @@ -0,0 +1,52 @@ +/* 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_EXTERNALTOOLS_COMMAND_H +#define KTEXTEDITOR_KATE_EXTERNALTOOLS_COMMAND_H + +#include + +class KateExternalToolsPlugin; +class KateExternalTool; + +/** + * Helper class that registers and executes the respective external tool. + */ +class KateExternalToolsCommand : public KTextEditor::Command +{ +public: + KateExternalToolsCommand(KateExternalToolsPlugin* plugin); + virtual ~KateExternalToolsCommand() = default; + +public: + // const QStringList& cmds() override; // FIXME + bool exec(KTextEditor::View* view, const QString& cmd, QString& msg, + const KTextEditor::Range& range = KTextEditor::Range::invalid()) override; + bool help(KTextEditor::View* view, const QString& cmd, QString& msg) override; + +private: + void runTool(KateExternalTool& tool, KTextEditor::View* view); + +private: + KateExternalToolsPlugin* m_plugin; +}; + +#endif // KTEXTEDITOR_KATE_EXTERNALTOOLS_COMMAND_H + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltoolscommand.cpp b/addons/externaltools/kateexternaltoolscommand.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltoolscommand.cpp @@ -0,0 +1,49 @@ +/* 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 "kateexternaltoolscommand.h" +#include "externaltoolsplugin.h" + +KateExternalToolsCommand::KateExternalToolsCommand(KateExternalToolsPlugin* plugin) + : KTextEditor::Command(plugin->commands()) + , m_plugin(plugin) +{ +} + +bool KateExternalToolsCommand::exec(KTextEditor::View* view, const QString& cmd, QString& msg, + const KTextEditor::Range& range) +{ + Q_UNUSED(msg) + Q_UNUSED(range) + + const QString command = cmd.trimmed(); + const auto tool = m_plugin->toolForCommand(command); + if (tool) { + m_plugin->runTool(*tool, view); + return true; + } + return false; +} + +bool KateExternalToolsCommand::help(KTextEditor::View*, const QString&, QString&) +{ + return false; +} + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltoolsconfigwidget.h b/addons/externaltools/kateexternaltoolsconfigwidget.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltoolsconfigwidget.h @@ -0,0 +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_EXTERNALTOOLS_CONFIGWIDGET_H +#define KTEXTEDITOR_EXTERNALTOOLS_CONFIGWIDGET_H + +#include "ui_configwidget.h" +#include "ui_tooldialog.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +class KActionCollection; +class KateExternalToolsPlugin; +class KateExternalTool; + +/** + * The config widget. + * The config widget allows the user to view a list of services of the type + * "Kate/ExternalTool" and add, remove or edit them. + */ +class KateExternalToolsConfigWidget : public KTextEditor::ConfigPage, public Ui::ExternalToolsConfigWidget +{ + Q_OBJECT +public: + KateExternalToolsConfigWidget(QWidget* parent, KateExternalToolsPlugin* plugin); + virtual ~KateExternalToolsConfigWidget(); + + QString name() const override; + QString fullName() const override; + QIcon icon() const override; + +public Q_SLOTS: + void apply() override; + void reset() override; + void defaults() override { reset(); } // double sigh + +private Q_SLOTS: + void slotNew(); + void slotEdit(); + void slotRemove(); + + void slotMoveUp(); + void slotMoveDown(); + + void slotSelectionChanged(); + +private: + QPixmap blankIcon(); + + QStringList m_removed; + + class KConfig* m_config = nullptr; + bool m_changed = false; + KateExternalToolsPlugin* m_plugin; +}; + +/** + * A Dialog to edit a single KateExternalTool object + */ +class KateExternalToolServiceEditor : public QDialog +{ + Q_OBJECT + +public: + explicit KateExternalToolServiceEditor(KateExternalTool* tool = nullptr, QWidget* parent = nullptr); + +private Q_SLOTS: + /** + * Run when the OK button is clicked, to ensure critical values are provided. + */ + void slotOKClicked(); + + /** + * show a mimetype chooser dialog + */ + void showMTDlg(); + +public: + Ui::ToolDialog* ui; + +private: + KateExternalTool* tool; +}; + +#endif // KTEXTEDITOR_EXTERNALTOOLS_CONFIGWIDGET_H + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltoolsconfigwidget.cpp b/addons/externaltools/kateexternaltoolsconfigwidget.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltoolsconfigwidget.cpp @@ -0,0 +1,374 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +// BEGIN ToolItem +/** + * This is a QListBoxItem, that has a KateExternalTool. The text is the Name + * of the tool. + */ +class ToolItem : public QListWidgetItem +{ +public: + ToolItem(QListWidget* lb, const QPixmap& icon, KateExternalTool* tool) + : QListWidgetItem(icon, tool->name, lb) + , tool(tool) + { + } + + ~ToolItem() {} + + KateExternalTool* tool; +}; +// END ToolItem + +// BEGIN KateExternalToolServiceEditor +KateExternalToolServiceEditor::KateExternalToolServiceEditor(KateExternalTool* tool, QWidget* parent) + : QDialog(parent) + , tool(tool) +{ + setWindowTitle(i18n("Edit External Tool")); + + 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); + + if (tool) { + ui->edtName->setText(tool->name); + if (!tool->icon.isEmpty()) + ui->btnIcon->setIcon(tool->icon); + + ui->edtExecutable->setText(tool->executable); + ui->edtArgs->setText(tool->arguments); + ui->edtInput->setText(tool->input); + ui->edtCommand->setText(tool->cmdname); + ui->edtWorkingDir->setText(tool->workingDir); + ui->edtMimeType->setText(tool->mimetypes.join(QStringLiteral("; "))); + ui->cmbSave->setCurrentIndex(static_cast(tool->saveMode)); + ui->chkIncludeStderr->setChecked(tool->includeStderr); + } +} + +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 + +// BEGIN KateExternalToolsConfigWidget +KateExternalToolsConfigWidget::KateExternalToolsConfigWidget(QWidget* parent, KateExternalToolsPlugin* plugin) + : KTextEditor::ConfigPage(parent) + , m_plugin(plugin) +{ + setupUi(this); + + btnMoveUp->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); + btnMoveDown->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); + + connect(lbTools, &QListWidget::itemSelectionChanged, this, &KateExternalToolsConfigWidget::slotSelectionChanged); + connect(lbTools, &QListWidget::itemDoubleClicked, this, &KateExternalToolsConfigWidget::slotEdit); + connect(btnNew, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotNew); + connect(btnRemove, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotRemove); + connect(btnEdit, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotEdit); + connect(btnMoveUp, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotMoveUp); + connect(btnMoveDown, &QPushButton::clicked, this, &KateExternalToolsConfigWidget::slotMoveDown); + + m_config = new KConfig(QStringLiteral("externaltools"), KConfig::NoGlobals, QStandardPaths::ApplicationsLocation); + reset(); + slotSelectionChanged(); +} + +KateExternalToolsConfigWidget::~KateExternalToolsConfigWidget() +{ + 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(); +} + +void KateExternalToolsConfigWidget::reset() +{ + // m_tools.clear(); + lbTools->clear(); + + // load the files from a KConfig + const QStringList tools = m_config->group("Global").readEntry("tools", QStringList()); + + for (int i = 0; i < tools.size(); ++i) { + const QString& toolSection = tools[i]; + KConfigGroup cg(m_config, toolSection); + KateExternalTool* t = new KateExternalTool(); + t->load(cg); + + if (t->hasexec) // we only show tools that are also in the menu. + new ToolItem(lbTools, t->icon.isEmpty() ? blankIcon() : SmallIcon(t->icon), t); + else + delete t; + } + 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; + + QStringList tools; + for (int i = 0; i < lbTools->count(); i++) { + const QString toolSection = QStringLiteral("Tool ") + QString::number(i); + tools << toolSection; + + KConfigGroup cg(m_config, toolSection); + KateExternalTool* t = static_cast(lbTools->item(i))->tool; + t->save(cg); + } + + m_config->group("Global").writeEntry("tools", tools); + + m_config->sync(); + m_plugin->reload(); +} + +void KateExternalToolsConfigWidget::slotSelectionChanged() +{ + // update button state + bool hs = lbTools->currentItem() != nullptr; + btnEdit->setEnabled(hs && dynamic_cast(lbTools->currentItem())); + btnRemove->setEnabled(hs); + btnMoveUp->setEnabled((lbTools->currentRow() > 0) && hs); + btnMoveDown->setEnabled((lbTools->currentRow() < (int)lbTools->count() - 1) && hs); +} + +void KateExternalToolsConfigWidget::slotNew() +{ + // display a editor, and if it is OK'd, create a new tool and + // create a listbox item for it + KateExternalToolServiceEditor editor(nullptr, this); + + if (editor.exec() == QDialog::Accepted) { + KateExternalTool* t = new KateExternalTool(); + t->name = editor.ui->edtName->text(); + t->icon = editor.ui->btnIcon->icon(); + t->executable = editor.ui->edtExecutable->text(); + t->arguments = editor.ui->edtArgs->text(); + t->input = editor.ui->edtInput->toPlainText(); + t->workingDir = editor.ui->edtWorkingDir->text(); + t->mimetypes = editor.ui->edtMimeType->text().split(QRegularExpression(QStringLiteral("\\s*;\\s*")), + QString::SkipEmptyParts); + t->saveMode = static_cast(editor.ui->cmbSave->currentIndex()); + t->includeStderr = editor.ui->chkIncludeStderr->isChecked(); + + // This is sticky, it does not change again, so that shortcuts sticks + // TODO check for dups + t->actionName = QStringLiteral("externaltool_") + QString(t->name).remove(QRegExp(QStringLiteral("\\W+"))); + + new ToolItem(lbTools, t->icon.isEmpty() ? blankIcon() : SmallIcon(t->icon), t); + + emit changed(); + m_changed = true; + } +} + +void KateExternalToolsConfigWidget::slotRemove() +{ + // add the tool action name to a list of removed items, + // remove the current listbox item + if (lbTools->currentRow() > -1) { + ToolItem* i = dynamic_cast(lbTools->currentItem()); + if (i) + m_removed << i->tool->actionName; + + delete lbTools->takeItem(lbTools->currentRow()); + emit changed(); + m_changed = true; + } +} + +void KateExternalToolsConfigWidget::slotEdit() +{ + if (!dynamic_cast(lbTools->currentItem())) + return; + // show the item in an editor + KateExternalTool* t = static_cast(lbTools->currentItem())->tool; + KateExternalToolServiceEditor editor(t, this); + editor.resize(m_config->group("Editor").readEntry("Size", QSize())); + if (editor.exec() /*== KDialog::Ok*/) { + + bool elementChanged = ((editor.ui->btnIcon->icon() != t->icon) || (editor.ui->edtName->text() != t->name)); + + t->name = editor.ui->edtName->text(); + t->icon = editor.ui->btnIcon->icon(); + t->executable = editor.ui->edtExecutable->text(); + t->arguments = editor.ui->edtArgs->text(); + t->input = editor.ui->edtInput->toPlainText(); + t->cmdname = editor.ui->edtCommand->text(); + t->workingDir = editor.ui->edtWorkingDir->text(); + t->mimetypes + = editor.ui->edtMimeType->text().split(QRegExp(QStringLiteral("\\s*;\\s*")), QString::SkipEmptyParts); + t->saveMode = static_cast(editor.ui->cmbSave->currentIndex()); + t->includeStderr = editor.ui->chkIncludeStderr->isChecked(); + + // if the icon has changed or name changed, I have to renew the listbox item :S + if (elementChanged) { + int idx = lbTools->row(lbTools->currentItem()); + delete lbTools->takeItem(idx); + lbTools->insertItem(idx, new ToolItem(nullptr, t->icon.isEmpty() ? blankIcon() : SmallIcon(t->icon), t)); + } + + emit changed(); + m_changed = true; + } + + m_config->group("Editor").writeEntry("Size", editor.size()); + m_config->sync(); +} + +void KateExternalToolsConfigWidget::slotMoveUp() +{ + // move the current item in the listbox upwards if possible + QListWidgetItem* item = lbTools->currentItem(); + if (!item) + return; + + int idx = lbTools->row(item); + + if (idx < 1) + return; + + if (dynamic_cast(item)) { + KateExternalTool* tool = static_cast(item)->tool; + delete lbTools->takeItem(idx); + lbTools->insertItem(idx - 1, + new ToolItem(nullptr, tool->icon.isEmpty() ? blankIcon() : SmallIcon(tool->icon), tool)); + } + + lbTools->setCurrentRow(idx - 1); + slotSelectionChanged(); + emit changed(); + m_changed = true; +} + +void KateExternalToolsConfigWidget::slotMoveDown() +{ + // move the current item in the listbox downwards if possible + QListWidgetItem* item = lbTools->currentItem(); + if (!item) + return; + + int idx = lbTools->row(item); + + if (idx > lbTools->count() - 1) + return; + + if (dynamic_cast(item)) { + KateExternalTool* tool = static_cast(item)->tool; + delete lbTools->takeItem(idx); + lbTools->insertItem(idx + 1, + new ToolItem(nullptr, tool->icon.isEmpty() ? blankIcon() : SmallIcon(tool->icon), tool)); + } + + lbTools->setCurrentRow(idx + 1); + slotSelectionChanged(); + emit changed(); + m_changed = true; +} +// END KateExternalToolsConfigWidget + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/katemacroexpander.h b/addons/externaltools/katemacroexpander.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/katemacroexpander.h @@ -0,0 +1,47 @@ +/* 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_MACRO_EXPANDER_H +#define KTEXTEDITOR_MACRO_EXPANDER_H + +#include + +namespace KTextEditor +{ +class View; +} + +/** + * Helper class for macro expansion. + */ +class MacroExpander : public KWordMacroExpander +{ +public: + MacroExpander(KTextEditor::View* view); + +protected: + bool expandMacro(const QString& str, QStringList& ret) override; + +private: + KTextEditor::View* m_view; +}; + +#endif // KTEXTEDITOR_MACRO_EXPANDER_H + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/katemacroexpander.cpp b/addons/externaltools/katemacroexpander.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/katemacroexpander.cpp @@ -0,0 +1,66 @@ +/* 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 "katemacroexpander.h" + +#include +#include +#include +#include + +MacroExpander::MacroExpander(KTextEditor::View* view) + : KWordMacroExpander() + , m_view(view) +{ +} + +bool MacroExpander::expandMacro(const QString& str, QStringList& ret) +{ + KTextEditor::View* view = m_view; + if (!view) + return false; + + KTextEditor::Document* doc = view->document(); + QUrl url = doc->url(); + + if (str == QStringLiteral("URL")) + ret += url.url(); + else if (str == QStringLiteral("directory")) // directory of current doc + ret += url.toString(QUrl::RemoveScheme | QUrl::RemoveFilename); + else if (str == QStringLiteral("filename")) + ret += url.fileName(); + else if (str == QStringLiteral("line")) // cursor line of current doc + ret += QString::number(view->cursorPosition().line()); + else if (str == QStringLiteral("col")) // cursor col of current doc + ret += QString::number(view->cursorPosition().column()); + else if (str == QStringLiteral("selection")) // selection of current doc if any + ret += view->selectionText(); + else if (str == QStringLiteral("text")) // text of current doc + ret += doc->text(); + else if (str == QStringLiteral("URLs")) { + foreach (KTextEditor::Document* it, KTextEditor::Editor::instance()->application()->documents()) + if (!it->url().isEmpty()) + ret += it->url().url(); + } else + return false; + + return true; +} + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/katetoolrunner.h b/addons/externaltools/katetoolrunner.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/katetoolrunner.h @@ -0,0 +1,74 @@ +/* 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 + +class KateExternalTool; +class QProcess; + +/** + * Helper class to run a KateExternalTool. + */ +class KateToolRunner : public QObject +{ + Q_OBJECT + +public: + KateToolRunner(KateExternalTool* tool, QObject* parent = nullptr); + KateToolRunner(const KateToolRunner&) = delete; + void operator=(const KateToolRunner&) = delete; + + ~KateToolRunner(); + + void run(); + void waitForFinished(); + QString outputData() const; + +Q_SIGNALS: + void toolFinished(KateToolRunner* runner); + +private Q_SLOTS: + /** + * More tool output is available + */ + void slotReadyRead(); + + /** + * Analysis finished + * @param exitCode analyzer process exit code + * @param exitStatus analyzer process exit status + */ + void handleToolFinished(int exitCode, QProcess::ExitStatus exitStatus); + +private: + KateExternalTool* m_tool; + QProcess* m_process = nullptr; + QByteArray m_output; +}; + +#endif // KTEXTEDITOR_EXTERNALTOOLRUNNER_H + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/katetoolrunner.cpp b/addons/externaltools/katetoolrunner.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/katetoolrunner.cpp @@ -0,0 +1,101 @@ +/* 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 + +KateToolRunner::KateToolRunner(KateExternalTool* tool, QObject* parent) + : QObject(parent) + , m_tool(tool) + , m_process(new QProcess()) +{ +} + +KateToolRunner::~KateToolRunner() +{ + delete m_tool; + m_tool = nullptr; + + delete m_process; + m_process = nullptr; +} + +void KateToolRunner::run() +{ + if (m_tool->includeStderr) { + m_process->setProcessChannelMode(QProcess::MergedChannels); + } + + if (!m_tool->workingDir.isEmpty()) { + m_process->setWorkingDirectory(m_tool->workingDir); + } + + QObject::connect(m_process, &QProcess::readyRead, this, &KateToolRunner::slotReadyRead); + QObject::connect(m_process, static_cast(&QProcess::finished), this, + &KateToolRunner::handleToolFinished); + + // Write stdin to process, if applicable, then close write channel + QObject::connect(m_process, &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(); +} + +void KateToolRunner::handleToolFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitCode != 0) { + // FIXME: somehow tell user + return; + } + + if (exitStatus != QProcess::NormalExit) { + // FIXME: somehow tell user + return; + } + + // FIXME: process m_output depending on the tool's outputMode + + Q_EMIT toolFinished(this); +} + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/plugin.qrc b/addons/externaltools/plugin.qrc new file mode 100644 --- /dev/null +++ b/addons/externaltools/plugin.qrc @@ -0,0 +1,6 @@ + + + + ui.rc + + diff --git a/addons/externaltools/tooldialog.ui b/addons/externaltools/tooldialog.ui new file mode 100644 --- /dev/null +++ b/addons/externaltools/tooldialog.ui @@ -0,0 +1,379 @@ + + + ToolDialog + + + + 0 + 0 + 470 + 510 + + + + Edit Tool + + + + + + Name: + + + + + + + + + The name will be displayed in the 'Tools->External Tools' menu. + + + Short name of the tool + + + + + + + + + + + + Executable: + + + + + + + + + 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 + + + + + + + + + Arguments: + + + + + + + Input: + + + + + + + + + <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 + + + + + + + + + Mime types: + + + + + + + + + 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: + + + + + + + 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 + + + + + + + Output: + + + + + + + + Ignore + + + + + Insert at Cursor Position + + + + + Replace Selected Text + + + + + Replace Current Document + + + + + Append to Current Document + + + + + Insert in New Document + + + + + Display in Pane + + + + + Embedded Console + + + + + + + + Editor command: + + + + + + + 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 + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Working directory: + + + + + + + + + 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 + + + + + buttonBox + accepted() + ToolDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ToolDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/addons/externaltools/ui.rc b/addons/externaltools/ui.rc new file mode 100644 --- /dev/null +++ b/addons/externaltools/ui.rc @@ -0,0 +1,11 @@ + + + + &File + + + &Tools + + + +