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,29 @@ +project(kateexternaltoolsplugin) +add_definitions(-DTRANSLATION_DOMAIN=\"externaltoolsplugin\") + +include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) + +set(kateexternaltoolsplugin_PART_SRCS + kateexternaltoolsplugin.cpp + kateexternaltools.cpp +) + +# resource for ui file and stuff +qt5_add_resources(kateexternaltoolsplugin_PART_SRCS plugin.qrc) + +set(kateexternaltoolsplugin_PART_UI + configwidget.ui +) +ki18n_wrap_ui(kateexternaltoolsplugin_PART_SRCS ${katebacktracebrowserplugin_PART_UI} ) +add_library(kateexternaltoolsplugin MODULE ${kateexternaltoolsplugin_PART_SRCS}) + +# we compile in the .desktop file +kcoreaddons_desktop_to_json (kateexternaltoolsplugin kateexternaltoolsplugin.desktop) + +target_link_libraries(kateexternaltoolsplugin + KF5::TextEditor + KF5::I18n +) + +########### install files ############### +install(TARGETS kateexternaltoolsplugin DESTINATION ${PLUGIN_INSTALL_DIR}/ktexteditor ) 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/kateexternaltoolsplugin.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,11 @@ +include(ECMMarkAsTest) + +# Project Plugin +add_executable(externaltools_test + externaltooltest.cpp + ../kateexternaltool.cpp + ../katetoolrunner.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,116 @@ +/* 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.category = QStringLiteral("Git Tools"); + 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, clonedTool); +} + +void ExternalToolTest::testRunListDirectory() +{ + std::unique_ptr tool(new KateExternalTool()); + tool->category = QStringLiteral("Tools"); + 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; + std::unique_ptr tool2(new KateExternalTool(*tool)); + + // 1. /tmp $ ls /usr + KateToolRunner runner1(std::move(tool), nullptr); + runner1.run(); + runner1.waitForFinished(); + QVERIFY(runner1.outputData().contains(QStringLiteral("bin"))); + + // 2. /usr $ ls + tool2->arguments.clear(); + tool2->workingDir = QStringLiteral("/usr"); + KateToolRunner runner2(std::move(tool2), nullptr); + 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() +{ + std::unique_ptr 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(std::move(tool), nullptr); + 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,115 @@ + + ExternalToolsConfigWidget + + + + 0 + 0 + 504 + 296 + + + + + 0 + + + + + + + This list shows all the configured tools, represented by their menu text. + + + + + + + Qt::Vertical + + + + 20 + 98 + + + + + + + + Qt::Vertical + + + + 17 + 108 + + + + + + + + + + New... + + + + + + + Edit... + + + + + + + Remove + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Insert Separator + + + + + + + + + + + + + + + + + + KListWidget + QListWidget +
klistwidget.h
+
+
+ + +
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=externaltool_RunScript,---,externaltool_GoogleSelection +version=1 + +[externaltool_GoogleSelection] +acname=externaltool_GoogleSelection +cmdname=google-selection +command=[ -n "%selection" ] && kfmclient openURL "gg:%selection" +executable=kfmclient +icon=globe +mimetypes= +name=Google Selection +save=0 + +[externaltool_RunScript] +acname=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/externaltoolsplugin.h b/addons/externaltools/externaltoolsplugin.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/externaltoolsplugin.h @@ -0,0 +1,123 @@ +/* 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 + +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(); + +public: + /** + * Called by the KateExternalToolsPluginView to register itself. + */ + void registerPluginView(KateExternalToolsPluginView * view); + + /** + * Called by the KateExternalToolsPluginView to unregister itself. + */ + void unregisterPluginView(KateExternalToolsPluginView * view); + + /** + * Returns the KateExternalToolsPluginView for the given mainWindow. + */ + KateExternalToolsPluginView* viewForMainWindow(KTextEditor::MainWindow* mainWindow) const; + +private: + QVector m_views; + QVector m_tools; + QStringList m_commands; + KateExternalToolsCommand* m_command = nullptr; + +private Q_SLOT: + /** + * Called whenever an external tool is done. + */ + void handleToolFinished(KateToolRunner* runner, int exitCode, bool crashed); +}; + +#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,396 @@ +/* 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 "katetoolrunner.h" +#include "kateexternaltoolsconfigwidget.h" + +#include +#include +#include +#include +#include +#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(); + + auto editor = KTextEditor::Editor::instance(); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:FileBaseName"), i18n("Current document: File base name without path and suffix."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return QFileInfo(url).baseName(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:FileExtension"), i18n("Current document: File extension."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return QFileInfo(url).completeSuffix(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:FileName"), i18n("Current document: File name without path."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return QFileInfo(url).fileName(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:FilePath"), i18n("Current document: Full path including file name."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return QFileInfo(url).absoluteFilePath(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:Text"), i18n("Current document: Contents of entire file."), [](const QStringView&, KTextEditor::View* view) { + return view ? view->document()->text() : QString(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:Path"), i18n("Current document: Full path excluding file name."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return QFileInfo(url).absolutePath(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:NativeFilePath"), i18n("Current document: Full path including file name, with native path separator (backslash on Windows)."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return url.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(url).absoluteFilePath()); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:NativePath"), i18n("Current document: Full path excluding file name, with native path separator (backslash on Windows)."), [](const QStringView&, KTextEditor::View* view) { + const auto url = view ? view->document()->url().toLocalFile() : QString(); + return url.isEmpty() ? QString() : QDir::toNativeSeparators(QFileInfo(url).absolutePath()); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:Cursor:Line"), i18n("Line number of the text cursor position in current document (starts with 0)."), [](const QStringView&, KTextEditor::View* view) { + return view ? QString::number(view->cursorPosition().line()) : QString(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:Cursor:Column"), i18n("Column number of the text cursor position in current document (starts with 0)."), [](const QStringView&, KTextEditor::View* view) { + return view ? QString::number(view->cursorPosition().column()) : QString(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:Cursor:XPos"), i18n("X component in global screen coordinates of the cursor position."), [](const QStringView&, KTextEditor::View* view) { + return view ? QString::number(view->mapToGlobal(view->cursorPositionCoordinates()).x()) : QString(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:Cursor:YPos"), i18n("Y component in global screen coordinates of the cursor position."), [](const QStringView&, KTextEditor::View* view) { + return view ? QString::number(view->mapToGlobal(view->cursorPositionCoordinates()).y()) : QString(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:Selection:Text"), i18n("Selection of current document."), [](const QStringView&, KTextEditor::View* view) { + return (view && view->selection()) ? view->selectionText() : QString(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:Selection:StartLine"), i18n("Start line of selected text of current document."), [](const QStringView&, KTextEditor::View* view) { + return (view && view->selection()) ? QString::number(view->selectionRange().start().line()) : QString(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:Selection:StartColumn"), i18n("Start column of selected text of current document."), [](const QStringView&, KTextEditor::View* view) { + return (view && view->selection()) ? QString::number(view->selectionRange().start().column()) : QString(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:Selection:EndLine"), i18n("End line of selected text of current document."), [](const QStringView&, KTextEditor::View* view) { + return (view && view->selection()) ? QString::number(view->selectionRange().end().line()) : QString(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:Selection:EndColumn"), i18n("End column of selected text of current document."), [](const QStringView&, KTextEditor::View* view) { + return (view && view->selection()) ? QString::number(view->selectionRange().end().column()) : QString(); + }); + editor->registerVariableMatch(QStringLiteral("CurrentDocument:RowCount"), i18n("Number of rows of current document."), [](const QStringView&, KTextEditor::View* view) { + return view ? QString::number(view->document()->lines()) : QString(); + }); + + editor->registerVariableMatch(QStringLiteral("Date:Locale"), i18n("The current date in current locale format."), [](const QStringView&, KTextEditor::View*) { + return QDate::currentDate().toString(Qt::DefaultLocaleShortDate); + }); + editor->registerVariableMatch(QStringLiteral("Date:ISO"), i18n("The current date (ISO)."), [](const QStringView&, KTextEditor::View*) { + return QDate::currentDate().toString(Qt::ISODate); + }); + editor->registerVariablePrefix(QStringLiteral("Date:"), i18n("The current date (QDate formatstring)."), [](const QStringView& str, KTextEditor::View*) { + return QDate::currentDate().toString(str.right(str.length() - 5)); + }); + + editor->registerVariableMatch(QStringLiteral("Time:Locale"), i18n("The current time in current locale format."), [](const QStringView&, KTextEditor::View*) { + return QTime::currentTime().toString(Qt::DefaultLocaleShortDate); + }); + editor->registerVariableMatch(QStringLiteral("Time:ISO"), i18n("The current time (ISO)."), [](const QStringView&, KTextEditor::View*) { + return QTime::currentTime().toString(Qt::ISODate); + }); + editor->registerVariablePrefix(QStringLiteral("Time:"), i18n("The current time (QTime formatstring)."), [](const QStringView& str, KTextEditor::View*) { + return QTime::currentTime().toString(str.right(str.length() - 5)); + }); + + editor->registerVariablePrefix(QStringLiteral("ENV:"), i18n("Access environment variables."), [](const QStringView& str, KTextEditor::View*) { + return QString::fromLocal8Bit(qgetenv(str.right(str.size() - 4).toLocal8Bit().constData())); + }); + editor->registerVariablePrefix(QStringLiteral("JS:"), i18n("Evaluate simple JavaScript statements. The statements may not contain '{' nor '}' characters."), [](const QStringView& str, KTextEditor::View*) { + // FIXME + Q_UNUSED(str) + return QString(); + }); + + editor->registerVariableMatch(QStringLiteral("UUID"), i18n("Generate a new UUID."), [](const QStringView&, KTextEditor::View*) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) + return QUuid::createUuid().toString(QUuid::WithoutBraces); +#else + // LEGACY + QString uuid = QUuid::createUuid().toString(); + if (uuid.startsWith(QLatin1Char('{'))) + uuid.remove(0, 1); + if (uuid.endsWith(QLatin1Char('}'))) + uuid.chop(1); + return uuid; +#endif + }); +} + +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; + } + } + } + + // copy tool + std::unique_ptr copy(new KateExternalTool(tool)); + + // clear previous toolview data + auto pluginView = viewForMainWindow(mw); + pluginView->clearToolView(); + pluginView->addToolStatus(i18n("Running external tool: %1", copy->name)); + pluginView->addToolStatus(i18n("- Executable: %1", copy->executable)); + pluginView->addToolStatus(i18n("- Arguments : %1", copy->arguments)); + pluginView->addToolStatus(i18n("- Input : %1", copy->input)); + pluginView->addToolStatus(QString()); + + // expand macros + auto editor = KTextEditor::Editor::instance(); + editor->expandText(copy->executable, view, copy->executable); + editor->expandText(copy->arguments, view, copy->arguments); + editor->expandText(copy->workingDir, view, copy->workingDir); + editor->expandText(copy->input, view, copy->input); + + // 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) +{ + auto view = runner->view(); + 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); + } + + KateExternalToolsPluginView* pluginView = runner->view() ? viewForMainWindow(runner->view()->mainWindow()) : nullptr; + if (pluginView) { + bool hasOutputInPane = false; + if (runner->tool()->outputMode == KateExternalTool::OutputMode::DisplayInPane) { + pluginView->setOutputData(runner->outputData()); + hasOutputInPane = !runner->outputData().isEmpty(); + } + + if (!runner->errorData().isEmpty()) { + pluginView->addToolStatus(i18n("Data written to stderr:")); + pluginView->addToolStatus(runner->errorData()); + } + + // empty line + pluginView->addToolStatus(QString()); + + // print crash & exit code + if (crashed) { + pluginView->addToolStatus(i18n("Warning: External tool crashed.")); + } + pluginView->addToolStatus(i18n("Finished with exit code: %1", exitCode)); + + if (crashed || exitCode != 0) { + pluginView->showToolView(ToolViewFocus::StatusTab); + } else if (hasOutputInPane) { + pluginView->showToolView(ToolViewFocus::OutputTab); + } + } + + 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/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,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; + +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/kateexternaltool.cpp b/addons/externaltools/kateexternaltool.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltool.cpp @@ -0,0 +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")); + + 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)); +} + +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; +} + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltools.h b/addons/externaltools/kateexternaltools.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltools.h @@ -0,0 +1,253 @@ +/* + 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 +*/ + +#ifndef _KATE_EXTERNAL_TOOLS_H_ +#define _KATE_EXTERNAL_TOOLS_H_ + +#include "ui_configwidget.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +class KateExternalToolsPlugin; + +/** + * 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 +{ + friend class KateExternalToolAction; + + Q_OBJECT + public: + KateExternalToolsMenuAction( const QString &text, class KActionCollection *collection, QObject *parent, class Kate::MainWindow *mw = 0 ); + virtual ~KateExternalToolsMenuAction(); + + /** + * This will load all the confiured services. + */ + void reload(); + + class KActionCollection *actionCollection() + { + return m_actionCollection; + } + + private Q_SLOTS: + void slotDocumentChanged(); + + private: + class KActionCollection *m_actionCollection; + Kate::MainWindow *mainwindow; // for the actions to access view/doc managers +}; + +/** + * This Action contains a KateExternalTool + */ +class KateExternalToolAction : public KAction, public KWordMacroExpander +{ + Q_OBJECT + public: + KateExternalToolAction( QObject *parent, class KateExternalTool *t ); + ~KateExternalToolAction(); + protected: + virtual bool expandMacro( const QString &str, QStringList &ret ); + + private Q_SLOTS: + void slotRun(); + + public: + class KateExternalTool *tool; +}; + +/** + * This class defines a single external tool. + */ +class KateExternalTool +{ + public: + explicit KateExternalTool( const QString &name = QString(), + const QString &command = QString(), + const QString &icon = QString(), + const QString &tryexec = QString(), + const QStringList &mimetypes = QStringList(), + const QString &acname = QString(), + const QString &cmdname = QString(), + int save = 0 ); + ~KateExternalTool() + {} + + QString name; ///< The name used in the menu. + QString command; ///< The command to execute. + QString icon; ///< the icon to use in the menu. + QString tryexec; ///< The name or path of the executable. + QStringList mimetypes; ///< Optional list of mimetypes for which this action is valid. + bool hasexec; ///< This is set by the constructor by calling checkExec(), if a value is present. + QString acname; ///< The name for the action. This is generated first time the action is is created. + QString cmdname; ///< The name for the commandline. + int save; ///< We can save documents prior to activating the tool command: 0 = nothing, 1 = current document, 2 = all documents. + + /** + * @return true if mimetypes is empty, or the @p mimetype matches. + */ + bool valid( const QString &mimetype ) const; + /** + * @return true if "tryexec" 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(); + + private: + QString m_exec; ///< The fully qualified path of the executable. +}; + +/** + * 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 Kate::PluginConfigPage + , public Ui::ExternalToolsConfigWidget +{ + Q_OBJECT + public: + KateExternalToolsConfigWidget( QWidget *parent, KateExternalToolsPlugin *plugin, const char* name); + virtual ~KateExternalToolsConfigWidget(); + + virtual void apply(); + virtual void reset(); + virtual void defaults() + { + reset(); + } // double sigh + + private Q_SLOTS: + void slotNew(); + void slotEdit(); + void slotRemove(); + void slotInsertSeparator(); + + void slotMoveUp(); + void slotMoveDown(); + + void slotSelectionChanged(); + + private: + QPixmap blankIcon(); + + QStringList m_removed; + + class KConfig *config; + + bool m_changed; + KateExternalToolsPlugin *m_plugin; +}; + +/** + * A Singleton class for invoking external tools with the view command line + */ +class KateExternalToolsCommand : public KTextEditor::Command +{ + public: + KateExternalToolsCommand (KateExternalToolsPlugin *plugin); + virtual ~KateExternalToolsCommand () + {} + void reload(); + public: + virtual const QStringList &cmds (); + virtual bool exec (KTextEditor::View *view, const QString &cmd, QString &msg); + virtual bool help (KTextEditor::View *view, const QString &cmd, QString &msg); + private: + QStringList m_list; + QHash m_map; + QHash m_name; + bool m_inited; + KateExternalToolsPlugin *m_plugin; +}; + +/** + * A Dialog to edit a single KateExternalTool object + */ +class KateExternalToolServiceEditor : public KDialog +{ + Q_OBJECT + + public: + + explicit KateExternalToolServiceEditor( KateExternalTool *tool = 0, + QWidget *parent = 0, const char *name = 0 ); + + class KLineEdit *leName, *leExecutable, *leMimetypes, *leCmdLine; + class QTextEdit *teCommand; + class KIconButton *btnIcon; + class KComboBox *cmbSave; + + private Q_SLOTS: + /** + * Run when the OK/Cancel button is clicked, to ensure critical values are provided + */ + void slotButtonClicked(int button); + /** + * show a mimetype chooser dialog + */ + void showMTDlg(); + + private: + KateExternalTool *tool; +}; +#endif //_KATE_EXTERNAL_TOOLS_H_ +// kate: space-indent on; indent-width 2; replace-tabs on; + diff --git a/addons/externaltools/kateexternaltools.cpp b/addons/externaltools/kateexternaltools.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltools.cpp @@ -0,0 +1,890 @@ +/* + 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) 2004, Anders Lund +*/ +// TODO +// Icons +// Direct shortcut setting +//BEGIN Includes +#include "kateexternaltoolsplugin.h" +#include "kateexternaltools.h" +#include "kateexternaltools.moc" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +//END Includes + + +//BEGIN KateExternalTool +KateExternalTool::KateExternalTool( const QString &name, + const QString &command, + const QString &icon, + const QString &tryexec, + const QStringList &mimetypes, + const QString &acname, + const QString &cmdname, + int save ) + : name ( name ), + command ( command ), + icon ( icon ), + tryexec ( tryexec ), + mimetypes ( mimetypes ), + acname ( acname ), + cmdname ( cmdname ), + save ( save ) +{ + //if ( ! tryexec.isEmpty() ) + hasexec = checkExec(); +} + +bool KateExternalTool::checkExec() +{ + // if tryexec is empty, it is the first word of command + if ( tryexec.isEmpty() ) + tryexec = command.section( ' ', 0, 0, QString::SectionSkipEmpty ); + + // NOTE this code is modified taken from kdesktopfile.cpp, from KDesktopFile::tryExec() + if (!tryexec.isEmpty()) + { + m_exec = KStandardDirs::findExe(tryexec); + return !m_exec.isEmpty(); + } + return false; +} + +bool KateExternalTool::valid( const QString &mt ) const +{ + return mimetypes.isEmpty() || mimetypes.contains( mt ); +} +//END KateExternalTool + +//BEGIN KateExternalToolsCommand +KateExternalToolsCommand::KateExternalToolsCommand(KateExternalToolsPlugin *plugin) : KTextEditor::Command(),m_plugin(plugin) +{ + m_inited = false; + reload(); +} + +const QStringList &KateExternalToolsCommand::cmds () +{ + return m_list; +} + + +void KateExternalToolsCommand::reload () +{ + m_list.clear(); + m_map.clear(); + m_name.clear(); + + KConfig _config( "externaltools", KConfig::NoGlobals, "appdata" ); + KConfigGroup config(&_config, "Global"); + const QStringList tools = config.readEntry("tools", QStringList()); + + + for( QStringList::const_iterator it = tools.begin(); it != tools.end(); ++it ) + { + if ( *it == "---" ) + continue; + + + config= KConfigGroup(&_config, *it ); + + KateExternalTool t = KateExternalTool( + config.readEntry( "name", "" ), + config.readEntry( "command", ""), + config.readEntry( "icon", ""), + config.readEntry( "executable", ""), + config.readEntry( "mimetypes", QStringList() ), + config.readEntry( "acname", "" ), + config.readEntry( "cmdname", "" ) ); + // FIXME test for a command name first! + if ( t.hasexec && (!t.cmdname.isEmpty())) + { + m_list.append("exttool-" + t.cmdname); + m_map.insert("exttool-" + t.cmdname, t.acname); + m_name.insert("exttool-" + t.cmdname, t.name); + } + } + if (m_inited) + { + KTextEditor::CommandInterface* cmdIface = + qobject_cast( Kate::application()->editor() ); + if( cmdIface ) + { + // reregister commands, in case of something has changed + cmdIface->unregisterCommand( this ); + cmdIface->registerCommand( this ); + } + } + else m_inited = true; +} + +bool KateExternalToolsCommand::exec (KTextEditor::View *view, const QString &cmd, QString &) +{ + QWidget *wv = dynamic_cast(view); + if (!wv) + { +// kDebug(13001)<<"KateExternalToolsCommand::exec: Could not get view widget"; + return false; + } + + +// kDebug(13001)<<"cmd="<(dmw->action("tools_external")); + if (!a) return false;*/ + KateExternalToolsPluginView *extview=m_plugin->extView(wv->window()); + if (!extview) return false; + if (!extview->externalTools) return false; +// kDebug(13001)<<"trying to find action"; + QAction *a1 = extview->externalTools->actionCollection()->action(actionName.toUtf8().constData ()); + if (!a1) return false; +// kDebug(13001)<<"activating action"; + a1->trigger(); + return true; +} + +bool KateExternalToolsCommand::help (KTextEditor::View *, const QString &, QString &) +{ + return false; +} +//END KateExternalToolsCommand + +//BEGIN KateExternalToolAction +KateExternalToolAction::KateExternalToolAction( QObject *parent, KateExternalTool *t) + : KAction( KIcon(t->icon), t->name, parent ), + tool ( t ) +{ + //setText( t->name ); + //if ( ! t->icon.isEmpty() ) + // setIcon( KIcon( t->icon ) ); + + connect( this, SIGNAL(triggered(bool)), SLOT(slotRun()) ); +} + +bool KateExternalToolAction::expandMacro( const QString &str, QStringList &ret ) +{ + Kate::MainWindow *mw = qobject_cast(parent()->parent()); + Q_ASSERT(mw); + + KTextEditor::View *view = mw->activeView(); + if ( ! view ) return false; + + KTextEditor::Document *doc = view->document(); + KUrl url = doc->url(); + + if ( str == "URL" ) + ret += url.url(); + else if ( str == "directory" ) // directory of current doc + ret += url.directory(); + else if ( str == "filename" ) + ret += url.fileName(); + else if ( str == "line" ) // cursor line of current doc + ret += QString::number( view->cursorPosition().line() ); + else if ( str == "col" ) // cursor col of current doc + ret += QString::number( view->cursorPosition().column() ); + else if ( str == "selection" ) // selection of current doc if any + ret += view->selectionText(); + else if ( str == "text" ) // text of current doc + ret += doc->text(); + else if ( str == "URLs" ) + { + foreach( KTextEditor::Document *it, Kate::documentManager()->documents()) + if ( ! it->url().isEmpty() ) + ret += it->url().url(); + } + else + return false; + + return true; +} + +KateExternalToolAction::~KateExternalToolAction() +{ + delete(tool); +} + +void KateExternalToolAction::slotRun() +{ + // expand the macros in command if any, + // and construct a command with an absolute path + QString cmd = tool->command; + + Kate::MainWindow *mw = qobject_cast(parent()->parent()); + if ( ! expandMacrosShellQuote( cmd ) ) + { + KMessageBox::sorry( mw->window(), + i18n("Failed to expand the command '%1'.", cmd ), + i18n( "Kate External Tools") ); + return; + } + kDebug(13001) << "externaltools: Running command: " << cmd; + + // save documents if requested + if ( tool->save == 1 ) + mw->activeView()->document()->save(); + else if ( tool->save == 2 ) + { + foreach (KXMLGUIClient *client, mw->guiFactory()->clients()) + { + if (QAction *a = client->actionCollection()->action("file_save_all")) + { + a->trigger(); + break; + } + } + } + + KRun::runCommand( cmd, tool->tryexec, tool->icon, mw->window() ); +} +//END KateExternalToolAction + +//BEGIN KateExternalToolsMenuAction +KateExternalToolsMenuAction::KateExternalToolsMenuAction( const QString &text, + KActionCollection *collection, + QObject *parent, + Kate::MainWindow *mw ) + : KActionMenu( text, parent ), + mainwindow( mw ) +{ + + m_actionCollection = collection; + + // connect to view changed... + connect(mw, SIGNAL(viewChanged()), this, SLOT(slotDocumentChanged())); + + reload(); +} + +KateExternalToolsMenuAction::~KateExternalToolsMenuAction() +{ + //kDebug() << "deleted KateExternalToolsMenuAction"; +} + +void KateExternalToolsMenuAction::reload() +{ + bool needs_readd=(m_actionCollection->takeAction(this)!=0); + m_actionCollection->clear (); + if (needs_readd) m_actionCollection->addAction("tools_external",this); + menu()->clear(); + + // load all the tools, and create a action for each of them + KSharedConfig::Ptr pConfig = KSharedConfig::openConfig( "externaltools", KConfig::NoGlobals, "appdata" ); + KConfigGroup config(pConfig, "Global" ); + QStringList tools = config.readEntry( "tools", QStringList() ); + + // if there are tools that are present but not explicitly removed, + // add them to the end of the list + pConfig->setReadDefaults( true ); + QStringList dtools = config.readEntry( "tools", QStringList() ); + int gver = config.readEntry( "version", 1 ); + pConfig->setReadDefaults( false ); + + int ver = config.readEntry( "version", 0 ); + if ( ver <= gver ) + { + QStringList removed = config.readEntry( "removed", QStringList() ); + bool sepadded = false; + for (QStringList::iterator itg = dtools.begin(); itg != dtools.end(); ++itg ) + { + if ( ! tools.contains( *itg ) && + ! removed.contains( *itg ) ) + { + if ( ! sepadded ) + { + tools << "---"; + sepadded = true; + } + tools << *itg; + } + } + + config.writeEntry( "tools", tools ); + config.sync(); + config.writeEntry( "version", gver ); + } + + for( QStringList::const_iterator it = tools.constBegin(); it != tools.constEnd(); ++it ) + { + if ( *it == "---" ) + { + menu()->addSeparator(); + // a separator + continue; + } + + config=KConfigGroup(pConfig, *it ); + + KateExternalTool *t = new KateExternalTool( + config.readEntry( "name", "" ), + config.readEntry( "command", ""), + config.readEntry( "icon", ""), + config.readEntry( "executable", ""), + config.readEntry( "mimetypes", QStringList() ), + config.readEntry( "acname", "" ), + config.readEntry( "cmdname", "" ), + config.readEntry( "save", 0 ) ); + + if ( t->hasexec ) + { + KAction *a = new KateExternalToolAction( this, t ); + m_actionCollection->addAction( t->acname.toAscii(), a ); + addAction( a ); + } + else + delete t; + } + + config=KConfigGroup(pConfig, "Shortcuts"); + m_actionCollection->readSettings( &config ); + slotDocumentChanged(); +} + +void KateExternalToolsMenuAction::slotDocumentChanged() +{ + // try to enable/disable to match current mime type + KTextEditor::View *v = mainwindow->activeView(); + + // no active view, oh oh + if (!v) + return; + + KTextEditor::Document *de = v->document(); + if ( de ) + { + QString mt = de->mimeType(); + QStringList l; + bool b; + + foreach (QAction* kaction, m_actionCollection->actions()) + { + KateExternalToolAction *action = dynamic_cast(kaction); + if ( action ) + { + l = action->tool->mimetypes; + b = ( ! l.count() || l.contains( mt ) ); + action->setEnabled( b ); + } + } + } +} +//END KateExternalToolsMenuAction + +//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, const char *name ) + : KDialog( parent ), + tool( tool ) +{ + setCaption( i18n("Edit External Tool") ); + setButtons( Ok | Cancel ); + setObjectName( name ); + setModal( true ); + + // create a entry for each property + // fill in the values from the service if available + QWidget *w = new QWidget( this ); + setMainWidget( w ); + QGridLayout *lo = new QGridLayout( w ); + lo->setSpacing( KDialog::spacingHint() ); + + QLabel *l; + + leName = new KLineEdit( w ); + lo->addWidget( leName, 1, 2 ); + l = new QLabel( w ); + l->setBuddy( leName ); + l->setText( i18n("&Label:") ); + l->setAlignment( l->alignment() | Qt::AlignRight ); + lo->addWidget( l, 1, 1 ); + if ( tool ) leName->setText( tool->name ); + leName->setWhatsThis(i18n( + "The name will be displayed in the 'Tools->External' menu") ); + + btnIcon = new KIconButton( w ); + btnIcon->setIconSize( KIconLoader::SizeSmall ); + lo->addWidget( btnIcon, 1, 3 ); + if ( tool && !tool->icon.isEmpty() ) + btnIcon->setIcon( tool->icon ); + + teCommand = new QTextEdit( w ); + lo->addWidget( teCommand, 2, 2, 1, 2 ); + l = new QLabel( w ); + l->setBuddy( teCommand ); + l->setText( i18n("S&cript:") ); + l->setAlignment( Qt::AlignTop | Qt::AlignRight ); + lo->addWidget( l, 2, 1 ); + if ( tool ) teCommand->setText( tool->command ); + teCommand->setWhatsThis(i18n( + "

