File indexing completed on 2024-05-05 05:52:22

0001 /***************************************************************************
0002                           plugin_katetextfilter.cpp  -  description
0003                              -------------------
0004     begin                : FRE Feb 23 2001
0005     copyright            : (C) 2001 by Joseph Wenninger <jowenn@bigfoot.com>
0006     copyright            : (C) 2009 Dominik Haumann <dhaumann kde org>
0007  ***************************************************************************/
0008 
0009 /***************************************************************************
0010  *                                                                         *
0011  *   SPDX-License-Identifier: GPL-2.0-or-later
0012  *                                                                         *
0013  ***************************************************************************/
0014 
0015 #include "plugin_katetextfilter.h"
0016 
0017 #include "ui_textfilterwidget.h"
0018 
0019 #include <ktexteditor/editor.h>
0020 #include <ktexteditor/message.h>
0021 
0022 #include <KLocalizedString>
0023 #include <KMessageBox>
0024 #include <QAction>
0025 #include <QDialog>
0026 #include <QString>
0027 
0028 #include <KActionCollection>
0029 #include <KAuthorized>
0030 #include <KConfigGroup>
0031 #include <KPluginFactory>
0032 #include <KSharedConfig>
0033 #include <KXMLGUIFactory>
0034 
0035 #include <QApplication>
0036 #include <QClipboard>
0037 
0038 K_PLUGIN_FACTORY_WITH_JSON(TextFilterPluginFactory, "textfilterplugin.json", registerPlugin<PluginKateTextFilter>();)
0039 
0040 PluginKateTextFilter::PluginKateTextFilter(QObject *parent, const QVariantList &)
0041     : KTextEditor::Plugin(parent)
0042 {
0043     // register command
0044     new PluginKateTextFilterCommand(this);
0045 }
0046 
0047 PluginKateTextFilter::~PluginKateTextFilter()
0048 {
0049     // cleanup the process the right way (TM)
0050     if (m_pFilterProcess) {
0051         m_pFilterProcess->kill();
0052         m_pFilterProcess->waitForFinished();
0053         delete m_pFilterProcess;
0054     }
0055 }
0056 
0057 QObject *PluginKateTextFilter::createView(KTextEditor::MainWindow *mainWindow)
0058 {
0059     m_mainWindow = mainWindow;
0060     // create a plugin view
0061     return new PluginViewKateTextFilter(this, mainWindow);
0062 }
0063 
0064 void PluginKateTextFilter::slotFilterReceivedStdout()
0065 {
0066     m_strFilterOutput += QString::fromLocal8Bit(m_pFilterProcess->readAllStandardOutput());
0067 }
0068 
0069 void PluginKateTextFilter::slotFilterReceivedStderr()
0070 {
0071     const QString block = QString::fromLocal8Bit(m_pFilterProcess->readAllStandardError());
0072     if (mergeOutput) {
0073         m_strFilterOutput += block;
0074     } else {
0075         m_stderrOutput += block;
0076     }
0077 }
0078 
0079 void PluginKateTextFilter::slotFilterProcessExited(int, QProcess::ExitStatus)
0080 {
0081     KTextEditor::View *kv(KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView());
0082     if (!kv) {
0083         return;
0084     }
0085 
0086     // Is there any error output to display?
0087     if (!mergeOutput && !m_stderrOutput.isEmpty()) {
0088         QPointer<KTextEditor::Message> message =
0089             new KTextEditor::Message(xi18nc("@info", "<title>Result of:</title><nl /><pre><code>$ %1\n<nl />%2</code></pre>", m_last_command, m_stderrOutput),
0090                                      KTextEditor::Message::Error);
0091         message->setWordWrap(true);
0092         message->setAutoHide(1000);
0093         kv->document()->postMessage(message);
0094     }
0095 
0096     if (newDocument) {
0097         auto v = m_mainWindow->openUrl(QUrl());
0098         if (v && v->document()) {
0099             v->document()->setText(m_strFilterOutput);
0100         }
0101         return;
0102     }
0103 
0104     if (copyResult) {
0105         QApplication::clipboard()->setText(m_strFilterOutput);
0106         return;
0107     }
0108 
0109     // Do not even try to change the document if no result collected...
0110     if (m_strFilterOutput.isEmpty()) {
0111         return;
0112     }
0113 
0114     KTextEditor::Document::EditingTransaction transaction(kv->document());
0115 
0116     KTextEditor::Cursor start = kv->cursorPosition();
0117     if (kv->selection()) {
0118         start = kv->selectionRange().start();
0119         kv->removeSelectionText();
0120     }
0121 
0122     kv->setCursorPosition(start); // for block selection
0123 
0124     kv->insertText(m_strFilterOutput);
0125 }
0126 
0127 static void slipInFilter(KProcess &proc, KTextEditor::View &view, const QString &command)
0128 {
0129     QString inputText;
0130 
0131     if (view.selection()) {
0132         inputText = view.selectionText();
0133     } else {
0134         inputText = view.document()->text();
0135     }
0136 
0137     proc.clearProgram();
0138     proc.setShellCommand(command);
0139 
0140     proc.start();
0141     QByteArray encoded = inputText.toLocal8Bit();
0142     proc.write(encoded);
0143     proc.closeWriteChannel();
0144     //  TODO: Put up a modal dialog to defend the text from further
0145     //  keystrokes while the command is out. With a cancel button...
0146 }
0147 
0148 void PluginKateTextFilter::slotEditFilter()
0149 {
0150     if (!KAuthorized::authorize(QStringLiteral("shell_access"))) {
0151         KMessageBox::error(nullptr,
0152                            i18n("You are not allowed to execute arbitrary external applications. If "
0153                                 "you want to be able to do this, contact your system administrator."),
0154                            i18n("Access Restrictions"));
0155         return;
0156     }
0157     if (!KTextEditor::Editor::instance()->application()->activeMainWindow()) {
0158         return;
0159     }
0160 
0161     KTextEditor::View *kv(KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView());
0162     if (!kv) {
0163         return;
0164     }
0165 
0166     QDialog dialog(KTextEditor::Editor::instance()->application()->activeMainWindow()->window());
0167 
0168     Ui::TextFilterWidget ui;
0169     ui.setupUi(&dialog);
0170     ui.filterBox->setFocus();
0171 
0172     dialog.setWindowTitle(i18n("Text Filter"));
0173 
0174     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("PluginTextFilter"));
0175     QStringList items = config.readEntry("Completion list", QStringList());
0176     copyResult = config.readEntry("Copy result", false);
0177     mergeOutput = config.readEntry("Merge output", true);
0178     newDocument = config.readEntry("New Document", false);
0179     ui.filterBox->setMaxCount(10);
0180     ui.filterBox->setHistoryItems(items, true);
0181     ui.filterBox->setMinimumContentsLength(80);
0182     ui.copyResult->setChecked(copyResult);
0183     ui.mergeOutput->setChecked(mergeOutput);
0184     ui.newDoc->setChecked(newDocument);
0185 
0186     if (dialog.exec() == QDialog::Accepted) {
0187         copyResult = ui.copyResult->isChecked();
0188         mergeOutput = ui.mergeOutput->isChecked();
0189         newDocument = ui.newDoc->isChecked();
0190         const QString filter = ui.filterBox->currentText();
0191         if (!filter.isEmpty()) {
0192             ui.filterBox->addToHistory(filter);
0193             config.writeEntry("New Document", newDocument);
0194             config.writeEntry("Completion list", ui.filterBox->historyItems());
0195             config.writeEntry("Copy result", copyResult);
0196             config.writeEntry("Merge output", mergeOutput);
0197             m_last_command = filter;
0198             runFilter(kv, filter);
0199         }
0200     }
0201 }
0202 
0203 void PluginKateTextFilter::runFilter(KTextEditor::View *kv, const QString &filter)
0204 {
0205     m_strFilterOutput.clear();
0206     m_stderrOutput.clear();
0207 
0208     if (!m_pFilterProcess) {
0209         m_pFilterProcess = new KProcess;
0210 
0211         connect(m_pFilterProcess, &KProcess::readyReadStandardOutput, this, &PluginKateTextFilter::slotFilterReceivedStdout);
0212 
0213         connect(m_pFilterProcess, &KProcess::readyReadStandardError, this, &PluginKateTextFilter::slotFilterReceivedStderr);
0214 
0215         connect(m_pFilterProcess,
0216                 static_cast<void (KProcess::*)(int, KProcess::ExitStatus)>(&KProcess::finished),
0217                 this,
0218                 &PluginKateTextFilter::slotFilterProcessExited);
0219     }
0220     m_pFilterProcess->setOutputChannelMode(mergeOutput ? KProcess::MergedChannels : KProcess::SeparateChannels);
0221 
0222     slipInFilter(*m_pFilterProcess, *kv, filter);
0223 }
0224 
0225 // BEGIN Kate::Command methods
0226 
0227 PluginKateTextFilterCommand::PluginKateTextFilterCommand(PluginKateTextFilter *plugin)
0228     : KTextEditor::Command(QStringList() << QStringLiteral("textfilter"), plugin)
0229     , m_plugin(plugin)
0230 {
0231 }
0232 
0233 bool PluginKateTextFilterCommand::exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &)
0234 {
0235     QString filter = cmd.section(QLatin1Char(' '), 1).trimmed();
0236 
0237     if (filter.isEmpty()) {
0238         msg = i18n("Usage: textfilter COMMAND");
0239         return false;
0240     }
0241 
0242     m_plugin->runFilter(view, filter);
0243     return true;
0244 }
0245 
0246 bool PluginKateTextFilterCommand::help(KTextEditor::View *, const QString &, QString &msg)
0247 {
0248     msg = i18n(
0249         "<qt><p>Usage: <code>textfilter COMMAND</code></p>"
0250         "<p>Replace the selection with the output of the specified shell command.</p></qt>");
0251     return true;
0252 }
0253 // END
0254 
0255 PluginViewKateTextFilter::PluginViewKateTextFilter(PluginKateTextFilter *plugin, KTextEditor::MainWindow *mainwindow)
0256     : QObject(mainwindow)
0257     , m_mainWindow(mainwindow)
0258 {
0259     // setup right xml gui data
0260     KXMLGUIClient::setComponentName(QStringLiteral("textfilter"), i18n("Text Filter"));
0261     setXMLFile(QStringLiteral("ui.rc"));
0262 
0263     // create our one and only action
0264     QAction *a = actionCollection()->addAction(QStringLiteral("edit_filter"));
0265     a->setText(i18n("&Filter Through Command..."));
0266     actionCollection()->setDefaultShortcut(a, Qt::CTRL | Qt::Key_Backslash);
0267     connect(a, &QAction::triggered, plugin, &PluginKateTextFilter::slotEditFilter);
0268 
0269     // register us at the UI
0270     mainwindow->guiFactory()->addClient(this);
0271 }
0272 
0273 PluginViewKateTextFilter::~PluginViewKateTextFilter()
0274 {
0275     // remove us from the UI again
0276     m_mainWindow->guiFactory()->removeClient(this);
0277 }
0278 
0279 // required for TextFilterPluginFactory vtable
0280 #include "plugin_katetextfilter.moc"
0281 
0282 #include "moc_plugin_katetextfilter.cpp"