diff --git a/qt/python/mantidqt/widgets/codeeditor/execution.py b/qt/python/mantidqt/widgets/codeeditor/execution.py
index 8f015af554a9bda126fb2242e94c071293098ba7..4d0b0fe39073d6178f04011484dda634fa28cd57 100644
--- a/qt/python/mantidqt/widgets/codeeditor/execution.py
+++ b/qt/python/mantidqt/widgets/codeeditor/execution.py
@@ -54,8 +54,10 @@ class PythonCodeExecution(object):
             def error_cb_wrapped(_): pass
         self.on_error = error_cb_wrapped
 
+        self.on_progress_update = progress_cb
+
     def execute_async(self, code_str, user_globals,
-                      user_locals, ):
+                      user_locals):
         """
         Execute the given code string on a separate thread. This function
         returns as soon as the new thread starts
@@ -65,7 +67,6 @@ class PythonCodeExecution(object):
         :param user_locals: A mutable mapping type to store local variables
         :returns: The created async task
         """
-
         t = AsyncTask(self.execute, args=(code_str, user_globals, user_locals),
                       success_cb=self.on_success, error_cb=self.on_error)
         t.start()
@@ -74,12 +75,59 @@ class PythonCodeExecution(object):
     def execute(self, code_str, user_globals,
                 user_locals):
         """Execute the given code on the calling thread
-        within the provided context. All exceptions are caught
-        and stored with the returned result
+        within the provided context.
 
         :param code_str: A string containing code to execute
         :param user_globals: A mutable mapping type to store global variables
         :param user_locals: A mutable mapping type to store local variables
         :raises: Any error that the code generates
         """
-        exec(code_str, user_globals, user_locals)
+        # execute whole string if no reporting is required
+        if self.on_progress_update is None:
+            self._do_exec(code_str, user_globals, user_locals)
+        else:
+            self._execute_as_blocks(code_str, user_globals, user_locals,
+                                    self.on_progress_update)
+
+    def _execute_as_blocks(self, code_str, user_globals, user_locals,
+                           progress_cb):
+        """Execute the code in the supplied context and report the progress
+        using the supplied callback"""
+        # will raise a SyntaxError if all of the code is invalid
+        compile(code_str, "<string>", mode='exec')
+
+        for block in code_blocks(code_str):
+            progress_cb(block.lineno)
+            self._do_exec(block.code_obj, user_globals, user_locals)
+
+    def _do_exec(self, code, user_globals, user_locals):
+        exec (code, user_globals, user_locals)
+
+
+class CodeBlock(object):
+    """Holds an executable code object. It also stores the line number
+    of the first line within a larger group of code blocks"""
+
+    def __init__(self, code_obj, lineno):
+        self.code_obj = code_obj
+        self.lineno = lineno
+
+
+def code_blocks(code_str):
+    """Generator to produce blocks of executable code
+    from the given code string.
+    """
+    lineno = 1
+    lines = code_str.splitlines()
+    cur_block = []
+    for line in lines:
+        cur_block.append(line)
+        code_block = "\n".join(cur_block)
+        try:
+            code_obj = compile(code_block, "<string>", mode='exec')
+            yield CodeBlock(code_obj, lineno)
+            lineno += len(cur_block)
+            cur_block = []
+        except (SyntaxError, TypeError):
+            # assume we don't have a full block yet
+            continue
diff --git a/qt/python/mantidqt/widgets/codeeditor/test/test_execution.py b/qt/python/mantidqt/widgets/codeeditor/test/test_execution.py
index df8bdcbaaf7ec88af3a7bb2a67f4dfc4883dbff6..09497f412642a85cbe96988a49f3024c642976a8 100644
--- a/qt/python/mantidqt/widgets/codeeditor/test/test_execution.py
+++ b/qt/python/mantidqt/widgets/codeeditor/test/test_execution.py
@@ -30,13 +30,28 @@ class PythonCodeExecutionTest(unittest.TestCase):
         error_cb_called = False
         task_exc = None
 
-        def on_success(self, task_result):
+        def on_success(self):
             self.success_cb_called = True
 
         def on_error(self, exc):
             self.error_cb_called = True
             self.task_exc = exc
 
