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