File indexing completed on 2024-04-21 15:55:23

0001 /******************************************************************************
0002   Copyright (C) 2006-2017 by Michel Ludwig (michel.ludwig@kdemail.net)
0003                 2011-2012 by Holger Danielsson (holger.danielsson@versanet.de)
0004  ******************************************************************************/
0005 
0006 /**************************************************************************
0007 *                                                                         *
0008 *   This program is free software; you can redistribute it and/or modify  *
0009 *   it under the terms of the GNU General Public License as published by  *
0010 *   the Free Software Foundation; either version 2 of the License, or     *
0011 *   (at your option) any later version.                                   *
0012 *                                                                         *
0013 ***************************************************************************/
0014 
0015 #include "scripting/script.h"
0016 
0017 #include <QFile>
0018 #include <QTextStream>
0019 #include <QScriptValue>
0020 #include <QTimer>
0021 
0022 #include <KActionCollection>
0023 #include <KTextEditor/Range>
0024 #include <KTextEditor/Cursor>
0025 #include <KLocalizedString>
0026 #include <KMessageBox>
0027 
0028 #include <iostream>
0029 
0030 #include "kileinfo.h"
0031 #include "kiledebug.h"
0032 #include "scripting/kilescriptobject.h"
0033 #include "scripting/kilescriptview.h"
0034 #include "scripting/kilescriptdocument.h"
0035 
0036 namespace KileScript {
0037 
0038 /*
0039 // Modified declaration from <khtml/ecma/kjs_proxy.h>
0040 // Acknowledgements go to:
0041 //  Copyright (C) 1999 Harri Porten (porten@kde.org)
0042 //  Copyright (C) 2001 Peter Kelly (pmk@post.com)
0043 
0044 class KJSCPUGuard {
0045 public:
0046   KJSCPUGuard() {}
0047   void start(unsigned int msec=5000, unsigned int i_msec=0);
0048   void stop();
0049 private:
0050   void (*oldAlarmHandler)(int);
0051   static void alarmHandler(int);
0052   itimerval oldtv;
0053 };
0054 
0055 // Modified implementation originating from <khtml/ecma/kjs_proxy.cpp>
0056 // Acknowledgements go to:
0057 // Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
0058 // Copyright (C) 2001,2003 Peter Kelly (pmk@post.com)
0059 // Copyright (C) 2001-2003 David Faure (faure@kde.org)
0060 void KJSCPUGuard::start(unsigned int ms, unsigned int i_ms)
0061 {
0062   oldAlarmHandler = signal(SIGVTALRM, alarmHandler);
0063   itimerval tv = {
0064       { i_ms / 1000, (i_ms % 1000) * 1000 },
0065       { ms / 1000, (ms % 1000) * 1000 }
0066   };
0067   setitimer(ITIMER_VIRTUAL, &tv, &oldtv);
0068 }
0069 
0070 void KJSCPUGuard::stop()
0071 {
0072   setitimer(ITIMER_VIRTUAL, &oldtv, Q_NULLPTR);
0073   signal(SIGVTALRM, oldAlarmHandler);
0074 }
0075 
0076 void KJSCPUGuard::alarmHandler(int) {
0077 
0078 //     KJS::ExecState::requestTerminate();
0079 }
0080 */
0081 
0082 #ifdef __GNUC__
0083 #warning "Fix time limit functionality!"
0084 #endif
0085 
0086 
0087 //BEGIN QtScript conversion functions for Cursors and Ranges
0088 /** Conversion function from KTextEditor::Cursor to QtScript cursor */
0089 static QScriptValue cursorToScriptValue(QScriptEngine *engine, const KTextEditor::Cursor &cursor)
0090 {
0091     QString code = QString("new Cursor(%1, %2);").arg(cursor.line())
0092                    .arg(cursor.column());
0093     return engine->evaluate(code);
0094 }
0095 
0096 /** Conversion function from QtScript cursor to KTextEditor::Cursor */
0097 static void cursorFromScriptValue(const QScriptValue &obj, KTextEditor::Cursor &cursor)
0098 {
0099     cursor.setPosition(obj.property(QStringLiteral("line")).toInt32(),
0100                        obj.property(QStringLiteral("column")).toInt32());
0101 }
0102 
0103 /** Conversion function from QtScript range to KTextEditor::Range */
0104 static QScriptValue rangeToScriptValue(QScriptEngine *engine, const KTextEditor::Range &range)
0105 {
0106     QString code = QString("new Range(%1, %2, %3, %4);").arg(range.start().line())
0107                    .arg(range.start().column())
0108                    .arg(range.end().line())
0109                    .arg(range.end().column());
0110     return engine->evaluate(code);
0111 }
0112 
0113 /** Conversion function from QtScript range to KTextEditor::Range */
0114 static void rangeFromScriptValue(const QScriptValue &obj, KTextEditor::Range &range)
0115 {
0116     range.setStart(KTextEditor::Cursor(obj.property(QStringLiteral("start")).property(QStringLiteral("line")).toInt32(),
0117                                        obj.property(QStringLiteral("start")).property(QStringLiteral("column")).toInt32()));
0118     range.setEnd(KTextEditor::Cursor(obj.property(QStringLiteral("end")).property(QStringLiteral("line")).toInt32(),
0119                                      obj.property(QStringLiteral("end")).property(QStringLiteral("column")).toInt32()));
0120 }
0121 //END
0122 
0123 ////////////////////////////// Script //////////////////////////////
0124 
0125 /* The IDs of the scripts are used to maintain correct bindings with QAction objects, i.e. for example, we
0126  * want to make sure the action script_execution_0 always refers to same script (the script with id 0 !), even
0127  * after reloading all the scripts.
0128  */
0129 
0130 Script::Script(unsigned int id, const QString& file)
0131     : m_id(id), m_file(file), m_action(Q_NULLPTR), m_sequencetype(KEY_SEQUENCE)
0132 {
0133     m_name = QFileInfo(file).fileName();
0134 
0135     if(m_name.endsWith(QLatin1String(".js"))) { // remove the extension
0136         m_name = m_name.left(m_name.length() - 3);
0137     }
0138 }
0139 
0140 QString Script::getCode() const
0141 {
0142     return readFile(m_file);
0143 }
0144 
0145 QString Script::getName() const
0146 {
0147     return m_name;
0148 }
0149 
0150 QString Script::getFileName() const
0151 {
0152     return m_file;
0153 }
0154 
0155 unsigned int Script::getID() const
0156 {
0157     return m_id;
0158 }
0159 
0160 void Script::setID(unsigned int id)
0161 {
0162     m_id = id;
0163 }
0164 
0165 void Script::setActionObject(QAction * action)
0166 {
0167     m_action = action;
0168 }
0169 
0170 // const QAction * Script::getActionObject() const
0171 // {
0172 //  return m_action;
0173 // }
0174 
0175 QAction * Script::getActionObject() const
0176 {
0177     return m_action;
0178 }
0179 
0180 void Script::setKeySequence(const QString& str)
0181 {
0182     m_keySequence = str;
0183 }
0184 
0185 QString Script::getKeySequence() const
0186 {
0187     return m_keySequence;
0188 }
0189 
0190 
0191 int Script::getSequenceType() const
0192 {
0193     return m_sequencetype;
0194 }
0195 
0196 void Script::setSequenceType(int type)
0197 {
0198     m_sequencetype = type;
0199 }
0200 
0201 QString Script::readFile(const QString &filename) {
0202     QFile file(filename);
0203     if ( !file.open(QIODevice::ReadOnly) ) {
0204         KILE_DEBUG_MAIN << i18n("Unable to find '%1'", filename);
0205         return QString();
0206     } else {
0207         QTextStream stream(&file);
0208         stream.setCodec("UTF-8");
0209         QString text = stream.readAll();
0210         file.close();
0211         return text;
0212     }
0213 }
0214 
0215 ////////////////////////////// ScriptEnvironment //////////////////////////////
0216 
0217 ScriptEnvironment::ScriptEnvironment(KileInfo *kileInfo,
0218                                      KileScriptView *scriptView, KileScriptDocument *scriptDocument,
0219                                      KileScriptObject *scriptObject, const QString &pluginCode)
0220     : m_kileInfo(kileInfo), m_scriptView(scriptView), m_scriptDocument(scriptDocument),
0221       m_kileScriptObject(scriptObject), m_enginePluginCode(pluginCode)
0222 {
0223 
0224     KILE_DEBUG_MAIN << "create ScriptEnvironment";
0225     m_engine = new QScriptEngine();
0226     qScriptRegisterMetaType(m_engine, cursorToScriptValue, cursorFromScriptValue);
0227     qScriptRegisterMetaType(m_engine, rangeToScriptValue, rangeFromScriptValue);
0228 }
0229 
0230 ScriptEnvironment::~ScriptEnvironment()
0231 {
0232     delete m_engine;
0233 }
0234 
0235 // Executes script code in this environment.
0236 void ScriptEnvironment::execute(const Script *script)
0237 {
0238     // initialize engine to work with Cursor and Range objects
0239     m_engine->evaluate(m_enginePluginCode, i18n("Cursor/Range plugin"));
0240 
0241     if(m_engine->hasUncaughtException()) {
0242         scriptError(i18n("Cursor/Range plugin"));
0243         return;
0244     }
0245     else {
0246         KILE_DEBUG_MAIN << "Cursor/Range plugin successfully installed ";
0247     }
0248 
0249     // set global objects
0250     if(m_scriptView->view()) {
0251         m_engine->globalObject().setProperty("view", m_engine->newQObject(m_scriptView));
0252         m_engine->globalObject().setProperty("document", m_engine->newQObject(m_scriptDocument));
0253     }
0254     m_engine->globalObject().setProperty("kile", m_engine->newQObject(m_kileScriptObject));
0255 
0256     // export debug function
0257     m_engine->globalObject().setProperty("debug", m_engine->newFunction(KileScript::debug));
0258 
0259     // start engine
0260     m_engine->evaluate(script->getCode());
0261 
0262     // success or error
0263     if(m_engine->hasUncaughtException()) {
0264         scriptError(script->getName());
0265     }
0266     else {
0267         KILE_DEBUG_MAIN << "script finished without errors";
0268     }
0269 
0270 //FIXME: add time execution limit once it becomes available
0271 //          bool useGuard = KileConfig::timeLimitEnabled();
0272 //          uint timeLimit = (uint)KileConfig::timeLimit();
0273 //          KJSCPUGuard guard;
0274 //          if(useGuard) {
0275 //              guard.start(timeLimit*1000);
0276 //          }
0277 //          KJS::Completion completion = m_interpreter->evaluate(QString(), 0, s);
0278 //          if(useGuard) {
0279 //              guard.stop();
0280 //          }
0281     QTimer::singleShot(0, m_scriptView->view(), SLOT(setFocus()));
0282 
0283     // remove global objects
0284     m_engine->globalObject().setProperty("view", QScriptValue());
0285     m_engine->globalObject().setProperty("document", QScriptValue());
0286     m_engine->globalObject().setProperty("kile", QScriptValue());
0287 }
0288 
0289 // Executes script code in this environment.
0290 void ScriptEnvironment::scriptError(const QString &name)
0291 {
0292     int errorline = m_engine->uncaughtExceptionLineNumber();
0293     QScriptValue exception = m_engine->uncaughtException();
0294     QString errormessage = ( exception.isError() ) ? exception.toString() : QString();
0295     QString message = i18n("An error has occurred at line %1 during the execution of the script \"%2\":\n%3", errorline, name, errormessage);
0296     KMessageBox::error(m_kileInfo->mainWindow(), message, i18n("Error"));
0297 }
0298 
0299 ////////////////////////////// ScriptHelpers //////////////////////////////
0300 
0301 QScriptValue debug(QScriptContext *context, QScriptEngine *engine)
0302 {
0303     QStringList message;
0304     for(int i = 0; i < context->argumentCount(); ++i) {
0305         message << context->argument(i).toString();
0306     }
0307     // debug output in blue to distinguish it from other debug output
0308     std::cout << "\033[34m" << qPrintable(message.join(' ')) << "\033[0m\n";
0309     return engine->nullValue();
0310 }
0311 
0312 }