From 72ec238edeb3ff50ac9e6d0e33e383ee09350aa2 Mon Sep 17 00:00:00 2001 From: Martyn Gigg <martyn.gigg@stfc.ac.uk> Date: Thu, 12 Apr 2012 09:19:16 +0100 Subject: [PATCH] Fix the script error reporting. Refs #5006 --- Code/Mantid/MantidPlot/CMakeLists.txt | 2 + Code/Mantid/MantidPlot/src/PythonScript.cpp | 467 +++++++++--------- Code/Mantid/MantidPlot/src/PythonScript.h | 30 +- Code/Mantid/MantidPlot/src/Script.cpp | 39 +- Code/Mantid/MantidPlot/src/Script.h | 29 +- Code/Mantid/MantidPlot/src/ScriptCode.cpp | 44 ++ Code/Mantid/MantidPlot/src/ScriptCode.h | 39 ++ .../MantidPlot/src/ScriptFileInterpreter.cpp | 10 +- .../MantidPlot/src/ScriptFileInterpreter.h | 2 +- Code/Mantid/MantidPlot/src/muParserScript.cpp | 8 +- Code/Mantid/MantidPlot/src/muParserScript.h | 8 +- .../inc/MantidQtMantidWidgets/ScriptEditor.h | 4 + .../MantidWidgets/src/ScriptEditor.cpp | 11 +- 13 files changed, 434 insertions(+), 259 deletions(-) create mode 100644 Code/Mantid/MantidPlot/src/ScriptCode.cpp create mode 100644 Code/Mantid/MantidPlot/src/ScriptCode.h diff --git a/Code/Mantid/MantidPlot/CMakeLists.txt b/Code/Mantid/MantidPlot/CMakeLists.txt index 7aeed1ab030..045bdb959e3 100644 --- a/Code/Mantid/MantidPlot/CMakeLists.txt +++ b/Code/Mantid/MantidPlot/CMakeLists.txt @@ -100,6 +100,7 @@ set ( QTIPLOT_SRCS src/ApplicationWindow.cpp src/ScalePicker.cpp src/ScreenPickerTool.cpp src/Script.cpp + src/ScriptCode.cpp src/Scripted.cpp src/ScriptingEnv.cpp src/ScriptingLangDialog.cpp @@ -330,6 +331,7 @@ set ( QTIPLOT_HDRS src/ApplicationWindow.h src/ScreenPickerTool.h src/Scripted.h src/Script.h + src/ScriptCode.h src/ScriptingEnv.h src/ScriptingLangDialog.h src/ScriptingWindow.h diff --git a/Code/Mantid/MantidPlot/src/PythonScript.cpp b/Code/Mantid/MantidPlot/src/PythonScript.cpp index 923f489c8a7..241cc27d092 100644 --- a/Code/Mantid/MantidPlot/src/PythonScript.cpp +++ b/Code/Mantid/MantidPlot/src/PythonScript.cpp @@ -146,27 +146,251 @@ bool PythonScript::compilesToCompleteStatement(const QString & code) const } /** - * Emits a signal from this object indicating the current line number of the - * code. This includes any offset + * Called from Python with the codeObject and line number of the currently executing line * @param codeObject A pointer to the code object whose line is executing - * @param lineNo The line number that the code is currently executing + * @param lineNo The line number that the code is currently executing, note that + * this will be relative to the top of the code that was executed */ void PythonScript::lineNumberChanged(PyObject *codeObject, int lineNo) { if(codeObject == m_CodeFileObject) { - lineNo += getLineOffset(); - emit currentLineChanged(lineNo, false); + sendLineChangeSignal(getRealLineNo(lineNo), false); } } +/** + * Emit the line change signal for the give line no + * @param lineNo The line number to flag + * @param error True if it is an error + */ +void PythonScript::sendLineChangeSignal(int lineNo, bool error) +{ + emit currentLineChanged(lineNo, error); +} + + +/** + * Create a list autocomplete keywords + */ +void PythonScript::generateAutoCompleteList() +{ + GlobalInterpreterLock gil; + + PyObject *main_module = PyImport_AddModule("__main__"); + PyObject *method = PyString_FromString("_ScopeInspector_GetFunctionAttributes"); + PyObject *keywords(NULL); + if( method && main_module ) + { + keywords = PyObject_CallMethodObjArgs(main_module, method, localDict, NULL); + } + else + { + return; + } + QStringList keywordList; + if(PyErr_Occurred() || !keywords) + { + PyErr_Print(); + return; + } + + keywordList = pythonEnv()->toStringList(keywords); + Py_DECREF(keywords); + Py_DECREF(method); + emit autoCompleteListGenerated(keywordList); +} + +/** + * Convert a python error state into a human-readable string + * + * @return A string containing the error message + */ +QString PythonScript::constructErrorMsg() +{ + GlobalInterpreterLock gil; + QString message; + if (!PyErr_Occurred()) + { + return message; + } + PyObject *exception(NULL), *value(NULL), *traceback(NULL); + PyErr_Fetch(&exception, &value, &traceback); + PyErr_NormalizeException(&exception, &value, &traceback); + PyErr_Clear(); + PyObject *str_repr = PyObject_Str(value); + QTextStream msgStream(&message); + if( value && str_repr ) + { + QString excTypeName(value->ob_type->tp_name); // This is fully qualified with the module name + excTypeName = excTypeName.section(".", -1); + QString exceptionAsStr(PyString_AsString(str_repr)); + if(value->ob_type == (PyTypeObject*)PyExc_SyntaxError) + { + exceptionAsStr = constructSyntaxErrorStr(exceptionAsStr); + } + msgStream << excTypeName << ": " << exceptionAsStr; + } + else + { + msgStream << "Unknown exception has occurred."; + } + tracebackToMsg(msgStream, (PyTracebackObject *)(traceback)); + msgStream << "\n"; + + Py_XDECREF(traceback); + Py_XDECREF(exception); + Py_XDECREF(value); + return msgStream.readAll(); +} + +/** + * Include the line offset & oddity that the syntax error + * line number is always 1 to high + */ +QString PythonScript::constructSyntaxErrorStr(const QString & originalString) +{ + // Format: "some prefix string (filename, line no)" + QStringList pieces = originalString.split(" "); + // Last 3 pieces should always be the parenthesized (filename, line no) + if(pieces.size() < 3) + { + return QString("Unknown syntax error occurred"); + } + QString linenoAsStr = pieces.takeLast(); + linenoAsStr.chop(1); // last bracket + int lineno = linenoAsStr.toInt(); + + + pieces.removeLast(); //Remove the word line + QString filename = pieces.takeLast(); + filename.chop(1); // comma + filename.remove(0,1); // first bracket + + if(filename == QFileInfo(name()).fileName()) + { + lineno -= 1; // Fix for bug when original exception generated + lineno = getRealLineNo(lineno); + sendLineChangeSignal(lineno, true); + } + linenoAsStr = QString::number(lineno); + QString msg = pieces.join(" "); + msg += " (" + filename + ", line " + linenoAsStr + ")"; + return msg; +} + + /** + * Form a traceback + * @param msg The reference to the textstream to accumulate the message + * @param traceback A traceback object + * @param root If true then this is the root of the traceback + */ +void PythonScript::tracebackToMsg(QTextStream &msgStream, + PyTracebackObject* traceback, + bool root) +{ + if(traceback == NULL) return; + msgStream << "\n "; + if (root) msgStream << "at"; + else msgStream << "caused by"; + + int lineno = traceback->tb_lineno; + QString filename = PyString_AsString(traceback->tb_frame->f_code->co_filename); + if(filename == name()) + { + lineno = getRealLineNo(lineno); + sendLineChangeSignal(lineno, true); + + } + + msgStream << " line " << lineno << " in \'" << filename << "\'"; + tracebackToMsg(msgStream, traceback->tb_next, false); +} + +bool PythonScript::setQObject(QObject *val, const char *name) +{ + if (localDict) + { + return pythonEnv()->setQObject(val, name, localDict); + } + else + return false; +} + +bool PythonScript::setInt(int val, const char *name) +{ + return pythonEnv()->setInt(val, name, localDict); +} + +bool PythonScript::setDouble(double val, const char *name) +{ + return pythonEnv()->setDouble(val, name, localDict); +} + +void PythonScript::setContext(QObject *context) +{ + Script::setContext(context); + setQObject(context, "self"); +} + +/** + * Sets the context for the script and if name points to a file then + * sets the __file__ variable + * @param name A string identifier for the script + * @param context A QObject defining the context + */ +void PythonScript::initialize(const QString & name, QObject *context) +{ + GlobalInterpreterLock pythonlock; + PyObject *pymodule = PyImport_AddModule("__main__"); + localDict = PyDict_Copy(PyModule_GetDict(pymodule)); + if( QFileInfo(name).exists() ) + { + QString scriptPath = QFileInfo(name).absoluteFilePath(); + // Make sure the __file__ variable is set + PyDict_SetItem(localDict,PyString_FromString("__file__"), PyString_FromString(scriptPath.toAscii().data())); + } + setContext(context); +} + + +//------------------------------------------------------- +// Private +//------------------------------------------------------- +/** + * Redirect the std out to this object + */ +void PythonScript::beginStdoutRedirect() +{ + if(!redirectStdOut()) return; + stdoutSave = PyDict_GetItemString(pythonEnv()->sysDict(), "stdout"); + Py_XINCREF(stdoutSave); + stderrSave = PyDict_GetItemString(pythonEnv()->sysDict(), "stderr"); + Py_XINCREF(stderrSave); + pythonEnv()->setQObject(this, "stdout", pythonEnv()->sysDict()); + pythonEnv()->setQObject(this, "stderr", pythonEnv()->sysDict()); +} + +/** + * Restore the std out to this object to what it was before the last call to + * beginStdouRedirect + */ +void PythonScript::endStdoutRedirect() +{ + if(!redirectStdOut()) return; + + PyDict_SetItemString(pythonEnv()->sysDict(), "stdout", stdoutSave); + Py_XDECREF(stdoutSave); + PyDict_SetItemString(pythonEnv()->sysDict(), "stderr", stderrSave); + Py_XDECREF(stderrSave); +} /** * Compile the code returning true if successful, false otherwise * @param code * @return True if success, false otherwise */ -bool PythonScript::compile(const QString & code) +bool PythonScript::compileImpl(const QString & code) { PyObject *codeObject = compileToByteCode(code, false); if(codeObject) @@ -183,7 +407,7 @@ bool PythonScript::compile(const QString & code) * Evaluate the code and return the value * @return */ -QVariant PythonScript::evaluate(const QString & code) +QVariant PythonScript::evaluateImpl(const QString & code) { GlobalInterpreterLock gil; PyObject *compiledCode = this->compileToByteCode(code, true); @@ -271,7 +495,7 @@ QVariant PythonScript::evaluate(const QString & code) } } } - + Py_DECREF(pyret); if (PyErr_Occurred()) { @@ -289,226 +513,18 @@ QVariant PythonScript::evaluate(const QString & code) return qret; } -bool PythonScript::execute(const QString & code) +bool PythonScript::executeImpl(const QString & code) { emit startedSerial(""); return executeString(code); } -QFuture<bool> PythonScript::executeAsync(const QString & code) +QFuture<bool> PythonScript::executeAsyncImpl(const QString & code) { emit startedAsync(""); return QtConcurrent::run(this, &PythonScript::executeString, code); } -/** - * Create a list autocomplete keywords - */ -void PythonScript::generateAutoCompleteList() -{ - GlobalInterpreterLock gil; - - PyObject *main_module = PyImport_AddModule("__main__"); - PyObject *method = PyString_FromString("_ScopeInspector_GetFunctionAttributes"); - PyObject *keywords(NULL); - if( method && main_module ) - { - keywords = PyObject_CallMethodObjArgs(main_module, method, localDict, NULL); - } - else - { - return; - } - QStringList keywordList; - if( PyErr_Occurred() || !keywords ) - { - PyErr_Print(); - return; - } - - keywordList = pythonEnv()->toStringList(keywords); - Py_DECREF(keywords); - Py_DECREF(method); - emit autoCompleteListGenerated(keywordList); -} - - -QString PythonScript::constructErrorMsg() -{ - if ( !PyErr_Occurred() ) - { - return QString(""); - } - PyObject *exception(NULL), *value(NULL), *traceback(NULL); - PyTracebackObject *excit(NULL); - PyErr_Fetch(&exception, &value, &traceback); - PyErr_NormalizeException(&exception, &value, &traceback); - - // Get the filename of the error. This will be blank if it occurred in the main script - QString filename(""); - int endtrace_line(-1); - if( traceback ) - { - excit = (PyTracebackObject*)traceback; - while (excit && (PyObject*)excit != Py_None) - { - _frame *frame = excit->tb_frame; - endtrace_line = excit->tb_lineno; - filename = PyString_AsString(frame->f_code->co_filename); - excit = excit->tb_next; - } - } - - // Exception value - - int msg_lineno(-1); - int marker_lineno(-1); - - QString message(""); - QString exception_details(""); - if( PyErr_GivenExceptionMatches(exception, PyExc_SyntaxError) ) - { - msg_lineno = pythonEnv()->toString(PyObject_GetAttrString(value, "lineno"),true).toInt(); - if( traceback ) - { - marker_lineno = endtrace_line; - } - else - { - // No traceback here get line from exception value object - marker_lineno = msg_lineno; - } - - message = "SyntaxError"; - QString except_value(pythonEnv()->toString(value)); - exception_details = except_value.section('(',0,0); - filename = except_value.section('(',1).section(',',0,0); - } - else - { - if( traceback ) - { - excit = (PyTracebackObject*)traceback; - marker_lineno = excit->tb_lineno; - } - else - { - marker_lineno = -10000; - } - - if( filename.isEmpty() ) - { - msg_lineno = marker_lineno; - } - else - { - msg_lineno = endtrace_line; - } - message = pythonEnv()->toString(exception).section('.',1).remove("'>"); - exception_details = pythonEnv()->toString(value) + QString(' '); - } - message += " on line " + QString::number(marker_lineno); - message += ": \"" + exception_details.trimmed() + "\" "; - - if( marker_lineno >=0 - && !PyErr_GivenExceptionMatches(exception, PyExc_SystemExit) - && !filename.isEmpty() - ) - { - message += "in file '" + QFileInfo(filename).fileName() - + "' at line " + QString::number(msg_lineno); - } - - emit currentLineChanged(marker_lineno, true); - - // We're responsible for the reference count of these objects - Py_XDECREF(traceback); - Py_XDECREF(value); - Py_XDECREF(exception); - - PyErr_Clear(); - - return message + QString("\n"); -} - - -bool PythonScript::setQObject(QObject *val, const char *name) -{ - if (localDict) - { - return pythonEnv()->setQObject(val, name, localDict); - } - else - return false; -} - -bool PythonScript::setInt(int val, const char *name) -{ - return pythonEnv()->setInt(val, name, localDict); -} - -bool PythonScript::setDouble(double val, const char *name) -{ - return pythonEnv()->setDouble(val, name, localDict); -} - -void PythonScript::setContext(QObject *context) -{ - Script::setContext(context); - setQObject(context, "self"); -} - -/** - * Sets the context for the script and if name points to a file then - * sets the __file__ variable - * @param name A string identifier for the script - * @param context A QObject defining the context - */ -void PythonScript::initialize(const QString & name, QObject *context) -{ - GlobalInterpreterLock pythonlock; - PyObject *pymodule = PyImport_AddModule("__main__"); - localDict = PyDict_Copy(PyModule_GetDict(pymodule)); - if( QFileInfo(name).exists() ) - { - QString scriptPath = QFileInfo(name).absoluteFilePath(); - // Make sure the __file__ variable is set - PyDict_SetItem(localDict,PyString_FromString("__file__"), PyString_FromString(scriptPath.toAscii().data())); - } - setContext(context); -} - - -//------------------------------------------------------- -// Private -//------------------------------------------------------- -/** - * Redirect the std out to this object - */ -void PythonScript::beginStdoutRedirect() -{ - if(!redirectStdOut()) return; - stdoutSave = PyDict_GetItemString(pythonEnv()->sysDict(), "stdout"); - Py_XINCREF(stdoutSave); - stderrSave = PyDict_GetItemString(pythonEnv()->sysDict(), "stderr"); - Py_XINCREF(stderrSave); - pythonEnv()->setQObject(this, "stdout", pythonEnv()->sysDict()); - pythonEnv()->setQObject(this, "stderr", pythonEnv()->sysDict()); -} - -/** - * Restore the std out to this object to what it was before the last call to - * beginStdouRedirect - */ -void PythonScript::endStdoutRedirect() -{ - if(!redirectStdOut()) return; - - PyDict_SetItemString(pythonEnv()->sysDict(), "stdout", stdoutSave); - Py_XDECREF(stdoutSave); - PyDict_SetItemString(pythonEnv()->sysDict(), "stderr", stderrSave); - Py_XDECREF(stderrSave); -} /// Performs the call to Python bool PythonScript::executeString(const QString & code) @@ -518,12 +534,12 @@ bool PythonScript::executeString(const QString & code) GlobalInterpreterLock gil; PyObject *compiledCode = compileToByteCode(code); - PyObject *result = executeCompiledCode((PyCodeObject*)compiledCode); + PyObject *result = executeCompiledCode(compiledCode); + success = checkResult(result); if(isInteractive()) { generateAutoCompleteList(); } - success = checkResult(result); Py_XDECREF(compiledCode); Py_XDECREF(result); @@ -565,14 +581,14 @@ namespace * @param compiledCode An object that has been compiled from a code fragment * @return The result python object */ -PyObject* PythonScript::executeCompiledCode(PyCodeObject *compiledCode) +PyObject* PythonScript::executeCompiledCode(PyObject *compiledCode) { PyObject *result(NULL); if(!compiledCode) return result; InstallTrace traceInstall(*this); beginStdoutRedirect(); - result = PyEval_EvalCode(compiledCode, localDict, localDict); + result = PyEval_EvalCode((PyCodeObject*)compiledCode, localDict, localDict); endStdoutRedirect(); return result; } @@ -695,13 +711,16 @@ PyObject *PythonScript::compileToByteCode(const QString & code, bool for_eval) success = (compiledCode != NULL); } - if(!success) + if(success) + { + m_CodeFileObject = ((PyCodeObject*)(compiledCode))->co_filename; + } + else { emit error(constructErrorMsg(), name(), 0); compiledCode = NULL; m_CodeFileObject = NULL; } - m_CodeFileObject = ((PyCodeObject*)(compiledCode))->co_filename; return compiledCode; } diff --git a/Code/Mantid/MantidPlot/src/PythonScript.h b/Code/Mantid/MantidPlot/src/PythonScript.h index d4c59c577aa..de02b8cc430 100644 --- a/Code/Mantid/MantidPlot/src/PythonScript.h +++ b/Code/Mantid/MantidPlot/src/PythonScript.h @@ -68,22 +68,21 @@ public: /// Emits a signal from this object indicating the current line number of the /// code. This includes any offset. void lineNumberChanged(PyObject * codeObject, int lineNo); + /// Emit the line change signal + void sendLineChangeSignal(int lineNo, bool error); -public slots: - /// Compile the code, returning true if it was successful, false otherwise - bool compile(const QString & code); - /// Evaluate the current code and return a result as a QVariant - QVariant evaluate(const QString & code); - /// Execute the current code and return a boolean indicating success/failure - bool execute(const QString &); - /// Execute the code asynchronously, returning immediately after the execution has started - QFuture<bool> executeAsync(const QString & code); /// Create a list of keywords for the code completion API void generateAutoCompleteList(); /// Construct the error message from the stack trace (if one exists) QString constructErrorMsg(); - + /// Fix the line number of the syntax errors. This includes the offset + /// & a fix for the syntax errors always off by one + QString constructSyntaxErrorStr(const QString & originalString); + /// Convert a traceback to a string + void tracebackToMsg(QTextStream &msgStream, PyTracebackObject* traceback, + bool root=true); + /// Set the name of the passed object so that Python can refer to it bool setQObject(QObject *val, const char *name); /// Set the name of the integer so that Python can refer to it @@ -139,10 +138,19 @@ private: void endStdoutRedirect(); // --------------------------- Script compilation/execution ----------------------------------- + /// Compile the code, returning true if it was successful, false otherwise + bool compileImpl(const QString & code); + /// Evaluate the current code and return a result as a QVariant + QVariant evaluateImpl(const QString & code); + /// Execute the current code and return a boolean indicating success/failure + bool executeImpl(const QString &); + /// Execute the code asynchronously, returning immediately after the execution has started + QFuture<bool> executeAsyncImpl(const QString & code); + /// Performs the call to Python from a string bool executeString(const QString & code); /// Executes the code object and returns the result, may be null - PyObject* executeCompiledCode(PyCodeObject *compiledCode); + PyObject* executeCompiledCode(PyObject *compiledCode); /// Check an object for a result. bool checkResult(PyObject *result); diff --git a/Code/Mantid/MantidPlot/src/Script.cpp b/Code/Mantid/MantidPlot/src/Script.cpp index 381b2181ab8..6d6f858909e 100644 --- a/Code/Mantid/MantidPlot/src/Script.cpp +++ b/Code/Mantid/MantidPlot/src/Script.cpp @@ -33,7 +33,7 @@ Script::Script(ScriptingEnv *env, const QString &name, const InteractionType interact, QObject * context) : QObject(), m_env(env), m_name(name) , m_context(context), - m_redirectOutput(true), m_reportProgress(false), m_lineOffset(0), m_interactMode(interact), + m_redirectOutput(true), m_reportProgress(false), m_codeOffset(0), m_interactMode(interact), m_execMode(NotExecuting) { m_env->incref(); @@ -49,6 +49,43 @@ Script::~Script() m_env->decref(); } +/// +/** + * Compile the code, returning true/false depending on the status + * @param code Code to compile + * @return True/false depending on success + */ +bool Script::compile(const ScriptCode & code) +{ + m_codeOffset = code.offset(); + return this->compileImpl(code); +} + +/** + * Evaluate the Code, returning QVariant() on an error / exception. + * @param code Code to evaluate + * @return The result as a QVariant + */ +QVariant Script::evaluate(const ScriptCode & code) +{ + m_codeOffset = code.offset(); + return this->evaluateImpl(code); +} + +/// Execute the Code, returning false on an error / exception. +bool Script::execute(const ScriptCode & code) +{ + m_codeOffset = code.offset(); + return this->executeImpl(code); +} + +/// Execute the code asynchronously, returning immediately after the execution has started +QFuture<bool> Script::executeAsync(const ScriptCode & code) +{ + m_codeOffset = code.offset(); + return this->executeAsyncImpl(code); +} + /// Sets the execution mode to NotExecuting void Script::setNotExecuting() diff --git a/Code/Mantid/MantidPlot/src/Script.h b/Code/Mantid/MantidPlot/src/Script.h index c65ae19b284..385790da1ae 100644 --- a/Code/Mantid/MantidPlot/src/Script.h +++ b/Code/Mantid/MantidPlot/src/Script.h @@ -35,6 +35,8 @@ #include <QEvent> #include <QFuture> +#include "ScriptCode.h" + //------------------------------------------- // Forward declarations //------------------------------------------- @@ -73,10 +75,6 @@ class Script : public QObject const QObject * context() const { return m_context; } /// Set the context in which the code is to be executed. virtual void setContext(QObject *context) { m_context = context; } - /// Set an offset for the current code fragment - void setLineOffset(const int offset) { m_lineOffset = offset;} - /// Set an offset for the current code fragment - inline int getLineOffset() const { return m_lineOffset; } /// Is this an interactive script inline bool isInteractive() { return m_interactMode == Interactive; } @@ -100,13 +98,13 @@ class Script : public QObject public slots: /// Compile the code, returning true/false depending on the status - virtual bool compile(const QString & code) = 0; + bool compile(const ScriptCode & code); /// Evaluate the Code, returning QVariant() on an error / exception. - virtual QVariant evaluate(const QString & code) = 0; + QVariant evaluate(const ScriptCode & code); /// Execute the Code, returning false on an error / exception. - virtual bool execute(const QString & code) = 0; + bool execute(const ScriptCode & code); /// Execute the code asynchronously, returning immediately after the execution has started - virtual QFuture<bool> executeAsync(const QString & code) = 0; + QFuture<bool> executeAsync(const ScriptCode & code); /// Sets the execution mode to NotExecuting void setNotExecuting(); @@ -138,6 +136,19 @@ signals: // Signal that new keywords are available void autoCompleteListGenerated(const QStringList & keywords); +protected: + /// Return the true line number by adding the offset + inline int getRealLineNo(const int codeLine) const { return codeLine + m_codeOffset; } + + /// Compile the code, returning true/false depending on the status + virtual bool compileImpl(const QString & code) = 0; + /// Evaluate the Code, returning QVariant() on an error / exception. + virtual QVariant evaluateImpl(const QString & code) = 0; + /// Execute the Code, returning false on an error / exception. + virtual bool executeImpl(const QString & code) = 0; + /// Execute the code asynchronously, returning immediately after the execution has started + virtual QFuture<bool> executeAsyncImpl(const QString & code) = 0; + private: /// Normalise line endings for the given code. The Python C/API does not seem to like CRLF endings so normalise to just LF QString normaliseLineEndings(QString text) const; @@ -147,7 +158,7 @@ private: QObject *m_context; bool m_redirectOutput; bool m_reportProgress; - int m_lineOffset; + int m_codeOffset; InteractionType m_interactMode; ExecutionMode m_execMode; diff --git a/Code/Mantid/MantidPlot/src/ScriptCode.cpp b/Code/Mantid/MantidPlot/src/ScriptCode.cpp new file mode 100644 index 00000000000..38ad4ae9676 --- /dev/null +++ b/Code/Mantid/MantidPlot/src/ScriptCode.cpp @@ -0,0 +1,44 @@ +#include "ScriptCode.h" + +/// Empty code +ScriptCode::ScriptCode() : m_code(), m_offset() +{ + +} + +/** + * Code from a c-style string, setting the offset to zero + * @param codeStr A string of code + */ +ScriptCode::ScriptCode(const char * codeStr) : m_code(codeStr), m_offset(0) +{ +} + +/** + * Code from a QString, setting the offset to the given value + * @param codeStr A string of code + * @param offset An offset for the first line in a larger block of code + */ +ScriptCode::ScriptCode(const char * codeStr, const int offset) + : m_code(codeStr), m_offset(offset) +{ +} + +/// Code from a QString with zero offset +ScriptCode::ScriptCode(const QString & codeStr) + : m_code(codeStr), m_offset(0) +{ +} + +/// Code from a QString with a defined offset +ScriptCode::ScriptCode(const QString & codeStr, const int offset) + : m_code(codeStr), m_offset(offset) +{ +} + +/// Conversion to a QString +/// @return A String containing the code +ScriptCode::operator const QString () const +{ + return m_code; +} diff --git a/Code/Mantid/MantidPlot/src/ScriptCode.h b/Code/Mantid/MantidPlot/src/ScriptCode.h new file mode 100644 index 00000000000..91e3b056f1d --- /dev/null +++ b/Code/Mantid/MantidPlot/src/ScriptCode.h @@ -0,0 +1,39 @@ +#ifndef SCRIPTCODE_H_ +#define SCRIPTCODE_H_ + +#include <QString> + +/** + * Code objects represent the code as a string but also store an + * optional offset that defines where they are within a larger + * chunk of code. They can be created directly from strings + * and are also implicitly convertible to strings + */ +class ScriptCode +{ +public: + /// Empty code + ScriptCode(); + /// Code from a C-string with zero offset + ScriptCode(const char * codeStr); + /// Code from a C-string with a defined offset + ScriptCode(const char * codeStr, const int offset); + /// Code from a QString with zero offset + ScriptCode(const QString & codeStr); + /// Code from a QString with a defined offset + ScriptCode(const QString & codeStr, const int offset); + + /// Conversion to a QString + operator const QString () const; + + /// Return the offset + inline int offset() const { return m_offset; } + +private: + /// The code string + QString m_code; + /// The offset within a larger chunk of code + int m_offset; +}; + +#endif /* SCRIPTCODE_H_ */ diff --git a/Code/Mantid/MantidPlot/src/ScriptFileInterpreter.cpp b/Code/Mantid/MantidPlot/src/ScriptFileInterpreter.cpp index f30d488ab26..0fcc8bee3b7 100644 --- a/Code/Mantid/MantidPlot/src/ScriptFileInterpreter.cpp +++ b/Code/Mantid/MantidPlot/src/ScriptFileInterpreter.cpp @@ -218,7 +218,9 @@ void ScriptFileInterpreter::executeSelection(const Script::ExecutionMode mode) { if(m_editor->hasSelectedText()) { - executeCode(m_editor->selectedText(), mode); + int firstLineOffset(0), unused(0); + m_editor->getSelection(&firstLineOffset, &unused, &unused, &unused); + executeCode(ScriptCode(m_editor->selectedText(), firstLineOffset), mode); } else { @@ -315,6 +317,7 @@ void ScriptFileInterpreter::setupScriptRunner(const ScriptingEnv & environ, cons connect(m_runner.data(), SIGNAL(finished(const QString &)), m_messages, SLOT(displayMessageWithTimestamp(const QString &))); connect(m_runner.data(), SIGNAL(print(const QString &)), m_messages, SLOT(displayMessage(const QString &))); connect(m_runner.data(), SIGNAL(error(const QString &,const QString &, int)), m_messages, SLOT(displayError(const QString &))); + connect(m_runner.data(), SIGNAL(error(const QString &,const QString &, int)), m_editor, SLOT(markExecutingLineAsError())); } /** @@ -342,9 +345,10 @@ bool ScriptFileInterpreter::readFileIntoEditor(const QString & filename) * Use the current Script object to execute the code asynchronously * @param code :: The code string to run */ -void ScriptFileInterpreter::executeCode(const QString & code, const Script::ExecutionMode mode) +void ScriptFileInterpreter::executeCode(const ScriptCode & code, const Script::ExecutionMode mode) { - if( code.isEmpty() ) return; + const QString codeStr = code; + if( codeStr.isEmpty() ) return; if(mode == Script::Asynchronous) { m_runner->executeAsync(code); diff --git a/Code/Mantid/MantidPlot/src/ScriptFileInterpreter.h b/Code/Mantid/MantidPlot/src/ScriptFileInterpreter.h index b00af06cfff..70d0b5f08a4 100644 --- a/Code/Mantid/MantidPlot/src/ScriptFileInterpreter.h +++ b/Code/Mantid/MantidPlot/src/ScriptFileInterpreter.h @@ -105,7 +105,7 @@ private: void setupScriptRunner(const ScriptingEnv & environ, const QString & identifier); bool readFileIntoEditor(const QString & filename); - void executeCode(const QString & code, const Script::ExecutionMode mode); + void executeCode(const ScriptCode & code, const Script::ExecutionMode mode); QSplitter *m_splitter; ScriptEditor *m_editor; diff --git a/Code/Mantid/MantidPlot/src/muParserScript.cpp b/Code/Mantid/MantidPlot/src/muParserScript.cpp index 3fa15722f9d..bee4d042331 100644 --- a/Code/Mantid/MantidPlot/src/muParserScript.cpp +++ b/Code/Mantid/MantidPlot/src/muParserScript.cpp @@ -396,7 +396,7 @@ double muParserScript::evalSingleLine() muParserScript *muParserScript::current = NULL; -bool muParserScript::compile(const QString & code) +bool muParserScript::compileImpl(const QString & code) { muCode.clear(); QString muCodeLine = ""; @@ -459,7 +459,7 @@ bool muParserScript::compile(const QString & code) return true; } -QVariant muParserScript::evaluate(const QString & code) +QVariant muParserScript::evaluateImpl(const QString & code) { if (!compile(code)) return QVariant(); @@ -479,7 +479,7 @@ QVariant muParserScript::evaluate(const QString & code) return QVariant(val); } -bool muParserScript::execute(const QString & code) +bool muParserScript::executeImpl(const QString & code) { if (!compile(code)) return false; @@ -501,7 +501,7 @@ bool muParserScript::execute(const QString & code) /** * Execute the script in a seprate thread */ -QFuture<bool> muParserScript::executeAsync(const QString &) +QFuture<bool> muParserScript::executeAsyncImpl(const QString &) { throw std::runtime_error("muParser does not support asynchronous execution"); } diff --git a/Code/Mantid/MantidPlot/src/muParserScript.h b/Code/Mantid/MantidPlot/src/muParserScript.h index ac4003d38d5..939efb38a31 100644 --- a/Code/Mantid/MantidPlot/src/muParserScript.h +++ b/Code/Mantid/MantidPlot/src/muParserScript.h @@ -50,12 +50,12 @@ class muParserScript: public Script bool compilesToCompleteStatement(const QString &) const { return true; }; public slots: - QVariant evaluate(const QString &); + QVariant evaluateImpl(const QString &); double evalSingleLine(); QString evalSingleLineToString(const QLocale& locale, char f, int prec); - bool compile(const QString & code); - bool execute(const QString &); - QFuture<bool> executeAsync(const QString &); + bool compileImpl(const QString & code); + bool executeImpl(const QString &); + QFuture<bool> executeAsyncImpl(const QString &); bool setQObject(QObject *val, const char *name); bool setInt(int val, const char* name); bool setDouble(double val, const char* name); diff --git a/Code/Mantid/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/ScriptEditor.h b/Code/Mantid/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/ScriptEditor.h index 149aa67ca5d..46f2d1bce7b 100644 --- a/Code/Mantid/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/ScriptEditor.h +++ b/Code/Mantid/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/ScriptEditor.h @@ -144,6 +144,8 @@ public slots: void setMarkerState(bool enabled); /// Update the progress marker void updateProgressMarker(int lineno, bool error); + /// Mark the progress arrow as an error + void markExecutingLineAsError(); /// Refresh the autocomplete information base on a new set of keywords void updateCompletionAPI(const QStringList & keywords); /// Print the text within the widget @@ -187,6 +189,8 @@ private: /// The margin marker int m_progressArrowKey; + /// Hold the line number of the currently executing line + int m_currentExecLine; /// A pointer to a QsciAPI object that handles the code completion QsciAPIs *m_completer; /// The colour of the marker for a success state diff --git a/Code/Mantid/MantidQt/MantidWidgets/src/ScriptEditor.cpp b/Code/Mantid/MantidQt/MantidWidgets/src/ScriptEditor.cpp index ee5f178270a..c787a7c73fc 100644 --- a/Code/Mantid/MantidQt/MantidWidgets/src/ScriptEditor.cpp +++ b/Code/Mantid/MantidQt/MantidWidgets/src/ScriptEditor.cpp @@ -138,7 +138,7 @@ QColor ScriptEditor::g_error_colour = QColor("red"); */ ScriptEditor::ScriptEditor(QWidget *parent, QsciLexer *codelexer) : QsciScintilla(parent), m_filename(""), m_progressArrowKey(markerDefine(QsciScintilla::RightArrow)), - m_completer(NULL),m_previousKey(0), m_zoomLevel(0), + m_currentExecLine(0), m_completer(NULL),m_previousKey(0), m_zoomLevel(0), m_findDialog(new FindReplaceDialog(this)) { //Syntax highlighting and code completion @@ -393,6 +393,7 @@ void ScriptEditor::setMarkerState(bool enabled) */ void ScriptEditor::updateProgressMarker(int lineno, bool error) { + m_currentExecLine = lineno; if(error) { setMarkerBackgroundColor(g_error_colour, m_progressArrowKey); @@ -406,7 +407,13 @@ void ScriptEditor::updateProgressMarker(int lineno, bool error) if( lineno < 0 || lineno > this->lines() ) return; ensureLineVisible(lineno); - markerAdd(lineno - 1, m_progressArrowKey); + markerAdd(m_currentExecLine - 1, m_progressArrowKey); +} + +/// Mark the progress arrow as an error +void ScriptEditor::markExecutingLineAsError() +{ + updateProgressMarker(m_currentExecLine, true); } /** -- GitLab