plugin_katetextfilter.cpp 8.74 KB
Newer Older
1 2 3 4
/***************************************************************************
                          plugin_katetextfilter.cpp  -  description
                             -------------------
    begin                : FRE Feb 23 2001
5 6
    copyright            : (C) 2001 by Joseph Wenninger <jowenn@bigfoot.com>
    copyright            : (C) 2009 Dominik Haumann <dhaumann kde org>
7 8 9 10 11 12 13 14 15 16 17 18 19
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "plugin_katetextfilter.h"

20 21
#include "ui_textfilterwidget.h"

22
#include <ktexteditor/editor.h>
23
#include <ktexteditor/message.h>
24

25
#include <QDialog>
26
#include <QAction>
27
#include <kmessagebox.h>
28
#include <klocalizedstring.h>
29 30
#include <qstring.h>
#include <klineedit.h>
31
#include <kprocess.h>
32

33
#include <KPluginFactory>
34
#include <KAuthorized>
35
#include <kactioncollection.h>
36
#include <KXMLGUIFactory>
Christoph Cullmann's avatar
Christoph Cullmann committed
37 38
#include <KConfigGroup>
#include <KSharedConfig>
39 40 41 42

#include <qapplication.h>
#include <qclipboard.h>

43
K_PLUGIN_FACTORY_WITH_JSON(TextFilterPluginFactory, "textfilterplugin.json", registerPlugin<PluginKateTextFilter>();)
44

45 46
PluginKateTextFilter::PluginKateTextFilter(QObject *parent, const QList<QVariant> &):
    KTextEditor::Plugin(parent)
47
{
48 49
    // register command
    new PluginKateTextFilterCommand(this);
50 51 52 53
}

PluginKateTextFilter::~PluginKateTextFilter()
{
Christoph Cullmann's avatar
Christoph Cullmann committed
54 55 56 57 58 59
    // cleanup the process the right way (TM)
    if (m_pFilterProcess) {
        m_pFilterProcess->kill();
        m_pFilterProcess->waitForFinished();
        delete m_pFilterProcess;
    }
60 61
}

62
QObject *PluginKateTextFilter::createView (KTextEditor::MainWindow *mainWindow)
63
{
Christoph Cullmann's avatar
Christoph Cullmann committed
64 65
    // create a plugin view
    return new PluginViewKateTextFilter(this, mainWindow);
66 67
}

68
void PluginKateTextFilter::slotFilterReceivedStdout()
69
{
70
  m_strFilterOutput += QString::fromLocal8Bit(m_pFilterProcess->readAllStandardOutput());
71 72 73
}


74
void PluginKateTextFilter::slotFilterReceivedStderr ()
75
{
Alex Turbov's avatar
Alex Turbov committed
76 77 78 79 80
  const QString block = QString::fromLocal8Bit(m_pFilterProcess->readAllStandardError());
  if (mergeOutput)
    m_strFilterOutput += block;
  else
    m_stderrOutput += block;
81
}
82

Alex Turbov's avatar
Alex Turbov committed
83
void PluginKateTextFilter::slotFilterProcessExited(int, QProcess::ExitStatus)
84
{
85
  KTextEditor::View* kv(KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView());
86
  if (!kv) return;
87

Alex Turbov's avatar
Alex Turbov committed
88 89 90 91
  // Is there any error output to display?
  if (!mergeOutput && !m_stderrOutput.isEmpty())
  {
      QPointer<KTextEditor::Message> message = new KTextEditor::Message(
92
          xi18nc(
Alex Turbov's avatar
Alex Turbov committed
93 94 95 96 97 98 99 100 101
              "@info"
            , "<title>Result of:</title><nl /><pre><code>$ %1\n<nl />%2</code></pre>"
            , m_last_command
            , m_stderrOutput
            )
        , KTextEditor::Message::Error
        );
      message->setWordWrap(true);
      message->setAutoHide(1000);
102
      kv->document()->postMessage(message);
Alex Turbov's avatar
Alex Turbov committed
103 104 105
  }

  if (copyResult) {
106 107 108 109
    QApplication::clipboard()->setText(m_strFilterOutput);
    return;
  }

Alex Turbov's avatar
Alex Turbov committed
110 111 112 113
  // Do not even try to change the document if no result collected...
  if (m_strFilterOutput.isEmpty())
    return;

114
  KTextEditor::Document::EditingTransaction transaction(kv->document());
115

116
  KTextEditor::Cursor start = kv->cursorPosition();
117
  if (kv->selection()) {
118
    start = kv->selectionRange().start();
119 120
    kv->removeSelectionText();
  }
121

122 123
  kv->setCursorPosition(start); // for block selection

124
  kv->insertText(m_strFilterOutput);
125 126 127
}


Laurent Montel's avatar
Laurent Montel committed
128
static void slipInFilter(KProcess & proc, KTextEditor::View & view, const QString &command)
129
{
130 131 132
  QString inputText;

  if (view.selection()) {
Alex Turbov's avatar
Alex Turbov committed
133
    inputText = view.selectionText();
134
  }
135

136 137
  proc.clearProgram ();
  proc.setShellCommand(command);
138

139
  proc.start();
Alex Turbov's avatar
Alex Turbov committed
140
  QByteArray encoded = inputText.toLocal8Bit();
141 142
  proc.write(encoded);
  proc.closeWriteChannel();
143 144 145 146
  //  TODO: Put up a modal dialog to defend the text from further
  //  keystrokes while the command is out. With a cancel button...
}

147
void PluginKateTextFilter::slotEditFilter()
148
{
149
  if (!KAuthorized::authorize(QStringLiteral("shell_access"))) {
150
    KMessageBox::sorry(nullptr,i18n(
151 152 153 154
        "You are not allowed to execute arbitrary external applications. If "
        "you want to be able to do this, contact your system administrator."),
        i18n("Access Restrictions"));
    return;
155
  }
156
  if (!KTextEditor::Editor::instance()->application()->activeMainWindow())
157 158
    return;

159
  KTextEditor::View* kv(KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView());
160 161
  if (!kv) return;

162
  QDialog dialog(KTextEditor::Editor::instance()->application()->activeMainWindow()->window());
163 164

  Ui::TextFilterWidget ui;
Christoph Cullmann's avatar
Christoph Cullmann committed
165
  ui.setupUi(&dialog);
166 167
  ui.filterBox->setFocus();

168 169
  dialog.setWindowTitle(i18n("Text Filter"));

Christoph Cullmann's avatar
Christoph Cullmann committed
170
  KConfigGroup config(KSharedConfig::openConfig(), "PluginTextFilter");
171
  QStringList items = config.readEntry("Completion list", QStringList());
Alex Turbov's avatar
Alex Turbov committed
172 173
  copyResult = config.readEntry("Copy result", false);
  mergeOutput = config.readEntry("Merge output", true);
174 175
  ui.filterBox->setMaxCount(10);
  ui.filterBox->setHistoryItems(items, true);
176
  ui.filterBox->setMinimumContentsLength(80);
Alex Turbov's avatar
Alex Turbov committed
177 178
  ui.copyResult->setChecked(copyResult);
  ui.mergeOutput->setChecked(mergeOutput);
179

180
  if (dialog.exec() == QDialog::Accepted) {
Alex Turbov's avatar
Alex Turbov committed
181 182
    copyResult = ui.copyResult->isChecked();
    mergeOutput = ui.mergeOutput->isChecked();
183 184 185 186
    const QString filter = ui.filterBox->currentText();
    if (!filter.isEmpty()) {
      ui.filterBox->addToHistory(filter);
      config.writeEntry("Completion list", ui.filterBox->historyItems());
Alex Turbov's avatar
Alex Turbov committed
187 188 189
      config.writeEntry("Copy result", copyResult);
      config.writeEntry("Merge output", mergeOutput);
      m_last_command = filter;
190 191 192
      runFilter(kv, filter);
    }
  }
193 194
}

195
void PluginKateTextFilter::runFilter(KTextEditor::View *kv, const QString &filter)
196
{
Alex Turbov's avatar
Alex Turbov committed
197 198
  m_strFilterOutput.clear();
  m_stderrOutput.clear();
199

200
  if (!m_pFilterProcess)
201
  {
202
    m_pFilterProcess = new KProcess;
203

204
    connect(m_pFilterProcess, &KProcess::readyReadStandardOutput, this, &PluginKateTextFilter::slotFilterReceivedStdout);
205

206
    connect(m_pFilterProcess, &KProcess::readyReadStandardError, this, &PluginKateTextFilter::slotFilterReceivedStderr);
207

208
    connect(m_pFilterProcess, static_cast<void (KProcess::*)(int, KProcess::ExitStatus)>(&KProcess::finished), this, &PluginKateTextFilter::slotFilterProcessExited);
209
  }
Alex Turbov's avatar
Alex Turbov committed
210 211 212
  m_pFilterProcess->setOutputChannelMode(
      mergeOutput ? KProcess::MergedChannels : KProcess::SeparateChannels
    );
213

Alex Turbov's avatar
Alex Turbov committed
214
  slipInFilter(*m_pFilterProcess, *kv, filter);
215 216 217
}

//BEGIN Kate::Command methods
218 219 220 221

PluginKateTextFilterCommand::PluginKateTextFilterCommand(PluginKateTextFilter *plugin)
  : KTextEditor::Command(QStringList() << QStringLiteral("textfilter"), plugin)
  , m_plugin(plugin)
222 223 224
{
}

225 226
bool PluginKateTextFilterCommand::exec (KTextEditor::View *view, const QString &cmd, QString &msg,
                      const KTextEditor::Range &)
227
{
228
  QString filter = cmd.section(QLatin1Char(' '), 1).trimmed();
229

230
  if (filter.isEmpty()) {
231 232 233 234
    msg = i18n("Usage: textfilter COMMAND");
    return false;
  }

235
  m_plugin->runFilter(view, filter);
236 237
  return true;
}
238 239 240 241 242 243 244 245

bool PluginKateTextFilterCommand::help (KTextEditor::View *, const QString &, QString &msg)
{
  msg = i18n("<qt><p>Usage: <code>textfilter COMMAND</code></p>"
             "<p>Replace the selection with the output of the specified shell command.</p></qt>");
  return true;

}
246
//END
247

248 249 250 251 252 253

PluginViewKateTextFilter::PluginViewKateTextFilter(PluginKateTextFilter *plugin,
                                                   KTextEditor::MainWindow *mainwindow)
  : QObject(mainwindow)
  , m_mainWindow(mainwindow)
{
Christoph Cullmann's avatar
Christoph Cullmann committed
254 255 256 257 258 259
    // setup right xml gui data
    KXMLGUIClient::setComponentName(QStringLiteral("textfilter"), i18n("Text Filter"));
    setXMLFile(QStringLiteral("ui.rc"));

    // create our one and only action
    QAction* a = actionCollection()->addAction(QStringLiteral("edit_filter"));
260
    a->setText(i18n("&Filter Through Command..."));
Christoph Cullmann's avatar
Christoph Cullmann committed
261
    actionCollection()->setDefaultShortcut(a, Qt::CTRL + Qt::Key_Backslash);
262
    connect(a, &QAction::triggered, plugin, &PluginKateTextFilter::slotEditFilter);
Christoph Cullmann's avatar
Christoph Cullmann committed
263 264 265

    // register us at the UI
    mainwindow->guiFactory()->addClient(this);
266 267 268 269
}

PluginViewKateTextFilter::~PluginViewKateTextFilter()
{
Christoph Cullmann's avatar
Christoph Cullmann committed
270 271
    // remove us from the UI again
    m_mainWindow->guiFactory()->removeClient (this);
272 273
}

274 275
// required for TextFilterPluginFactory vtable
#include "plugin_katetextfilter.moc"