File indexing completed on 2024-05-12 17:09:15

0001 /*
0002     SPDX-FileCopyrightText: 2009 Aaron Seigo <aseigo@kde.org>
0003     SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "interactiveconsole.h"
0009 
0010 #include <QAction>
0011 #include <QActionGroup>
0012 #include <QApplication>
0013 #include <QDBusConnection>
0014 #include <QDBusMessage>
0015 #include <QDateTime>
0016 #include <QElapsedTimer>
0017 #include <QFile>
0018 #include <QFileDialog>
0019 #include <QHBoxLayout>
0020 #include <QIcon>
0021 #include <QLabel>
0022 #include <QMenu>
0023 #include <QSplitter>
0024 #include <QStandardPaths>
0025 #include <QTextBrowser>
0026 #include <QToolButton>
0027 #include <QVBoxLayout>
0028 
0029 #include <KConfigGroup>
0030 #include <KMessageBox>
0031 #include <KPluginFactory>
0032 #include <KSharedConfig>
0033 #include <KShell>
0034 #include <KStandardAction>
0035 #include <KTextEdit>
0036 #include <KTextEditor/ConfigInterface>
0037 #include <KTextEditor/Document>
0038 #include <KTextEditor/View>
0039 #include <KToolBar>
0040 #include <KX11Extras>
0041 #include <klocalizedstring.h>
0042 
0043 #include <KPackage/Package>
0044 #include <KPackage/PackageLoader>
0045 
0046 // TODO:
0047 // interactive help?
0048 static const QString s_autosaveFileName(QStringLiteral("interactiveconsoleautosave.js"));
0049 static const QString s_kwinService = QStringLiteral("org.kde.KWin");
0050 static const QString s_plasmaShellService = QStringLiteral("org.kde.plasmashell");
0051 
0052 InteractiveConsole::InteractiveConsole(ConsoleMode mode, QWidget *parent)
0053     : QDialog(parent)
0054     , m_splitter(new QSplitter(Qt::Vertical, this))
0055     , m_editorPart(nullptr)
0056     , m_editor(nullptr)
0057     , m_output(nullptr)
0058     , m_loadAction(KStandardAction::open(this, SLOT(openScriptFile()), this))
0059     , m_saveAction(KStandardAction::saveAs(this, SLOT(saveScript()), this))
0060     , m_clearAction(KStandardAction::clear(this, SLOT(clearEditor()), this))
0061     , m_executeAction(new QAction(QIcon::fromTheme(QStringLiteral("system-run")), i18n("&Execute"), this))
0062     , m_plasmaAction(new QAction(QIcon::fromTheme(QStringLiteral("plasma")), i18nc("Toolbar Button to switch to Plasma Scripting Mode", "Plasma"), this))
0063     , m_kwinAction(new QAction(QIcon::fromTheme(QStringLiteral("kwin")), i18nc("Toolbar Button to switch to KWin Scripting Mode", "KWin"), this))
0064     , m_snippetsMenu(new QMenu(i18n("Templates"), this))
0065     , m_fileDialog(nullptr)
0066     , m_closeWhenCompleted(false)
0067     , m_mode(mode)
0068 {
0069     addAction(KStandardAction::close(this, SLOT(close()), this));
0070     addAction(m_saveAction);
0071     addAction(m_clearAction);
0072 
0073     setWindowTitle(i18n("Desktop Shell Scripting Console"));
0074     setAttribute(Qt::WA_DeleteOnClose);
0075     // setButtons(QDialog::None);
0076 
0077     QWidget *widget = new QWidget(m_splitter);
0078     QVBoxLayout *editorLayout = new QVBoxLayout(widget);
0079 
0080     QLabel *label = new QLabel(i18n("Editor"), widget);
0081     QFont f = label->font();
0082     f.setBold(true);
0083     label->setFont(f);
0084     editorLayout->addWidget(label);
0085 
0086     connect(m_snippetsMenu, &QMenu::aboutToShow, this, &InteractiveConsole::populateTemplatesMenu);
0087 
0088     QToolButton *loadTemplateButton = new QToolButton(this);
0089     loadTemplateButton->setPopupMode(QToolButton::InstantPopup);
0090     loadTemplateButton->setMenu(m_snippetsMenu);
0091     loadTemplateButton->setText(i18n("Load"));
0092     connect(loadTemplateButton, &QToolButton::triggered, this, &InteractiveConsole::loadTemplate);
0093 
0094     QToolButton *useTemplateButton = new QToolButton(this);
0095     useTemplateButton->setPopupMode(QToolButton::InstantPopup);
0096     useTemplateButton->setMenu(m_snippetsMenu);
0097     useTemplateButton->setText(i18n("Use"));
0098     connect(useTemplateButton, &QToolButton::triggered, this, &InteractiveConsole::useTemplate);
0099 
0100     QActionGroup *modeGroup = new QActionGroup(this);
0101     modeGroup->addAction(m_plasmaAction);
0102     modeGroup->addAction(m_kwinAction);
0103     m_plasmaAction->setCheckable(true);
0104     m_kwinAction->setCheckable(true);
0105     m_kwinAction->setChecked(mode == KWinConsole);
0106     m_plasmaAction->setChecked(mode == PlasmaConsole);
0107     connect(modeGroup, &QActionGroup::triggered, this, &InteractiveConsole::modeSelectionChanged);
0108 
0109     KToolBar *toolBar = new KToolBar(this, true, false);
0110     toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
0111     toolBar->addAction(m_loadAction);
0112     toolBar->addAction(m_saveAction);
0113     toolBar->addAction(m_clearAction);
0114     toolBar->addAction(m_executeAction);
0115     toolBar->addAction(m_plasmaAction);
0116     toolBar->addAction(m_kwinAction);
0117     toolBar->addWidget(loadTemplateButton);
0118     toolBar->addWidget(useTemplateButton);
0119 
0120     editorLayout->addWidget(toolBar);
0121 
0122     auto tryLoadingKatePart = [=]() -> KTextEditor::Document * {
0123         const auto loadResult =
0124             KPluginFactory::instantiatePlugin<KTextEditor::Document>(KPluginMetaData(QStringLiteral("kf" QT_STRINGIFY(QT_VERSION_MAJOR) "/parts/katepart")),
0125                                                                      this);
0126         if (!loadResult) {
0127             qWarning() << "Error loading katepart plugin:" << loadResult.errorString;
0128             return nullptr;
0129         }
0130 
0131         KTextEditor::Document *result = loadResult.plugin;
0132 
0133         result->setHighlightingMode(QStringLiteral("JavaScript/PlasmaDesktop"));
0134 
0135         KTextEditor::View *view = result->createView(widget);
0136         view->setContextMenu(view->defaultContextMenu());
0137 
0138         KTextEditor::ConfigInterface *config = qobject_cast<KTextEditor::ConfigInterface *>(view);
0139         if (config) {
0140             config->setConfigValue(QStringLiteral("line-numbers"), true);
0141             config->setConfigValue(QStringLiteral("dynamic-word-wrap"), true);
0142         }
0143 
0144         editorLayout->addWidget(view);
0145         connect(result, &KTextEditor::Document::textChanged, this, &InteractiveConsole::scriptTextChanged);
0146 
0147         return result;
0148     };
0149 
0150     m_editorPart = tryLoadingKatePart();
0151 
0152     if (!m_editorPart) {
0153         m_editor = new KTextEdit(widget);
0154         editorLayout->addWidget(m_editor);
0155         connect(m_editor, &QTextEdit::textChanged, this, &InteractiveConsole::scriptTextChanged);
0156     }
0157 
0158     m_splitter->addWidget(widget);
0159 
0160     widget = new QWidget(m_splitter);
0161     QVBoxLayout *outputLayout = new QVBoxLayout(widget);
0162 
0163     label = new QLabel(i18n("Output"), widget);
0164     f = label->font();
0165     f.setBold(true);
0166     label->setFont(f);
0167     outputLayout->addWidget(label);
0168 
0169     KToolBar *outputToolBar = new KToolBar(widget, true, false);
0170     outputToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
0171     QAction *clearOutputAction = KStandardAction::clear(this, SLOT(clearOutput()), this);
0172     outputToolBar->addAction(clearOutputAction);
0173     outputLayout->addWidget(outputToolBar);
0174 
0175     m_output = new QTextBrowser(widget);
0176     outputLayout->addWidget(m_output);
0177     m_splitter->addWidget(widget);
0178 
0179     QVBoxLayout *l = new QVBoxLayout(this);
0180     l->addWidget(m_splitter);
0181 
0182     KConfigGroup cg(KSharedConfig::openConfig(), "InteractiveConsole");
0183     restoreGeometry(cg.readEntry<QByteArray>("Geometry", QByteArray()));
0184 
0185     m_splitter->setStretchFactor(0, 10);
0186     m_splitter->restoreState(cg.readEntry("SplitterState", QByteArray()));
0187 
0188     scriptTextChanged();
0189 
0190     connect(m_executeAction, &QAction::triggered, this, &InteractiveConsole::evaluateScript);
0191     m_executeAction->setShortcut(Qt::CTRL | Qt::Key_E);
0192 
0193     const QString autosave = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/" + s_autosaveFileName;
0194     if (QFile::exists(autosave)) {
0195         loadScript(autosave);
0196     }
0197 }
0198 
0199 InteractiveConsole::~InteractiveConsole()
0200 {
0201     KConfigGroup cg(KSharedConfig::openConfig(), "InteractiveConsole");
0202     cg.writeEntry("Geometry", saveGeometry());
0203     cg.writeEntry("SplitterState", m_splitter->saveState());
0204 }
0205 
0206 void InteractiveConsole::setMode(const QString &mode)
0207 {
0208     if (mode.toLower() == QLatin1String("desktop")) {
0209         m_plasmaAction->trigger();
0210     } else if (mode.toLower() == QLatin1String("windowmanager")) {
0211         m_kwinAction->trigger();
0212     }
0213 }
0214 
0215 void InteractiveConsole::modeSelectionChanged()
0216 {
0217     if (m_plasmaAction->isChecked()) {
0218         m_mode = PlasmaConsole;
0219     } else if (m_kwinAction->isChecked()) {
0220         m_mode = KWinConsole;
0221     }
0222 
0223     Q_EMIT modeChanged();
0224 }
0225 
0226 QString InteractiveConsole::mode() const
0227 {
0228     if (m_mode == KWinConsole) {
0229         return QStringLiteral("windowmanager");
0230     }
0231 
0232     return QStringLiteral("desktop");
0233 }
0234 
0235 void InteractiveConsole::setScriptInterface(QObject *obj)
0236 {
0237     if (m_scriptEngine != obj) {
0238         if (m_scriptEngine) {
0239             disconnect(m_scriptEngine, nullptr, this, nullptr);
0240         }
0241 
0242         m_scriptEngine = obj;
0243         connect(m_scriptEngine, SIGNAL(print(QString)), this, SLOT(print(QString)));
0244         connect(m_scriptEngine, SIGNAL(printError(QString)), this, SLOT(print(QString)));
0245         Q_EMIT scriptEngineChanged();
0246     }
0247 }
0248 
0249 QObject *InteractiveConsole::scriptEngine() const
0250 {
0251     return m_scriptEngine;
0252 }
0253 
0254 void InteractiveConsole::loadScript(const QString &script)
0255 {
0256     if (m_editorPart) {
0257         m_editorPart->closeUrl(false);
0258         if (m_editorPart->openUrl(QUrl::fromLocalFile(script))) {
0259             m_editorPart->setHighlightingMode(QStringLiteral("JavaScript/PlasmaDesktop"));
0260             return;
0261         }
0262     } else {
0263         QFile file(KShell::tildeExpand(script));
0264         if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
0265             m_editor->setText(file.readAll());
0266             return;
0267         }
0268     }
0269 
0270     m_output->append(i18n("Unable to load script file <b>%1</b>", script));
0271 }
0272 
0273 void InteractiveConsole::showEvent(QShowEvent *)
0274 {
0275     if (m_editorPart) {
0276         m_editorPart->views().constFirst()->setFocus();
0277     } else {
0278         m_editor->setFocus();
0279     }
0280 
0281     KX11Extras::setOnDesktop(winId(), KX11Extras::currentDesktop());
0282     Q_EMIT visibleChanged(true);
0283 }
0284 
0285 void InteractiveConsole::closeEvent(QCloseEvent *event)
0286 {
0287     // need to save first!
0288     const QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/" + s_autosaveFileName;
0289     m_closeWhenCompleted = true;
0290     saveScript(QUrl::fromLocalFile(path));
0291     QDialog::closeEvent(event);
0292     Q_EMIT visibleChanged(false);
0293 }
0294 
0295 void InteractiveConsole::reject()
0296 {
0297     QDialog::reject();
0298     close();
0299 }
0300 
0301 void InteractiveConsole::print(const QString &string)
0302 {
0303     m_output->append(string);
0304 }
0305 
0306 void InteractiveConsole::scriptTextChanged()
0307 {
0308     const bool enable = m_editorPart ? !m_editorPart->isEmpty() : !m_editor->document()->isEmpty();
0309     m_saveAction->setEnabled(enable);
0310     m_clearAction->setEnabled(enable);
0311     m_executeAction->setEnabled(enable);
0312 }
0313 
0314 void InteractiveConsole::openScriptFile()
0315 {
0316     delete m_fileDialog;
0317 
0318     m_fileDialog = new QFileDialog();
0319     m_fileDialog->setAcceptMode(QFileDialog::AcceptOpen);
0320     m_fileDialog->setWindowTitle(i18n("Open Script File"));
0321 
0322     QStringList mimetypes;
0323     mimetypes << QStringLiteral("application/javascript");
0324     m_fileDialog->setMimeTypeFilters(mimetypes);
0325 
0326     connect(m_fileDialog, &QDialog::finished, this, &InteractiveConsole::openScriptUrlSelected);
0327     m_fileDialog->show();
0328 }
0329 
0330 void InteractiveConsole::openScriptUrlSelected(int result)
0331 {
0332     if (!m_fileDialog) {
0333         return;
0334     }
0335 
0336     if (result == QDialog::Accepted) {
0337         const QUrl url = m_fileDialog->selectedUrls().constFirst();
0338         if (!url.isEmpty()) {
0339             loadScriptFromUrl(url);
0340         }
0341     }
0342 
0343     m_fileDialog->deleteLater();
0344     m_fileDialog = nullptr;
0345 }
0346 
0347 void InteractiveConsole::loadScriptFromUrl(const QUrl &url)
0348 {
0349     if (m_editorPart) {
0350         m_editorPart->closeUrl(false);
0351         m_editorPart->openUrl(url);
0352         m_editorPart->setHighlightingMode(QStringLiteral("JavaScript/PlasmaDesktop"));
0353     } else {
0354         m_editor->clear();
0355         m_editor->setEnabled(false);
0356 
0357         if (m_job) {
0358             m_job.data()->kill();
0359         }
0360 
0361         auto job = KIO::get(url, KIO::Reload, KIO::HideProgressInfo);
0362         connect(job, &KIO::TransferJob::data, this, &InteractiveConsole::scriptFileDataRecvd);
0363         connect(job, &KJob::result, this, &InteractiveConsole::reenableEditor);
0364         m_job = job;
0365     }
0366 }
0367 
0368 void InteractiveConsole::populateTemplatesMenu()
0369 {
0370     m_snippetsMenu->clear();
0371     auto templates = KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/LayoutTemplate"), QString(), [](const KPluginMetaData &metaData) {
0372         return metaData.value(QStringLiteral("X-Plasma-Shell")) == qApp->applicationName();
0373     });
0374     std::sort(templates.begin(), templates.end(), [](const KPluginMetaData &left, const KPluginMetaData &right) {
0375         return left.name() < right.name();
0376     });
0377     KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate"));
0378     for (const auto &templateMetaData : qAsConst(templates)) {
0379         package.setPath(templateMetaData.pluginId());
0380         const QString scriptFile = package.filePath("mainscript");
0381         if (!scriptFile.isEmpty()) {
0382             QAction *action = m_snippetsMenu->addAction(templateMetaData.name());
0383             action->setData(templateMetaData.pluginId());
0384         }
0385     }
0386 }
0387 
0388 void InteractiveConsole::loadTemplate(QAction *action)
0389 {
0390     KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LayoutTemplate"), action->data().toString());
0391     const QString scriptFile = package.filePath("mainscript");
0392     if (!scriptFile.isEmpty()) {
0393         loadScriptFromUrl(QUrl::fromLocalFile(scriptFile));
0394     }
0395 }
0396 
0397 void InteractiveConsole::useTemplate(QAction *action)
0398 {
0399     QString code("var template = loadTemplate('" + action->data().toString() + "')");
0400     if (m_editorPart) {
0401         const QList<KTextEditor::View *> views = m_editorPart->views();
0402         if (views.isEmpty()) {
0403             m_editorPart->insertLines(m_editorPart->lines(), QStringList() << code);
0404         } else {
0405             KTextEditor::Cursor cursor = views.at(0)->cursorPosition();
0406             m_editorPart->insertLines(cursor.line(), QStringList() << code);
0407             cursor.setLine(cursor.line() + 1);
0408             views.at(0)->setCursorPosition(cursor);
0409         }
0410     } else {
0411         m_editor->insertPlainText(code);
0412     }
0413 }
0414 
0415 void InteractiveConsole::scriptFileDataRecvd(KIO::Job *job, const QByteArray &data)
0416 {
0417     Q_ASSERT(m_editor);
0418 
0419     if (job == m_job.data()) {
0420         m_editor->insertPlainText(data);
0421     }
0422 }
0423 
0424 void InteractiveConsole::saveScript()
0425 {
0426     if (m_editorPart) {
0427         m_editorPart->documentSaveAs();
0428         return;
0429     }
0430 
0431     delete m_fileDialog;
0432 
0433     m_fileDialog = new QFileDialog();
0434     m_fileDialog->setAcceptMode(QFileDialog::AcceptSave);
0435     m_fileDialog->setWindowTitle(i18n("Save Script File"));
0436 
0437     QStringList mimetypes;
0438     mimetypes << QStringLiteral("application/javascript") << QStringLiteral("text/javascript");
0439     m_fileDialog->setMimeTypeFilters(mimetypes);
0440 
0441     connect(m_fileDialog, &QDialog::finished, this, &InteractiveConsole::saveScriptUrlSelected);
0442     m_fileDialog->show();
0443 }
0444 
0445 void InteractiveConsole::saveScriptUrlSelected(int result)
0446 {
0447     if (!m_fileDialog) {
0448         return;
0449     }
0450 
0451     if (result == QDialog::Accepted) {
0452         const QUrl url = m_fileDialog->selectedUrls().constFirst();
0453         if (!url.isEmpty()) {
0454             saveScript(url);
0455         }
0456     }
0457 
0458     m_fileDialog->deleteLater();
0459     m_fileDialog = nullptr;
0460 }
0461 
0462 void InteractiveConsole::saveScript(const QUrl &url)
0463 {
0464     // create the folder to save if doesn't exists
0465     QFileInfo info(url.path());
0466     QDir dir;
0467     dir.mkpath(info.absoluteDir().absolutePath());
0468 
0469     if (m_editorPart) {
0470         m_editorPart->saveAs(url);
0471     } else {
0472         m_editor->setEnabled(false);
0473 
0474         if (m_job) {
0475             m_job.data()->kill();
0476         }
0477 
0478         auto job = KIO::put(url, -1, KIO::HideProgressInfo);
0479         connect(job, &KIO::TransferJob::dataReq, this, &InteractiveConsole::scriptFileDataReq);
0480         connect(job, &KJob::result, this, &InteractiveConsole::reenableEditor);
0481         m_job = job;
0482     }
0483 }
0484 
0485 void InteractiveConsole::scriptFileDataReq(KIO::Job *job, QByteArray &data)
0486 {
0487     Q_ASSERT(m_editor);
0488 
0489     if (!m_job || m_job.data() != job) {
0490         return;
0491     }
0492 
0493     data.append(m_editor->toPlainText().toLocal8Bit());
0494     m_job.clear();
0495 }
0496 
0497 void InteractiveConsole::reenableEditor(KJob *job)
0498 {
0499     Q_ASSERT(m_editor);
0500     if (m_closeWhenCompleted && job->error() != 0) {
0501         close();
0502     }
0503 
0504     m_closeWhenCompleted = false;
0505     m_editor->setEnabled(true);
0506 }
0507 
0508 void InteractiveConsole::evaluateScript()
0509 {
0510     // qDebug() << "evaluating" << m_editor->toPlainText();
0511     m_output->moveCursor(QTextCursor::End);
0512     QTextCursor cursor = m_output->textCursor();
0513     m_output->setTextCursor(cursor);
0514 
0515     QTextCharFormat format;
0516     format.setFontWeight(QFont::Bold);
0517     format.setFontUnderline(true);
0518 
0519     if (cursor.position() > 0) {
0520         cursor.insertText(QStringLiteral("\n\n"));
0521     }
0522 
0523     QDateTime dt = QDateTime::currentDateTime();
0524     cursor.insertText(i18n("Executing script at %1", QLocale().toString(dt)));
0525 
0526     format.setFontWeight(QFont::Normal);
0527     format.setFontUnderline(false);
0528     QTextBlockFormat block = cursor.blockFormat();
0529     block.setLeftMargin(10);
0530     cursor.insertBlock(block, format);
0531     QElapsedTimer t;
0532     t.start();
0533 
0534     if (m_mode == PlasmaConsole) {
0535         QDBusMessage message = QDBusMessage::createMethodCall(s_plasmaShellService,
0536                                                               QStringLiteral("/PlasmaShell"),
0537                                                               QStringLiteral("org.kde.PlasmaShell"),
0538                                                               QStringLiteral("evaluateScript"));
0539         QList<QVariant> arguments;
0540         arguments << QVariant(m_editorPart->text());
0541         message.setArguments(arguments);
0542         QDBusMessage reply = QDBusConnection::sessionBus().call(message);
0543         if (reply.type() == QDBusMessage::ErrorMessage) {
0544             print(reply.errorMessage());
0545         } else {
0546             print(reply.arguments().first().toString());
0547         }
0548     } else if (m_mode == KWinConsole) {
0549         const QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/" + s_autosaveFileName;
0550         saveScript(QUrl::fromLocalFile(path));
0551 
0552         QDBusMessage message = QDBusMessage::createMethodCall(s_kwinService, QStringLiteral("/Scripting"), QString(), QStringLiteral("loadScript"));
0553         QList<QVariant> arguments;
0554         arguments << QVariant(path);
0555         message.setArguments(arguments);
0556         QDBusMessage reply = QDBusConnection::sessionBus().call(message);
0557         if (reply.type() == QDBusMessage::ErrorMessage) {
0558             print(reply.errorMessage());
0559         } else {
0560             const int id = reply.arguments().constFirst().toInt();
0561             QDBusConnection::sessionBus().connect(s_kwinService, "/" + QString::number(id), QString(), QStringLiteral("print"), this, SLOT(print(QString)));
0562             QDBusConnection::sessionBus()
0563                 .connect(s_kwinService, "/" + QString::number(id), QString(), QStringLiteral("printError"), this, SLOT(print(QString)));
0564             message = QDBusMessage::createMethodCall(s_kwinService, "/" + QString::number(id), QString(), QStringLiteral("run"));
0565             reply = QDBusConnection::sessionBus().call(message);
0566             if (reply.type() == QDBusMessage::ErrorMessage) {
0567                 print(reply.errorMessage());
0568             }
0569         }
0570     }
0571 
0572     cursor.insertText(QStringLiteral("\n\n"));
0573     format.setFontWeight(QFont::Bold);
0574     // xgettext:no-c-format
0575     cursor.insertText(i18n("Runtime: %1ms", QString::number(t.elapsed())), format);
0576     block.setLeftMargin(0);
0577     cursor.insertBlock(block);
0578     m_output->ensureCursorVisible();
0579 }
0580 
0581 void InteractiveConsole::clearEditor()
0582 {
0583     if (m_editorPart) {
0584         m_editorPart->clear();
0585     } else {
0586         m_editor->clear();
0587     }
0588 }
0589 
0590 void InteractiveConsole::clearOutput()
0591 {
0592     m_output->clear();
0593 }