[utilities/kate] /: Build: Allow run commands

Waqar Ahmed null at kde.org
Tue Oct 4 14:43:58 BST 2022


Git commit 5ef70ab5bea900a7f117dd91e146050df2e1994b by Waqar Ahmed.
Committed on 04/10/2022 at 13:11.
Pushed by waqar into branch 'master'.

Build: Allow run commands

Build plugin is great but it misses one functionality i.e., ability to
run a target. With this change you will be able to do so.

M  +3    -0    addons/katebuild-plugin/TargetHtmlDelegate.cpp
M  +24   -23   addons/katebuild-plugin/TargetModel.cpp
M  +7    -2    addons/katebuild-plugin/TargetModel.h
M  +72   -7    addons/katebuild-plugin/plugin_katebuild.cpp
M  +5    -0    addons/katebuild-plugin/plugin_katebuild.h
M  +6    -1    addons/katebuild-plugin/targets.cpp
M  +1    -0    addons/katebuild-plugin/targets.h
M  +2    -1    addons/katebuild-plugin/ui.rc
M  +7    -0    addons/project/kateprojectinfoview.cpp
M  +2    -0    addons/project/kateprojectinfoview.h
M  +13   -0    addons/project/kateprojectinfoviewterminal.cpp
M  +2    -0    addons/project/kateprojectinfoviewterminal.h
M  +10   -0    addons/project/kateprojectpluginview.cpp
M  +2    -0    addons/project/kateprojectpluginview.h
M  +4    -0    doc/kate/plugins.docbook

https://invent.kde.org/utilities/kate/commit/5ef70ab5bea900a7f117dd91e146050df2e1994b

diff --git a/addons/katebuild-plugin/TargetHtmlDelegate.cpp b/addons/katebuild-plugin/TargetHtmlDelegate.cpp
index 78e9cc1c0..42cddf3da 100644
--- a/addons/katebuild-plugin/TargetHtmlDelegate.cpp
+++ b/addons/katebuild-plugin/TargetHtmlDelegate.cpp
@@ -88,6 +88,9 @@ QSize TargetHtmlDelegate::sizeHint(const QStyleOptionViewItem & /* option */, co
     if (index.column() == 0 && index.internalId() != TargetModel::InvalidIndex) {
         return doc.size().toSize() + QSize(30, 0); // add margin for the check-box;
     }
+    if (index.column() == 1 && !index.parent().isValid()) {
+        return doc.size().toSize() + QSize(38, 0); // add space for "Dir"
+    }
     return doc.size().toSize();
 }
 