The script to execute to invoke the tool. The script is passed " + "to /bin/sh for execution. The following macros " + "will be expanded:

" + "
  • %URL - the URL of the current document.
  • " + "
  • %URLs - a list of the URLs of all open documents.
  • " + "
  • %directory - the URL of the directory containing " + "the current document.
  • " + "
  • %filename - the filename of the current document.
  • " + "
  • %line - the current line of the text cursor in the " + "current view.
  • " + "
  • %column - the column of the text cursor in the " + "current view.
  • " + "
  • %selection - the selected text in the current view.
  • " + "
  • %text - the text of the current document.
" ) ); + + + leExecutable = new KLineEdit( w ); + lo->addWidget( leExecutable, 3, 2, 1, 2 ); + l = new QLabel( w ); + l->setBuddy( leExecutable ); + l->setText( i18n("&Executable:") ); + l->setAlignment( l->alignment() | Qt::AlignRight ); + lo->addWidget( l, 3, 1 ); + if ( tool ) leExecutable->setText( tool->tryexec ); + leExecutable->setWhatsThis(i18n( + "The executable used by the command. This is used to check if a tool " + "should be displayed; if not set, the first word of command " + "will be used.") ); + + leMimetypes = new KLineEdit( w ); + lo->addWidget( leMimetypes, 4, 2 ); + l = new QLabel( w ); + l->setBuddy( leMimetypes ); + l->setText( i18n("&Mime types:") ); + l->setAlignment( l->alignment() | Qt::AlignRight ); + lo->addWidget( l, 4, 1 ); + if ( tool ) leMimetypes->setText( tool->mimetypes.join("; ") ); + leMimetypes->setWhatsThis(i18n( + "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.") ); + + QToolButton *btnMTW = new QToolButton(w); + lo->addWidget( btnMTW, 4, 3 ); + btnMTW->setIcon(QIcon(SmallIcon("wizard"))); + connect(btnMTW, SIGNAL(clicked()), this, SLOT(showMTDlg())); + btnMTW->setWhatsThis(i18n( + "Click for a dialog that can help you create a list of mimetypes.") ); + + cmbSave = new KComboBox(w); + lo->addWidget( cmbSave, 5, 2, 1, 2 ); + l = new QLabel( w ); + l->setBuddy( cmbSave ); + l->setText( i18n("&Save:") ); + l->setAlignment( l->alignment() | Qt::AlignRight ); + lo->addWidget( l, 5, 1 ); + QStringList sl; + sl << i18n("None") << i18n("Current Document") << i18n("All Documents"); + cmbSave->addItems( sl ); + if ( tool ) cmbSave->setCurrentIndex( tool->save ); + cmbSave->setWhatsThis(i18n( + "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.") ); + + + leCmdLine = new KLineEdit( w ); + lo->addWidget( leCmdLine, 6, 2, 1, 2 ); + l = new QLabel( i18n("&Command line name:"), w ); + l->setBuddy( leCmdLine ); + l->setAlignment( l->alignment() | Qt::AlignRight ); + lo->addWidget( l, 6, 1 ); + if ( tool ) leCmdLine->setText( tool->cmdname ); + leCmdLine->setWhatsThis(i18n( + "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.")); +} + +void KateExternalToolServiceEditor::slotButtonClicked(int button) +{ + switch( button ) { + case Ok: + if ( leName->text().isEmpty() || teCommand->document()->isEmpty() ) + { + KMessageBox::information( this, i18n("You must specify at least a name and a command") ); + return; + } + accept(); + break; + case Cancel: + reject(); + break; + } + +} + +void KateExternalToolServiceEditor::showMTDlg() +{ + QString text = i18n("Select the MimeTypes for which to enable this tool."); + QStringList list = leMimetypes->text().split( QRegExp("\\s*;\\s*"), QString::SkipEmptyParts ); + KMimeTypeChooserDialog d( i18n("Select Mime Types"), text, list, "text", this ); + if ( d.exec() == KDialog::Accepted ) + { + leMimetypes->setText( d.chooser()->mimeTypes().join(";") ); + } +} +//END KateExternalToolServiceEditor + +//BEGIN KateExternalToolsConfigWidget +KateExternalToolsConfigWidget::KateExternalToolsConfigWidget( QWidget *parent, KateExternalToolsPlugin *plugin, const char* name) + : Kate::PluginConfigPage( parent, name ) + , m_changed( false ) + , m_plugin(plugin) +{ + setupUi(this); + + btnMoveUp->setIcon(KIcon("go-up")); + btnMoveDown->setIcon(KIcon("go-down")); + + connect( lbTools, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectionChanged()) ); + connect( lbTools, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotEdit()) ); + connect( btnNew, SIGNAL(clicked()), this, SLOT(slotNew()) ); + connect( btnRemove, SIGNAL(clicked()), this, SLOT(slotRemove()) ); + connect( btnEdit, SIGNAL(clicked()), this, SLOT(slotEdit()) ); + connect( btnSeparator, SIGNAL(clicked()), this, SLOT(slotInsertSeparator()) ); + connect( btnMoveUp, SIGNAL(clicked()), this, SLOT(slotMoveUp()) ); + connect( btnMoveDown, SIGNAL(clicked()), this, SLOT(slotMoveDown()) ); + + config = new KConfig( "externaltools", KConfig::NoGlobals, "appdata" ); + reset(); + slotSelectionChanged(); +} + +KateExternalToolsConfigWidget::~KateExternalToolsConfigWidget() +{ + delete config; +} + +void KateExternalToolsConfigWidget::reset() +{ + //m_tools.clear(); + lbTools->clear(); + + // load the files from a KConfig + const QStringList tools = config->group("Global").readEntry("tools", QStringList()); + + for( QStringList::const_iterator it = tools.begin(); it != tools.end(); ++it ) + { + if ( *it == "---" ) + { + new QListWidgetItem( "---" , lbTools); + } + else + { + KConfigGroup cg( config, *it ); + + KateExternalTool *t = new KateExternalTool( + cg.readEntry( "name", "" ), + cg.readEntry( "command", ""), + cg.readEntry( "icon", ""), + cg.readEntry( "executable", ""), + cg.readEntry( "mimetypes", QStringList() ), + cg.readEntry( "acname" ), + cg.readEntry( "cmdname"), + cg.readEntry( "save", 0 ) ); + + 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; + + // save a new list + // save each item + QStringList tools; + for ( int i = 0; i < lbTools->count(); i++ ) + { + if ( lbTools->item(i)->text() == "---" ) + { + tools << "---"; + continue; + } + KateExternalTool *t = static_cast(lbTools->item( i ))->tool; +// kDebug(13001)<<"adding tool: "<name; + tools << t->acname; + + KConfigGroup cg( config, t->acname ); + + cg.writeEntry( "name", t->name ); + cg.writeEntry( "command", t->command ); + cg.writeEntry( "icon", t->icon ); + cg.writeEntry( "executable", t->tryexec ); + cg.writeEntry( "mimetypes", t->mimetypes ); + cg.writeEntry( "acname", t->acname ); + cg.writeEntry( "cmdname", t->cmdname ); + cg.writeEntry( "save", t->save ); + } + + config->group("Global").writeEntry( "tools", tools ); + + // if any tools was removed, try to delete their groups, and + // add the group names to the list of removed items. + if ( m_removed.count() ) + { + for ( QStringList::iterator it = m_removed.begin(); it != m_removed.end(); ++it ) + { + if ( config->hasGroup( *it ) ) + config->deleteGroup( *it ); + } + QStringList removed = config->group("Global").readEntry( "removed", QStringList() ); + removed += m_removed; + + // clean up the list of removed items, so that it does not contain + // non-existing groups (we can't remove groups from a non-owned global file). + config->sync(); + QStringList::iterator it1 = removed.begin(); + while ( it1 != removed.end() ) + { + if ( ! config->hasGroup( *it1 ) ) + it1 = removed.erase( it1 ); + else + ++it1; + } + config->group("Global").writeEntry( "removed", removed ); + } + + config->sync(); + m_plugin->reload(); +} + +void KateExternalToolsConfigWidget::slotSelectionChanged() +{ + // update button state + bool hs = lbTools->currentItem() != 0; + 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( 0, this ); + + if ( editor.exec() ) + { + KateExternalTool *t = new KateExternalTool( + editor.leName->text(), + editor.teCommand->toPlainText(), + editor.btnIcon->icon(), + editor.leExecutable->text(), + editor.leMimetypes->text().split( QRegExp("\\s*;\\s*"), QString::SkipEmptyParts ) ); + + // This is sticky, it does not change again, so that shortcuts sticks + // TODO check for dups + t->acname = "externaltool_" + QString(t->name).remove( QRegExp("\\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->acname; + + 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( config->group("Editor").readEntry( "Size", QSize() ) ); + if ( editor.exec() /*== KDialog::Ok*/ ) + { + + bool elementChanged = ( ( editor.btnIcon->icon() != t->icon ) || (editor.leName->text() != t->name ) ) ; + + t->name = editor.leName->text(); + t->cmdname = editor.leCmdLine->text(); + t->command = editor.teCommand->toPlainText(); + t->icon = editor.btnIcon->icon(); + t->tryexec = editor.leExecutable->text(); + t->mimetypes = editor.leMimetypes->text().split( QRegExp("\\s*;\\s*"), QString::SkipEmptyParts ); + t->save = editor.cmbSave->currentIndex(); + + //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( 0, t->icon.isEmpty() ? blankIcon() : SmallIcon( t->icon ), t )); + } + + emit changed(); + m_changed = true; + } + + config->group("Editor").writeEntry( "Size", editor.size() ); + config->sync(); +} + +void KateExternalToolsConfigWidget::slotInsertSeparator() +{ + lbTools->insertItem( lbTools->currentRow() + 1 , "---" ); + emit changed(); + m_changed = true; +} + +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( 0, tool->icon.isEmpty() ? blankIcon() : SmallIcon( tool->icon ), tool ) ); + } + else // a separator! + { + delete lbTools->takeItem( idx ); + lbTools->insertItem( idx - 1 , new QListWidgetItem( "---" )); + } + + 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( 0, tool->icon.isEmpty() ? blankIcon() : SmallIcon( tool->icon ), tool ) ); + } + else // a separator! + { + delete lbTools->takeItem( idx ); + lbTools->insertItem( idx + 1 , new QListWidgetItem( "---" ) ); + } + + lbTools->setCurrentRow( idx + 1 ); + slotSelectionChanged(); + emit changed(); + m_changed = true; +} +//END KateExternalToolsConfigWidget +// kate: space-indent on; indent-width 2; 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,51 @@ +/* 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: + 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,59 @@ +/* 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" +#include "kateexternaltool.h" + +#include + +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& cmd, QString& msg) +{ + const QString command = cmd.trimmed(); + const auto tool = m_plugin->toolForCommand(command); + if (tool) { + msg = i18n("Starts the external tool '%1'", tool->name); + return true; + } + + 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,168 @@ +/* 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 + +class KConfig; +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(); } + +private Q_SLOTS: + void slotAddCategory(); + void slotAddTool(); + void slotEdit(); + void slotRemove(); + void slotSelectionChanged(); + + /** + * Helper to open the ToolDialog. + * Returns true, if the user clicked OK. + */ + bool editTool(KateExternalTool* tool); + + /** + * Creates a new category or returns existing one. + */ + QStandardItem * addCategory(const QString & category); + + /** + * Returns the currently active category. The returned pointer is always valid. + */ + QStandardItem * currentCategory() const; + + /** + * Clears the tools model. + */ + void clearTools(); + +private: + QPixmap blankIcon(); + +private: + KConfig* m_config = nullptr; + bool m_changed = false; + KateExternalToolsPlugin* m_plugin; + QStandardItemModel m_toolsModel; + QStandardItem * m_noCategory = nullptr; +}; + +/** + * 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* m_tool; +}; + +/** + * Action that is only visible if QLineEdit has focus. + */ +class ContextAction : public QObject +{ + Q_OBJECT + +public: + /** + * Constructor that will add @p action to all attached QLineEdits. + */ + ContextAction(const QIcon & icon, const QString & text, QObject * parent = nullptr); + + /** + * Attaches the action provided in the constructor to @p lineEdit + * whenever the @p lineEdit has focus. + */ + void attachTo(QLineEdit * lineEdit); + +Q_SIGNALS: + /** + * This signal is emitted whenever the action was triggered for + * the given @p lineEdit. The @p lineEdit is always a valid pointer. + */ + void triggered(QLineEdit * lineEdit); + +protected: + /** + * Reimplemented to show/hide action for the currently focused + * line edit. + */ + bool eventFilter(QObject *watched, QEvent *event) override; + +private: + QLineEdit * m_currentLineEdit = nullptr; + QAction * m_action; +}; + + +#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,491 @@ +/* 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 "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); + + auto contextAction = new ContextAction(QIcon::fromTheme(QStringLiteral("code-context"), QIcon::fromTheme(QStringLiteral("tools-wizard"))), + QStringLiteral("Insert variable"), this); + contextAction->attachTo(ui->edtExecutable); + contextAction->attachTo(ui->edtArgs); +// contextAction->attachTo(ui->edtInput); // not a QLineEdit + contextAction->attachTo(ui->edtWorkingDir); + + 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->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->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 + + +ContextAction::ContextAction(const QIcon & icon, const QString & text, QObject * parent) + : QObject(parent) + , m_action(new QAction(icon, text, this)) +{ + connect(m_action, &QAction::triggered, [this]() { + if (m_currentLineEdit) { + Q_EMIT triggered(m_currentLineEdit); + } + }); +} + +void ContextAction::attachTo(QLineEdit * lineEdit) +{ + lineEdit->installEventFilter(this); +} + +bool ContextAction::eventFilter(QObject *watched, QEvent *event) +{ + auto lineEdit = qobject_cast(watched); + if (lineEdit) { + if (event->type() == QEvent::FocusOut) { + lineEdit->removeAction(m_action); + m_currentLineEdit = nullptr; + } + else if (event->type() == QEvent::FocusIn) { + m_currentLineEdit = lineEdit; + lineEdit->addAction(m_action, QLineEdit::TrailingPosition); + } + } + return false; +} + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltoolsplugin.h b/addons/externaltools/kateexternaltoolsplugin.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltoolsplugin.h @@ -0,0 +1,89 @@ +/* This file is part of the KDE project + Copyright (C) 2001 Christoph Cullmann + Copyright (C) 2002 Joseph Wenninger + Copyright (C) 2002 Anders Lund + + 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. +*/ + +#ifndef __KATE_EXTERNALTOOLS_H__ +#define __KATE_EXTERNALTOOLS_H__ + +#include +#include +#include + +#include + +#include "kateexternaltools.h" + +class KateExternalToolsPluginView; + +class KateExternalToolsPlugin + : public Kate::Plugin + , public Kate::PluginConfigPageInterface +{ + Q_OBJECT + Q_INTERFACES(Kate::PluginConfigPageInterface) + + public: + explicit KateExternalToolsPlugin( QObject* parent = 0, const QList& = QList() ); + virtual ~KateExternalToolsPlugin(); + + + void reload(); + + Kate::PluginView *createView (Kate::MainWindow *mainWindow); + KateExternalToolsPluginView *extView(QWidget *widget); + private: + QList m_views; + KateExternalToolsCommand *m_command; + private Q_SLOT: + void viewDestroyed(QObject *view); + // + // ConfigInterface + // + public: + virtual uint configPages() const; + virtual Kate::PluginConfigPage *configPage (uint number = 0, QWidget *parent = 0, const char *name = 0 ); + virtual QString configPageName (uint number = 0) const; + virtual QString configPageFullName (uint number = 0) const; + virtual KIcon configPageIcon (uint number = 0) const; + +}; + +class KateExternalToolsPluginView : public Kate::PluginView, public Kate::XMLGUIClient +{ + Q_OBJECT + + public: + /** + * Constructor. + */ + KateExternalToolsPluginView (Kate::MainWindow *mainWindow); + + /** + * Virtual destructor. + */ + ~KateExternalToolsPluginView (); + + void rebuildMenu(); + + KateExternalToolsMenuAction *externalTools; +}; + +#endif +// kate: space-indent on; indent-width 2; replace-tabs on; + diff --git a/addons/externaltools/kateexternaltoolsplugin.cpp b/addons/externaltools/kateexternaltoolsplugin.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltoolsplugin.cpp @@ -0,0 +1,186 @@ +/* This file is part of the KDE project + Copyright (C) 2001 Christoph Cullmann + Copyright (C) 2002 Joseph Wenninger + Copyright (C) 2002 Anders Lund + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kateexternaltoolsplugin.h" +#include "kateexternaltoolsplugin.moc" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +K_PLUGIN_FACTORY(KateExternalToolsFactory, registerPlugin();) +K_EXPORT_PLUGIN(KateExternalToolsFactory(KAboutData("kateexternaltools","kateexternaltoolsplugin",ki18n("External Tools"), "0.1", ki18n("Run external tools"), KAboutData::License_LGPL)) ) + + +KateExternalToolsPlugin::KateExternalToolsPlugin( QObject* parent, const QList& ): + Kate::Plugin ( (Kate::Application*)parent ),m_command(0) +{ + if (KAuthorized::authorizeKAction("shell_access")) + { + KTextEditor::CommandInterface* cmdIface = + qobject_cast( Kate::application()->editor() ); + if( cmdIface ) { + m_command=new KateExternalToolsCommand(this); + cmdIface->registerCommand( m_command ); + } + } +} + +KateExternalToolsPlugin::~KateExternalToolsPlugin() +{ + if (KAuthorized::authorizeKAction("shell_access")) + { + if (m_command) { + KTextEditor::CommandInterface* cmdIface = + qobject_cast( Kate::application()->editor() ); + if( cmdIface ) + cmdIface->unregisterCommand( m_command ); + delete m_command; + } + } +} + +Kate::PluginView *KateExternalToolsPlugin::createView (Kate::MainWindow *mainWindow) +{ + KateExternalToolsPluginView *view= new KateExternalToolsPluginView (mainWindow); + connect(view,SIGNAL(destroyed(QObject*)),this,SLOT(viewDestroyed(QObject*))); + m_views.append(view); + return view; +} + +KateExternalToolsPluginView *KateExternalToolsPlugin::extView(QWidget *widget) +{ + foreach (KateExternalToolsPluginView* view, m_views) + { + if (view->mainWindow()->window()==widget) return view; + } + return 0; +} + +void KateExternalToolsPlugin::viewDestroyed(QObject *view) +{ + m_views.removeAll(dynamic_cast(view)); +} + +void KateExternalToolsPlugin::reload() { + if (KAuthorized::authorizeKAction("shell_access")) + { + KTextEditor::CommandInterface* cmdIface = + qobject_cast( Kate::application()->editor() ); + if (cmdIface) + if(m_command) m_command->reload(); + } + foreach(KateExternalToolsPluginView* view,m_views) { + view->rebuildMenu(); + } +} + +uint KateExternalToolsPlugin::configPages() const +{ + return 1; +} + +Kate::PluginConfigPage *KateExternalToolsPlugin::configPage (uint number, QWidget *parent, const char *name ) +{ + if (number == 0) { + return new KateExternalToolsConfigWidget(parent, this, name); + } + return 0; +} + +QString KateExternalToolsPlugin::configPageName (uint number) const +{ + if (number == 0) { + return i18n("External Tools"); + } + return QString(); +} + +QString KateExternalToolsPlugin::configPageFullName (uint number) const +{ + if (number == 0) { + return i18n("External Tools"); + } + return QString(); +} + +KIcon KateExternalToolsPlugin::configPageIcon (uint number) const +{ + if (number == 0) { + return KIcon(); + } + return KIcon(); +} + + +KateExternalToolsPluginView::KateExternalToolsPluginView (Kate::MainWindow *mainWindow) + : Kate::PluginView (mainWindow), Kate::XMLGUIClient(KateExternalToolsFactory::componentData()) +{ + externalTools = 0; + + if (KAuthorized::authorizeKAction("shell_access")) + { + externalTools = new KateExternalToolsMenuAction( i18n("External Tools"), actionCollection(), mainWindow, mainWindow ); + actionCollection()->addAction("tools_external", externalTools); + externalTools->setWhatsThis( i18n("Launch external helper applications") ); + + } + + mainWindow->guiFactory()->addClient (this); +} + +void KateExternalToolsPluginView::rebuildMenu() { + kDebug(13001); + if (externalTools) { + KXMLGUIFactory *f=factory(); + f->removeClient(this); + reloadXML(); + externalTools->reload(); + kDebug(13001)<<"has just returned from externalTools->reload()"; + f->addClient(this); + } +} + +KateExternalToolsPluginView::~KateExternalToolsPluginView () +{ + mainWindow()->guiFactory()->removeClient (this); + + delete externalTools; + externalTools = 0; +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/addons/externaltools/kateexternaltoolsplugin.desktop b/addons/externaltools/kateexternaltoolsplugin.desktop new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltoolsplugin.desktop @@ -0,0 +1,121 @@ +[Desktop Entry] +Type=Service +ServiceTypes=Kate/Plugin +X-KDE-Library=kateexternaltoolsplugin +X-Kate-Version=2.8 +Name=External Tools +Name[ar]=الأدوات الخارجية +Name[ast]=Ferramientes esternes +Name[be]=Знешнія інструменты +Name[bg]=Външни инструменти +Name[bs]=Spoljašnje alatke +Name[ca]=Eines externes +Name[ca@valencia]=Eines externes +Name[cs]=Externí nástroje +Name[da]=Eksterne værktøjer +Name[de]=Externe Programme +Name[el]=Εξωτερικά εργαλεία +Name[en_GB]=External Tools +Name[es]=Herramientas externas +Name[et]=Välised tööriistad +Name[eu]=Kanpoko tresnak +Name[fi]=Ulkoiset työkalut +Name[fr]=Outils externes +Name[ga]=Uirlisí Seachtracha +Name[gl]=Utilidades externas +Name[he]=כלים חיצוניים +Name[hu]=Külső eszközök +Name[ia]=Instrumentos externe +Name[it]=Strumenti esterni +Name[ja]=外部ツール +Name[kk]=Сыртқы құралдар +Name[km]=ឧបករណ៍​ខាង​ក្រៅ +Name[ko]=외부 도구 +Name[lt]=Išoriniai įrankiai +Name[lv]=Ārējie rīki +Name[mai]=बाहरी अओजार +Name[nb]=Eksterne verktøy +Name[nds]=Extern Warktüüch +Name[ne]=बाह्य उपकरण +Name[nl]=Externe hulpmiddelen +Name[nn]=Eksterne verktøy +Name[pa]=ਬਾਹਰੀ ਟੂਲ +Name[pl]=Narzędzia zewnętrzne +Name[pt]=Ferramentas Externas +Name[pt_BR]=Ferramentas externas +Name[ro]=Unelte externe +Name[ru]=Внешние инструменты +Name[si]=බාහිර මෙවලම් +Name[sk]=Externé nástroje +Name[sl]=Zunanja orodja +Name[sr]=Спољашње алатке +Name[sr@ijekavian]=Спољашње алатке +Name[sr@ijekavianlatin]=Spoljašnje alatke +Name[sr@latin]=Spoljašnje alatke +Name[sv]=Externa verktyg +Name[tg]=Асбобҳои берунӣ +Name[tr]=Dış Araçlar +Name[ug]=سىرتقى قوراللار +Name[uk]=Зовнішні засоби +Name[wa]=Divantrinnès usteyes +Name[x-test]=xxExternal Toolsxx +Name[zh_CN]=外部工具 +Name[zh_TW]=外部工具 +Comment=External Tools +Comment[ar]=الأدوات الخارجية +Comment[ast]=Ferramientes esternes +Comment[be]=Знешнія інструменты +Comment[bg]=Външни инструменти +Comment[bs]=Spoljašnje alatke +Comment[ca]=Eines externes +Comment[ca@valencia]=Eines externes +Comment[cs]=Externí nástroje +Comment[da]=Eksterne værktøjer +Comment[de]=Externe Programme +Comment[el]=Εξωτερικά εργαλεία +Comment[en_GB]=External Tools +Comment[es]=Herramientas externas +Comment[et]=Välised tööriistad +Comment[eu]=Kanpoko tresnak +Comment[fi]=Ulkoiset työkalut +Comment[fr]=Outils externes +Comment[ga]=Uirlisí Seachtracha +Comment[gl]=Utilidades externas +Comment[he]=כלים חיצוניים +Comment[hu]=Külső eszközök +Comment[ia]=Instrumentos externe +Comment[it]=Strumenti esterni +Comment[ja]=外部ツール +Comment[kk]=Сыртқы құралдар +Comment[km]=ឧបករណ៍​ខាង​ក្រៅ +Comment[ko]=외부 도구 +Comment[lt]=Išoriniai įrankiai +Comment[lv]=Ārējie rīki +Comment[mai]=बाहरी अओजार +Comment[nb]=Eksterne verktøy +Comment[nds]=Extern Warktüüch +Comment[ne]=बाह्य उपकरण +Comment[nl]=Externe hulpmiddelen +Comment[nn]=Eksterne verktøy +Comment[pa]=ਬਾਹਰੀ ਟੂਲ +Comment[pl]=Narzędzia zewnętrzne +Comment[pt]=Ferramentas Externas +Comment[pt_BR]=Ferramentas externas +Comment[ro]=Unelte externe +Comment[ru]=Внешние инструменты +Comment[si]=බාහිර මෙවලම් +Comment[sk]=Externé nástroje +Comment[sl]=Zunanja orodja +Comment[sr]=Спољашње алатке +Comment[sr@ijekavian]=Спољашње алатке +Comment[sr@ijekavianlatin]=Spoljašnje alatke +Comment[sr@latin]=Spoljašnje alatke +Comment[sv]=Externa verktyg +Comment[tg]=Асбобҳои берунӣ +Comment[tr]=Dış Araçlar +Comment[ug]=سىرتقى قوراللار +Comment[uk]=Зовнішні інструменти +Comment[wa]=Divantrinnès usteyes +Comment[x-test]=xxExternal Toolsxx +Comment[zh_CN]=调用外部工具 +Comment[zh_TW]=外部工具 diff --git a/addons/externaltools/kateexternaltoolsview.h b/addons/externaltools/kateexternaltoolsview.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltoolsview.h @@ -0,0 +1,144 @@ +/* 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 QTextDocument; + +class KActionCollection; +class KateExternalToolsPlugin; +class KateExternalTool; + +namespace Ui { class ToolView; } + +enum class ToolViewFocus { + OutputTab = 0, + StatusTab +}; + +/** + * Menu action that displays all KateExternalTool in a submenu. + * Enables/disables the tool actions whenever the view changes, depending on the mimetype. + */ +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() const { return m_actionCollection; } + +private Q_SLOTS: + /** + * Called whenever the current view changed. + * Required to enable/disable the tools that depend on specific mimetypes. + */ + void slotViewChanged(KTextEditor::View* view); + +private: + KateExternalToolsPlugin* m_plugin; + KTextEditor::MainWindow* m_mainwindow; // for the actions to access view/doc managers + KActionCollection* m_actionCollection; +}; + +class KateExternalToolsPluginView : public QObject, public KXMLGUIClient +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + KateExternalToolsPluginView(KTextEditor::MainWindow* mainWindow, KateExternalToolsPlugin* plugin); + + /** + * Virtual destructor. + */ + ~KateExternalToolsPluginView(); + + /** + * Returns the associated mainWindow + */ + KTextEditor::MainWindow* mainWindow() const; + +public Q_SLOTS: + /** + * Called by the plugin view to reload the menu + */ + void rebuildMenu(); + + /** + * Creates the tool view. If already existing, does nothing. + */ + void createToolView(); + + /** + * Shows the tool view. The toolview will be created, if not yet existing. + */ + void showToolView(ToolViewFocus tab); + + /** + * Clears the toolview data. If no toolview is around, nothing happens. + */ + void clearToolView(); + + /** + * Shows the External Tools toolview and points the error message along with + * some more info about the tool. + */ + void addToolStatus(const QString& message); + + /** + * Sets the output data to data; + */ + void setOutputData(const QString& data); + + /** + * Deletes the tool view, if existing. + */ + void deleteToolView(); + +private: + KateExternalToolsPlugin* m_plugin; + KTextEditor::MainWindow* m_mainWindow; + KateExternalToolsMenuAction* m_externalToolsMenu = nullptr; + QWidget* m_toolView = nullptr; + Ui::ToolView* m_ui = nullptr; + QTextDocument* m_outputDoc = nullptr; + QTextDocument* m_statusDoc = nullptr; +}; + +#endif // KTEXTEDITOR_EXTERNALTOOLS_H + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/kateexternaltoolsview.cpp b/addons/externaltools/kateexternaltoolsview.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/kateexternaltoolsview.cpp @@ -0,0 +1,271 @@ +/* This file is part of the KDE project + * + * Copyright 2019 Dominik Haumann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#include "kateexternaltoolsview.h" +#include "externaltoolsplugin.h" +#include "kateexternaltool.h" +#include "ui_toolview.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + + +// BEGIN KateExternalToolsMenuAction +KateExternalToolsMenuAction::KateExternalToolsMenuAction(const QString& text, KActionCollection* collection, + KateExternalToolsPlugin* plugin, KTextEditor::MainWindow* mw) + : KActionMenu(text, mw) + , m_plugin(plugin) + , m_mainwindow(mw) + , m_actionCollection(collection) +{ + reload(); + + // track active view to adapt enabled tool actions + connect(mw, &KTextEditor::MainWindow::viewChanged, this, &KateExternalToolsMenuAction::slotViewChanged); +} + +KateExternalToolsMenuAction::~KateExternalToolsMenuAction() = default; + +void KateExternalToolsMenuAction::reload() +{ + // clear action collection + bool needs_readd = (m_actionCollection->takeAction(this) != nullptr); + m_actionCollection->clear(); + if (needs_readd) + m_actionCollection->addAction(QStringLiteral("tools_external"), this); + menu()->clear(); + + // create tool actions + std::map categories; + std::vector uncategorizedActions; + + // first add categorized actions, such that the submenus appear at the top + for (auto tool : m_plugin->tools()) { + if (tool->hasexec) { + auto a = new QAction(tool->name, this); + a->setIcon(QIcon::fromTheme(tool->icon)); + a->setData(QVariant::fromValue(tool)); + + connect(a, &QAction::triggered, [this, a]() { + m_plugin->runTool(*a->data().value(), m_mainwindow->activeView()); + }); + + m_actionCollection->addAction(tool->actionName, a); + if (!tool->category.isEmpty()) { + auto categoryMenu = categories[tool->category]; + if (!categoryMenu) { + categoryMenu = new KActionMenu(tool->category, this); + categories[tool->category] = categoryMenu; + addAction(categoryMenu); + } + categoryMenu->addAction(a); + } else { + uncategorizedActions.push_back(a); + } + } + } + + // now add uncategorized actions below + for (auto uncategorizedAction : uncategorizedActions) { + addAction(uncategorizedAction); + } + + // load shortcuts + KSharedConfig::Ptr pConfig = KSharedConfig::openConfig(QStringLiteral("externaltools"), KConfig::NoGlobals, + QStandardPaths::ApplicationsLocation); + KConfigGroup config(pConfig, "Global"); + config = KConfigGroup(pConfig, "Shortcuts"); + m_actionCollection->readSettings(&config); + slotViewChanged(m_mainwindow->activeView()); +} + +void KateExternalToolsMenuAction::slotViewChanged(KTextEditor::View* view) +{ + // no active view, oh oh + if (!view) { + return; + } + + // try to enable/disable to match current mime type + const QString mimeType = view->document()->mimeType(); + foreach (QAction* action, m_actionCollection->actions()) { + if (action && action->data().value()) { + auto tool = action->data().value(); + action->setEnabled(tool->matchesMimetype(mimeType)); + } + } +} +// END KateExternalToolsMenuAction + + + + + +// BEGIN KateExternalToolsPluginView +KateExternalToolsPluginView::KateExternalToolsPluginView(KTextEditor::MainWindow* mainWindow, + KateExternalToolsPlugin* plugin) + : QObject(mainWindow) + , m_plugin(plugin) + , m_mainWindow(mainWindow) + , m_outputDoc(new QTextDocument(this)) + , m_statusDoc(new QTextDocument(this)) +{ + m_plugin->registerPluginView(this); + + KXMLGUIClient::setComponentName(QLatin1String("externaltools"), i18n("External Tools")); + setXMLFile(QLatin1String("ui.rc")); + + if (KAuthorized::authorizeAction(QStringLiteral("shell_access"))) { + m_externalToolsMenu = new KateExternalToolsMenuAction(i18n("External Tools"), actionCollection(), plugin, mainWindow); + actionCollection()->addAction(QStringLiteral("tools_external"), m_externalToolsMenu); + m_externalToolsMenu->setWhatsThis(i18n("Launch external helper applications")); + } + + mainWindow->guiFactory()->addClient(this); + + // ESC should close & hide ToolView + connect(m_mainWindow, &KTextEditor::MainWindow::unhandledShortcutOverride, [this](QEvent* event) { + auto keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape && keyEvent->modifiers() == Qt::NoModifier) { + deleteToolView(); + } + }); +} + +KateExternalToolsPluginView::~KateExternalToolsPluginView() +{ + m_plugin->unregisterPluginView(this); + + m_mainWindow->guiFactory()->removeClient(this); + + deleteToolView(); + + delete m_externalToolsMenu; + m_externalToolsMenu = nullptr; +} + +void KateExternalToolsPluginView::rebuildMenu() +{ + if (m_externalToolsMenu) { + KXMLGUIFactory* f = factory(); + f->removeClient(this); + reloadXML(); + m_externalToolsMenu->reload(); + f->addClient(this); + } +} + +KTextEditor::MainWindow* KateExternalToolsPluginView::mainWindow() const +{ + return m_mainWindow; +} + +void KateExternalToolsPluginView::createToolView() +{ + if (!m_toolView) { + m_toolView = mainWindow()->createToolView(m_plugin, QStringLiteral("ktexteditor_plugin_externaltools"), + KTextEditor::MainWindow::Bottom, QIcon::fromTheme(QStringLiteral("system-run")), i18n("External Tools")); + + m_ui = new Ui::ToolView(); + m_ui->setupUi(m_toolView); + + // set the documents + m_ui->teOutput->setDocument(m_outputDoc); + m_ui->teStatus->setDocument(m_statusDoc); + + // use fixed font for displaying status and output text + const auto fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); + m_ui->teOutput->setFont(fixedFont); + m_ui->teStatus->setFont(fixedFont); + + // close button to delete tool view + auto btnClose = new QToolButton(); + btnClose->setAutoRaise(true); + btnClose->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); + connect(btnClose, &QToolButton::clicked, this, &KateExternalToolsPluginView::deleteToolView); + m_ui->tabWidget->setCornerWidget(btnClose); + } +} + +void KateExternalToolsPluginView::showToolView(ToolViewFocus tab) +{ + createToolView(); + + if (tab == ToolViewFocus::OutputTab) { + m_ui->tabWidget->setCurrentWidget(m_ui->tabOutput); + } else { + m_ui->tabWidget->setCurrentWidget(m_ui->tabStatus); + } + + mainWindow()->showToolView(m_toolView); +} + +void KateExternalToolsPluginView::clearToolView() +{ + m_outputDoc->clear(); + m_statusDoc->clear(); +} + +void KateExternalToolsPluginView::addToolStatus(const QString& message) +{ + QTextCursor cursor(m_statusDoc); + cursor.movePosition(QTextCursor::End); + cursor.insertText(message); + cursor.insertText(QStringLiteral("\n")); +} + +void KateExternalToolsPluginView::setOutputData(const QString& data) +{ + QTextCursor cursor(m_outputDoc); + cursor.movePosition(QTextCursor::End); + cursor.insertText(data); +} + +void KateExternalToolsPluginView::deleteToolView() +{ + if (m_toolView) { + delete m_ui; + m_ui = nullptr; + + delete m_toolView; + m_toolView = nullptr; + } +} +// END KateExternalToolsPluginView + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/addons/externaltools/katetoolrunner.h b/addons/externaltools/katetoolrunner.h new file mode 100644 --- /dev/null +++ b/addons/externaltools/katetoolrunner.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_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. + */ + QString outputData() const; + + /** + * Returns the data that was collected on stderr. + */ + QString errorData() const; + +Q_SIGNALS: + /** + * This signal is emitted when the tool is finished. + */ + 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 + 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/katetoolrunner.cpp b/addons/externaltools/katetoolrunner.cpp new file mode 100644 --- /dev/null +++ b/addons/externaltools/katetoolrunner.cpp @@ -0,0 +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->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::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_stdout); +} + +QString KateToolRunner::errorData() const +{ + return QString::fromLocal8Bit(m_stderr); +} + +// 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,319 @@ + + + ToolDialog + + + + 0 + 0 + 470 + 534 + + + + Edit Tool + + + + + + Na&me: + + + edtName + + + + + + + + + The name will be displayed in the 'Tools->External Tools' menu. + + + Short name of the tool + + + + + + + + + + + + E&xecutable: + + + edtExecutable + + + + + + + + + Application or interpreter + + + + + + + + .. + + + QToolButton::InstantPopup + + + + + + + + + Ar&guments: + + + edtArgs + + + + + + + Command line arguments + + + + + + + &Input: + + + edtInput + + + + + + + Optional standard input + + + + + + + Working &directory: + + + edtWorkingDir + + + + + + + + + Uses current document path if empty + + + + + + + + .. + + + QToolButton::InstantPopup + + + + + + + + + Mime &types: + + + edtMimeType + + + + + + + + + A semicolon-separated list of mime types for which this tool should be available. If empty, the tool is always available. + + + Show tool only for given mime types + + + + + + + Opens a dialog that helps in creating a list of mimetypes. + + + + .. + + + + + + + + + Save: + + + cmbSave + + + + + + + Optionally 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 + + + + + + + + KIconButton + QPushButton +
kiconbutton.h
+
+
+ + edtName + btnIcon + edtExecutable + btnExecutable + edtArgs + edtInput + edtWorkingDir + btnWorkingDir + edtMimeType + btnMimeType + cmbSave + chkReload + cmbOutput + edtCommand + + + +
diff --git a/addons/externaltools/toolview.ui b/addons/externaltools/toolview.ui new file mode 100644 --- /dev/null +++ b/addons/externaltools/toolview.ui @@ -0,0 +1,63 @@ + + + ToolView + + + + 0 + 0 + 678 + 295 + + + + + + 6 + 6 + 451 + 240 + + + + 0 + + + + Output + + + + + + true + + + Displays output from the external tool + + + + + + + + Status + + + + + + true + + + No errors detected + + + + + + + + + + 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 + + + +