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"