+    class ReceiverWithProgress(Receiver):
+
+        def __init__(self):
+            self.lines_received = []
+
+        def on_progess_update(self, lineno):
+            self.lines_received.append(lineno)
+
+        def on_error(self, exc):
+            self.error_cb_called = True
+            self.task_exc = exc
+
+    # ---------------------------------------------------------------------------
+    # Successful execution tests
+    # ---------------------------------------------------------------------------
     def test_execute_places_output_in_provided_mapping_object(self):
         code = "_local=100"
         namespace = {}
@@ -60,6 +75,9 @@ class PythonCodeExecutionTest(unittest.TestCase):
         task = executor.execute_async(code, {}, {})
         task.join()
 
+    # ---------------------------------------------------------------------------
+    # Error execution tests
+    # ---------------------------------------------------------------------------
     def test_execute_raises_syntax_error_on_bad_code(self):
         code = "if:"
         self._verify_failed_serial_execute(SyntaxError, code, {}, {})
@@ -80,6 +98,85 @@ class PythonCodeExecutionTest(unittest.TestCase):
         code = "x = _local + 1"
         self._verify_failed_serial_execute(NameError, code, {}, {})
 
+    # ---------------------------------------------------------------------------
+    # Progress tests
+    # ---------------------------------------------------------------------------
+    def test_progress_cb_is_not_called_for_empty_string(self):
+        code = ""
+        recv = PythonCodeExecutionTest.ReceiverWithProgress()
+        executor = PythonCodeExecution(success_cb=recv.on_success, error_cb=recv.on_error,
+                                       progress_cb=recv.on_progess_update)
+        task = executor.execute_async(code, {}, {})
+        task.join()
+        self.assertEqual(0, len(recv.lines_received))
+
+    def test_progress_cb_is_not_called_for_code_with_syntax_errors(self):
+        code = """x = 1
+y = 
+"""
+        recv = PythonCodeExecutionTest.ReceiverWithProgress()
+        executor = PythonCodeExecution(success_cb=recv.on_success, error_cb=recv.on_error,
+                                       progress_cb=recv.on_progess_update)
+        task = executor.execute_async(code, {}, {})
+        task.join()
+        self.assertFalse(recv.success_cb_called)
+        self.assertTrue(recv.error_cb_called)
+        self.assertEqual(0, len(recv.lines_received))
+
+    def test_progress_cb_is_called_for_single_line(self):
+        code = "x = 1"
+        recv = PythonCodeExecutionTest.ReceiverWithProgress()
+        executor = PythonCodeExecution(success_cb=recv.on_success, error_cb=recv.on_error,
+                                       progress_cb=recv.on_progess_update)
+        task = executor.execute_async(code, {}, {})
+        task.join()
+        if not recv.success_cb_called:
+            self.assertTrue(recv.error_cb_called)
+            self.fail("Execution failed with error:\n" + str(recv.task_exc))
+
+        self.assertEqual([1], recv.lines_received)
+
+    def test_progress_cb_is_called_for_multiple_single_lines(self):
+        code = """x = 1
+y = 2
+"""
+        recv = PythonCodeExecutionTest.ReceiverWithProgress()
+        executor = PythonCodeExecution(success_cb=recv.on_success, error_cb=recv.on_error,
+                                       progress_cb=recv.on_progess_update)
+        task = executor.execute_async(code, {}, {})
+        task.join()
+        if not recv.success_cb_called:
+            self.assertTrue(recv.error_cb_called)
+            self.fail("Execution failed with error:\n" + str(recv.task_exc))
+
+        self.assertEqual([1, 2], recv.lines_received)
+
+    def test_progress_cb_is_called_for_mix_single_lines_and_blocks(self):
+        code = """x = 1
+# comment line
+
+sum = 0
+for i in range(10):
+    if i %2 == 0:
+        sum += i
+
+squared = sum*sum
+"""
+        recv = PythonCodeExecutionTest.ReceiverWithProgress()
+        executor = PythonCodeExecution(success_cb=recv.on_success, error_cb=recv.on_error,
+                                       progress_cb=recv.on_progess_update)
+        context = {}
+        task = executor.execute_async(code, context, context)
+        task.join()
+        if not recv.success_cb_called:
+            self.assertTrue(recv.error_cb_called)
+            self.fail("Execution failed with error:\n" + str(recv.task_exc))
+
+        self.assertEqual(20, context['sum'])
+        self.assertEqual(20*20, context['squared'])
+        self.assertEqual(1, context['x'])
+        self.assertEqual([1, 2, 3, 4, 5, 8, 9], recv.lines_received)
+
     # -------------------------------------------------------------------------
     # Helpers
     # -------------------------------------------------------------------------