diff --git a/addons/katebuild-plugin/TargetModel.cpp b/addons/katebuild-plugin/TargetModel.cpp
index 61d6c9d4f..1d78b1b32 100644
--- a/addons/katebuild-plugin/TargetModel.cpp
+++ b/addons/katebuild-plugin/TargetModel.cpp
@@ -45,7 +45,7 @@ void TargetModel::setDefaultCmd(int rootRow, const QString &defCmd)
     }
 
     for (int i = 0; i < m_targets[rootRow].commands.size(); i++) {
-        if (defCmd == m_targets[rootRow].commands[i].first) {
+        if (defCmd == m_targets[rootRow].commands[i].name) {
             m_targets[rootRow].defaultCmd = defCmd;
             return;
         }
@@ -70,7 +70,7 @@ QModelIndex TargetModel::addTargetSet(const QString &setName, const QString &wor
     return index(m_targets.count() - 1, 0);
 }
 
-QModelIndex TargetModel::addCommand(const QModelIndex &parentIndex, const QString &cmdName, const QString &command)
+QModelIndex TargetModel::addCommand(const QModelIndex &parentIndex, const QString &cmdName, const QString &buildCmd, const QString &runCmd)
 {
     int rootRow = parentIndex.row();
     if (rootRow < 0 || rootRow >= m_targets.size()) {
@@ -81,7 +81,7 @@ QModelIndex TargetModel::addCommand(const QModelIndex &parentIndex, const QStrin
     // make the name unique
     QString newName = cmdName;
     for (int i = 0; i < m_targets[rootRow].commands.count(); i++) {
-        if (m_targets[rootRow].commands[i].first == newName) {
+        if (m_targets[rootRow].commands[i].name == newName) {
             newName += QStringLiteral(" 2");
             i = -1;
         }
@@ -89,7 +89,7 @@ QModelIndex TargetModel::addCommand(const QModelIndex &parentIndex, const QStrin
 
     QModelIndex rootIndex = createIndex(rootRow, 0, InvalidIndex);
     beginInsertRows(rootIndex, m_targets[rootRow].commands.count(), m_targets[rootRow].commands.count());
-    m_targets[rootRow].commands << QPair<QString, QString>(newName, command);
+    m_targets[rootRow].commands << Command{newName, buildCmd, runCmd};
     endInsertRows();
     return createIndex(m_targets[rootRow].commands.size() - 1, 0, rootRow);
 }
@@ -137,14 +137,15 @@ QModelIndex TargetModel::copyTargetOrSet(const QModelIndex &index)
     QModelIndex rootIndex = createIndex(rootRow, 0, InvalidIndex);
     beginInsertRows(rootIndex, m_targets[rootRow].commands.count(), m_targets[rootRow].commands.count());
 
-    QString newName = m_targets[rootRow].commands[index.row()].first + QStringLiteral(" 2");
+    const auto cmd = m_targets[rootRow].commands[index.row()];
+    QString newName = cmd.name + QStringLiteral(" 2");
     for (int i = 0; i < m_targets[rootRow].commands.count(); i++) {
-        if (m_targets[rootRow].commands[i].first == newName) {
+        if (m_targets[rootRow].commands[i].name == newName) {
             newName += QStringLiteral(" 2");
             i = -1;
         }
     }
-    m_targets[rootRow].commands << QPair<QString, QString>(newName, m_targets[rootRow].commands[index.row()].second);
+    m_targets[rootRow].commands << Command{newName, cmd.buildCmd, cmd.runCmd};
 
     endInsertRows();
     return createIndex(m_targets[rootRow].commands.count() - 1, 0, rootRow);
@@ -299,13 +300,10 @@ const QString TargetModel::targetName(const QModelIndex &itemIndex)
 
 QVariant TargetModel::data(const QModelIndex &index, int role) const
 {
-    if (!index.isValid()) {
+    if (!index.isValid() || !hasIndex(index.row(), index.column(), index.parent())) {
         return QVariant();
     }
 
-    if (index.column() < 0 || index.column() > 1) {
-        return QVariant();
-    }
     // Tooltip
     if (role == Qt::ToolTipRole) {
         if (index.column() == 0 && index.parent().isValid()) {
@@ -323,6 +321,7 @@ QVariant TargetModel::data(const QModelIndex &index, int role) const
         if (row < 0 || row >= m_targets.size() || role == Qt::CheckStateRole) {
             return QVariant();
         }
+
         switch (index.column()) {
         case 0:
             return m_targets[row].name;
@@ -342,13 +341,15 @@ QVariant TargetModel::data(const QModelIndex &index, int role) const
             if (index.column() != 0) {
                 return QVariant();
             }
-            return m_targets[rootIndex].commands[row].first == m_targets[rootIndex].defaultCmd ? Qt::Checked : Qt::Unchecked;
+            return m_targets[rootIndex].commands[row].buildCmd == m_targets[rootIndex].defaultCmd ? Qt::Checked : Qt::Unchecked;
         } else {
             switch (index.column()) {
             case 0:
-                return m_targets[rootIndex].commands[row].first;
+                return m_targets[rootIndex].commands[row].name;
             case 1:
-                return m_targets[rootIndex].commands[row].second;
+                return m_targets[rootIndex].commands[row].buildCmd;
+            case 2:
+                return m_targets[rootIndex].commands[row].runCmd;
             }
         }
     }
@@ -372,6 +373,9 @@ QVariant TargetModel::headerData(int section, Qt::Orientation orientation, int r
     if (section == 1) {
         return i18n("Working Directory / Command");
     }
+    if (section == 2) {
+        return i18n("Run Command");
+    }
     return QVariant();
 }
 
@@ -381,14 +385,11 @@ bool TargetModel::setData(const QModelIndex &index, const QVariant &value, int r
     if (role != Qt::EditRole && role != Qt::CheckStateRole) {
         return false;
     }
-    if (!index.isValid()) {
-        return false;
-    }
-    if (index.column() < 0 || index.column() > 1) {
+    if (!index.isValid() || !hasIndex(index.row(), index.column(), index.parent())) {
         return false;
     }
-    int row = index.row();
 
+    int row = index.row();
     if (index.internalId() == InvalidIndex) {
         if (row < 0 || row >= m_targets.size()) {
             return false;
@@ -412,16 +413,16 @@ bool TargetModel::setData(const QModelIndex &index, const QVariant &value, int r
 
         if (role == Qt::CheckStateRole) {
             if (index.column() == 0) {
-                m_targets[rootIndex].defaultCmd = m_targets[rootIndex].commands[row].first;
+                m_targets[rootIndex].defaultCmd = m_targets[rootIndex].commands[row].buildCmd;
                 Q_EMIT dataChanged(createIndex(0, 0, rootIndex), createIndex(m_targets[rootIndex].commands.size() - 1, 0, rootIndex));
             }
         } else {
             switch (index.column()) {
             case 0:
-                m_targets[rootIndex].commands[row].first = value.toString();
+                m_targets[rootIndex].commands[row].name = value.toString();
                 return true;
             case 1:
-                m_targets[rootIndex].commands[row].second = value.toString();
+                m_targets[rootIndex].commands[row].buildCmd = value.toString();
                 return true;
             }
         }
@@ -462,7 +463,7 @@ int TargetModel::rowCount(const QModelIndex &parent) const
 
 int TargetModel::columnCount(const QModelIndex &) const
 {
-    return 2;
+    return 3;
 }
 
 QModelIndex TargetModel::index(int row, int column, const QModelIndex &parent) const
diff --git a/addons/katebuild-plugin/TargetModel.h b/addons/katebuild-plugin/TargetModel.h
index 94c7b420c..f216aac44 100644
--- a/addons/katebuild-plugin/TargetModel.h
+++ b/addons/katebuild-plugin/TargetModel.h
@@ -15,12 +15,17 @@ class TargetModel : public QAbstractItemModel
 {
     Q_OBJECT
 public:
+    struct Command {
+        QString name;
+        QString buildCmd;
+        QString runCmd;
+    };
     struct TargetSet {
         TargetSet(const QString &_name, const QString &_workDir);
         QString name;
         QString workDir;
         QString defaultCmd;
-        QList<QPair<QString, QString>> commands;
+        QList<Command> commands;
     };
 
     TargetModel(QObject *parent = nullptr);
@@ -39,7 +44,7 @@ public Q_SLOTS:
     QModelIndex addTargetSet(const QString &setName, const QString &workDir);
 
     /** This function adds a new command to a target-set and returns the model index */
-    QModelIndex addCommand(const QModelIndex &parentIndex, const QString &cmdName, const QString &command);
+    QModelIndex addCommand(const QModelIndex &parentIndex, const QString &cmdName, const QString &buildCmd, const QString &runCmd = {});
 
     /** This function copies the target(-set) the model index points to and returns
      * the model index of the copy. */
diff --git a/addons/katebuild-plugin/plugin_katebuild.cpp b/addons/katebuild-plugin/plugin_katebuild.cpp
index b2076dac7..adfbb5151 100644
--- a/addons/katebuild-plugin/plugin_katebuild.cpp
+++ b/addons/katebuild-plugin/plugin_katebuild.cpp
@@ -141,6 +141,10 @@ KateBuildView::KateBuildView(KTextEditor::Plugin *plugin, KTextEditor::MainWindo
     a->setText(i18n("Build Default Target"));
     connect(a, &QAction::triggered, this, &KateBuildView::slotBuildDefaultTarget);
 
+    a = actionCollection()->addAction(QStringLiteral("build_and_run_default_target"));
+    a->setText(i18n("Build & Run Default Target"));
+    connect(a, &QAction::triggered, this, &KateBuildView::slotBuildAndRunDefaultTarget);
+
     a = actionCollection()->addAction(QStringLiteral("build_previous_target"));
     a->setText(i18n("Build Previous Target"));
     connect(a, &QAction::triggered, this, &KateBuildView::slotBuildPreviousTarget);
@@ -220,7 +224,12 @@ KateBuildView::KateBuildView(KTextEditor::Plugin *plugin, KTextEditor::MainWindo
 
     connect(m_targetsUi->addButton, &QToolButton::clicked, this, &KateBuildView::slotAddTargetClicked);
     connect(m_targetsUi->buildButton, &QToolButton::clicked, this, &KateBuildView::slotBuildActiveTarget);
+    connect(m_targetsUi->runButton, &QToolButton::clicked, this, [this] {
+        m_runAfterBuild = true;
+        slotBuildActiveTarget();
+    });
     connect(m_targetsUi, &TargetsUi::enterPressed, this, &KateBuildView::slotBuildActiveTarget);
+    connect(m_targetsUi->targetsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &KateBuildView::onSelectionChanged);
 
     m_proc.setOutputChannelMode(KProcess::SeparateChannels);
     connect(&m_proc, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &KateBuildView::slotProcExited);
@@ -304,10 +313,6 @@ void KateBuildView::readSessionConfig(const KConfigGroup &cg)
         tmpCmd = cg.readEntry(QStringLiteral("Active Target Command"), 0);
     }
 
-    m_targetsUi->targetsView->expandAll();
-    m_targetsUi->targetsView->resizeColumnToContents(0);
-    m_targetsUi->targetsView->collapseAll();
-
     QModelIndex root = m_targetsUi->targetsModel.index(tmpIndex);
     QModelIndex cmdIndex = m_targetsUi->targetsModel.index(tmpCmd, 0, root);
     cmdIndex = m_targetsUi->proxyModel.mapFromSource(cmdIndex);
@@ -318,6 +323,10 @@ void KateBuildView::readSessionConfig(const KConfigGroup &cg)
 
     // Add project targets, if any
     slotAddProjectTarget();
+
+    m_targetsUi->targetsView->expandAll();
+    m_targetsUi->targetsView->resizeColumnToContents(0);
+    m_targetsUi->targetsView->resizeColumnToContents(1);
 }
 
 /******************************************************************/
@@ -336,8 +345,8 @@ void KateBuildView::writeSessionConfig(KConfigGroup &cg)
         QStringList cmdNames;
 
         for (int j = 0; j < targets[i].commands.count(); j++) {
-            const QString &cmdName = targets[i].commands[j].first;
-            const QString &buildCmd = targets[i].commands[j].second;
+            const QString &cmdName = targets[i].commands[j].name;
+            const QString &buildCmd = targets[i].commands[j].buildCmd;
             cmdNames << cmdName;
             cg.writeEntry(QStringLiteral("%1 BuildCmd %2").arg(i).arg(cmdName), buildCmd);
         }
@@ -850,6 +859,16 @@ void KateBuildView::slotBuildActiveTarget()
     }
 }
 
+void KateBuildView::slotBuildAndRunDefaultTarget()
+{
+    if (!m_targetsUi->targetsView->currentIndex().isValid()) {
+        slotSelectTarget();
+    } else {
+        m_runAfterBuild = true;
+        slotBuildDefaultTarget();
+    }
+}
+
 /******************************************************************/
 void KateBuildView::slotBuildPreviousTarget()
 {
@@ -1023,6 +1042,33 @@ void KateBuildView::slotProcExited(int exitCode, QProcess::ExitStatus)
         // add marks
         slotViewChanged();
     }
+
+    slotRunAfterBuild();
+}
+
+void KateBuildView::slotRunAfterBuild()
+{
+    if (!m_runAfterBuild) {
+        return;
+    }
+    m_runAfterBuild = false;
+    if (!m_previousIndex.isValid()) {
+        return;
+    }
+    auto *projectPluginView = m_win->pluginView(QStringLiteral("kateprojectplugin"));
+    QModelIndex idx = m_previousIndex;
+    idx = idx.siblingAtColumn(2);
+    const QString runCmd = idx.data().toString();
+    if (runCmd.isEmpty()) {
+        return;
+    }
+    const QString workDir = TargetModel::workDir(idx);
+    if (workDir.isEmpty()) {
+        return;
+    }
+    if (projectPluginView) {
+        QMetaObject::invokeMethod(projectPluginView, "runCmdInTerminal", Q_ARG(QString, workDir), Q_ARG(QString, runCmd));
+    }
 }
 
 static void appendPlainTextTo(QPlainTextEdit *edit, const QString &text)
@@ -1340,11 +1386,12 @@ void KateBuildView::slotAddProjectTarget()
         QVariantMap targetMap = targetVariant.toMap();
         QString tgtName = targetMap[QStringLiteral("name")].toString();
         QString buildCmd = targetMap[QStringLiteral("build_cmd")].toString();
+        QString runCmd = targetMap[QStringLiteral("run_cmd")].toString();
 
         if (tgtName.isEmpty() || buildCmd.isEmpty()) {
             continue;
         }
-        m_targetsUi->targetsModel.addCommand(set, tgtName, buildCmd);
+        m_targetsUi->targetsModel.addCommand(set, tgtName, buildCmd, runCmd);
     }
 
     if (!set.model()->index(0, 0, set).data().isValid()) {
@@ -1362,6 +1409,24 @@ void KateBuildView::slotAddProjectTarget()
             m_targetsUi->targetsModel.addCommand(set, i18n("quick"), quickCmd);
         }
     }
+    const auto index = m_targetsUi->proxyModel.mapFromSource(set);
+    if (index.isValid()) {
+        m_targetsUi->targetsView->expand(index);
+    }
+}
+
+void KateBuildView::onSelectionChanged(const QModelIndex &current, const QModelIndex &)
+{
+    if (!current.isValid() || !current.parent().isValid()) {
+        m_targetsUi->buildButton->setEnabled(false);
+        m_targetsUi->runButton->setEnabled(false);
+        return;
+    }
+    const bool hasBuildCmd = !current.siblingAtColumn(1).data().toString().isEmpty();
+    const bool hasRunCmd = !current.siblingAtColumn(2).data().toString().isEmpty();
+    m_targetsUi->buildButton->setEnabled(hasBuildCmd);
+    // Run button can be enabled even if there is no build command
+    m_targetsUi->runButton->setEnabled(hasRunCmd);
 }
 
 /******************************************************************/
diff --git a/addons/katebuild-plugin/plugin_katebuild.h b/addons/katebuild-plugin/plugin_katebuild.h
index 5d9d9ecdc..85cd68c47 100644
--- a/addons/katebuild-plugin/plugin_katebuild.h
+++ b/addons/katebuild-plugin/plugin_katebuild.h
@@ -72,6 +72,7 @@ private Q_SLOTS:
 
     // Building
     void slotSelectTarget();
+    void slotBuildAndRunDefaultTarget();
     void slotBuildActiveTarget();
     void slotBuildPreviousTarget();
     void slotBuildDefaultTarget();
@@ -81,6 +82,7 @@ private Q_SLOTS:
     void slotProcExited(int exitCode, QProcess::ExitStatus exitStatus);
     void slotReadReadyStdErr();
     void slotReadReadyStdOut();
+    void slotRunAfterBuild();
 
     // Selecting warnings/errors
     void slotNext();
@@ -110,6 +112,8 @@ private Q_SLOTS:
     void slotProjectMapChanged();
     void slotAddProjectTarget();
 
+    void onSelectionChanged(const QModelIndex &, const QModelIndex &current);
+
 protected:
     bool eventFilter(QObject *obj, QEvent *ev) override;
 
@@ -140,6 +144,7 @@ private:
     QString m_stdErr;
     QString m_currentlyBuildingTarget;
     bool m_buildCancelled;
+    bool m_runAfterBuild = false;
     int m_displayModeBeforeBuild;
     QString m_make_dir;
     QStack<QString> m_make_dir_stack;
diff --git a/addons/katebuild-plugin/targets.cpp b/addons/katebuild-plugin/targets.cpp
index e572cdd29..6f09237f9 100644
--- a/addons/katebuild-plugin/targets.cpp
+++ b/addons/katebuild-plugin/targets.cpp
@@ -49,6 +49,10 @@ TargetsUi::TargetsUi(QObject *view, QWidget *parent)
     buildButton->setIcon(QIcon::fromTheme(QStringLiteral("run-build")));
     buildButton->setToolTip(i18n("Build selected target"));
 
+    runButton = new QToolButton(this);
+    runButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
+    runButton->setToolTip(i18n("Build & run selected target"));
+
     targetsView = new QTreeView(this);
     targetsView->setAlternatingRowColors(true);
 
@@ -66,7 +70,8 @@ TargetsUi::TargetsUi(QObject *view, QWidget *parent)
     tLayout->addWidget(targetCombo);
     tLayout->addWidget(targetFilterEdit);
     tLayout->addWidget(buildButton);
-    tLayout->addSpacing(20);
+    tLayout->addWidget(runButton);
+    tLayout->addSpacing(15);
     tLayout->addWidget(addButton);
     tLayout->addWidget(newTarget);
     tLayout->addWidget(copyTarget);
diff --git a/addons/katebuild-plugin/targets.h b/addons/katebuild-plugin/targets.h
index e7ecf4cb9..f6d7acf78 100644
--- a/addons/katebuild-plugin/targets.h
+++ b/addons/katebuild-plugin/targets.h
@@ -39,6 +39,7 @@ public:
 
     QToolButton *addButton;
     QToolButton *buildButton;
+    QToolButton *runButton;
 
 public Q_SLOTS:
     void targetSetSelected(int index);
diff --git a/addons/katebuild-plugin/ui.rc b/addons/katebuild-plugin/ui.rc
index 81555cca3..91083606e 100644
--- a/addons/katebuild-plugin/ui.rc
+++ b/addons/katebuild-plugin/ui.rc
@@ -1,12 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE gui SYSTEM "kpartgui.dtd">
-<gui name="katebuild-plugin" library="katebuildplugin" version="11" translationDomain="katebuild-plugin">
+<gui name="katebuild-plugin" library="katebuildplugin" version="12" translationDomain="katebuild-plugin">
   <MenuBar>
     <Menu name="Build Menubar">
       <text>&Build</text>
       <Action name="select_target"/>
       <Action name="build_default_target" />
       <Action name="build_previous_target"/>
+      <Action name="build_and_run_default_target" />
       <Action name="stop"/>
       <Separator/>
       <Action name="goto_prev"/>
diff --git a/addons/project/kateprojectinfoview.cpp b/addons/project/kateprojectinfoview.cpp
index c0fe03763..ce3ff7026 100644
--- a/addons/project/kateprojectinfoview.cpp
+++ b/addons/project/kateprojectinfoview.cpp
@@ -86,3 +86,10 @@ void KateProjectInfoView::resetTerminal(const QString &directory)
         m_terminal->respawn(directory);
     }
 }
+
+void KateProjectInfoView::runCmdInTerminal(const QString &workingDir, const QString &cmd)
+{
+    if (auto terminal = qobject_cast<KateProjectInfoViewTerminal *>(currentWidget())) {
+        terminal->runCommand(workingDir, cmd);
+    }
+}
diff --git a/addons/project/kateprojectinfoview.h b/addons/project/kateprojectinfoview.h
index 526f65388..cedd29551 100644
--- a/addons/project/kateprojectinfoview.h
+++ b/addons/project/kateprojectinfoview.h
@@ -55,6 +55,8 @@ public:
 
     void resetTerminal(const QString &directory);
 
+    void runCmdInTerminal(const QString &workingDir, const QString &cmd);
+
 private:
     /**
      * our plugin view
diff --git a/addons/project/kateprojectinfoviewterminal.cpp b/addons/project/kateprojectinfoviewterminal.cpp
index 1613659b8..8c44fb4fb 100644
--- a/addons/project/kateprojectinfoviewterminal.cpp
+++ b/addons/project/kateprojectinfoviewterminal.cpp
@@ -12,6 +12,7 @@
 #include <KLocalizedString>
 #include <KPluginFactory>
 #include <KSharedConfig>
+#include <KShell>
 #include <kde_terminal_interface.h>
 #include <ktexteditor_utils.h>
 
@@ -188,3 +189,15 @@ bool KateProjectInfoViewTerminal::eventFilter(QObject *w, QEvent *e)
 
     return QWidget::eventFilter(w, e);
 }
+
+void KateProjectInfoViewTerminal::runCommand(const QString &workingDir, const QString &cmd)
+{
+    auto terminal = qobject_cast<TerminalInterface *>(m_konsolePart);
+    if (!terminal) {
+        loadTerminal();
+    }
+    terminal->sendInput(QStringLiteral("\x05\x15"));
+    const QString changeDirCmd = QStringLiteral("cd ") + KShell::quoteArg(workingDir) + QStringLiteral("\n");
+    terminal->sendInput(changeDirCmd);
+    terminal->sendInput(cmd.trimmed() + QStringLiteral("\n"));
+}
diff --git a/addons/project/kateprojectinfoviewterminal.h b/addons/project/kateprojectinfoviewterminal.h
index 224787001..74d225462 100644
--- a/addons/project/kateprojectinfoviewterminal.h
+++ b/addons/project/kateprojectinfoviewterminal.h
@@ -55,6 +55,8 @@ public:
 
     bool eventFilter(QObject *o, QEvent *e) override;
 
+    void runCommand(const QString &workingDir, const QString &cmd);
+
 private Q_SLOTS:
     /**
      * Construct a new terminal for this view
diff --git a/addons/project/kateprojectpluginview.cpp b/addons/project/kateprojectpluginview.cpp
index 702a85f83..37e08add2 100755
--- a/addons/project/kateprojectpluginview.cpp
+++ b/addons/project/kateprojectpluginview.cpp
@@ -575,6 +575,16 @@ void KateProjectPluginView::switchToProject(const QDir &dir)
     }
 }
 
+void KateProjectPluginView::runCmdInTerminal(const QString &workingDir, const QString &cmd)
+{
+    m_mainWindow->showToolView(m_toolInfoView);
+    auto widget = static_cast<KateProjectInfoView *>(m_stackedProjectInfoViews->currentWidget());
+    if (!widget) {
+        return;
+    }
+    widget->runCmdInTerminal(workingDir, cmd);
+}
+
 void KateProjectPluginView::slotViewCreated(KTextEditor::View *view)
 {
     /**
diff --git a/addons/project/kateprojectpluginview.h b/addons/project/kateprojectpluginview.h
index 0e8215ffb..9d8706f33 100755
--- a/addons/project/kateprojectpluginview.h
+++ b/addons/project/kateprojectpluginview.h
@@ -132,6 +132,8 @@ public Q_SLOTS:
      */
     void switchToProject(const QDir &dir);
 
+    void runCmdInTerminal(const QString &workingDir, const QString &cmd);
+
 private Q_SLOTS:
     /**
      * Plugin config updated
diff --git a/doc/kate/plugins.docbook b/doc/kate/plugins.docbook
index 70b9e9306..1769a4cd1 100644
--- a/doc/kate/plugins.docbook
+++ b/doc/kate/plugins.docbook
@@ -2087,6 +2087,7 @@ You can also mix version control and files based on filters.
       {
         "name": "all",
         "build_cmd": "ninja"
+        "run_cmd": "./bin/kate"
       },
       {
         "name": "kate",
@@ -2100,6 +2101,9 @@ You can also mix version control and files based on filters.
 <para>
 The targets specified above will then appear in the <link linkend="kate-application-plugin-build">Build Plugin </link> under <emphasis role="bold">
 "Project Plugin Targets"</emphasis>.
+
+If the <code>"targets"</code> array is specified then <code>"build"</code>, <code>"clean"</code> and <code>"install"</code> are ignored. Each element in the array specifies a target. <code>"name"</code> is the name of the target, <code>"build_cmd"</code> will be used to build the target, <code>"run_cmd"</code> is will be used to run the target. Most important of all is <code> "directory"</code>, this is where the commands will be executed.
+
 </para>
 
 <para>



More information about the kde-doc-english mailing list