From 0bedc275daffebba5b2fe29f05b62c1282faab50 Mon Sep 17 00:00:00 2001
From: Martyn Gigg <martyn.gigg@gmail.com>
Date: Fri, 12 Jan 2018 17:44:13 +0000
Subject: [PATCH] Add support for executing selected pieces of code

Refs #21251
---
 .../widgets/codeeditor/interpreter.py         | 41 +++++++++++++------
 .../codeeditor/multifileinterpreter.py        |  4 +-
 .../codeeditor/test/test_codeeditor.py        | 17 ++++++++
 .../mantidqt/widgets/src/_widgetscore.sip     | 10 +++++
 4 files changed, 59 insertions(+), 13 deletions(-)

diff --git a/qt/python/mantidqt/widgets/codeeditor/interpreter.py b/qt/python/mantidqt/widgets/codeeditor/interpreter.py
index 858a66db013..1d19431193a 100644
--- a/qt/python/mantidqt/widgets/codeeditor/interpreter.py
+++ b/qt/python/mantidqt/widgets/codeeditor/interpreter.py
@@ -53,8 +53,8 @@ class PythonFileInterpreter(QWidget):
         # presenter
         self._presenter = PythonFileInterpreterPresenter(self, PythonCodeExecution())
 
-    def execute_all_async(self):
-        self._presenter.req_execute_all_async()
+    def execute_async(self):
+        self._presenter.req_execute_async()
 
     def set_editor_readonly(self, ro):
         self.editor.setReadOnly(ro)
@@ -88,30 +88,41 @@ class PythonFileInterpreterPresenter(QObject):
 
     def __init__(self, view, model):
         super(PythonFileInterpreterPresenter, self).__init__()
+        # attributes
         self.view = view
         self.model = model
+        # offset of executing code from start of the file
+        self._code_start_offset = 0
 
         # connect signals
-        self.model.sig_exec_success.connect(self.on_exec_success)
-        self.model.sig_exec_error.connect(self.on_exec_error)
-        self.model.sig_exec_progress.connect(self.view.editor.updateProgressMarker)
+        self.model.sig_exec_success.connect(self._on_exec_success)
+        self.model.sig_exec_error.connect(self._on_exec_error)
+        self.model.sig_exec_progress.connect(self._on_progress_update)
 
         # starts idle
         self.view.set_status_message(IDLE_STATUS_MSG)
 
-    def req_execute_all_async(self):
-        text = self.view.editor.text()
-        if not text:
-            return
+    def req_execute_async(self):
+        code_str, self._code_start_offset = self._get_code_for_execution()
         self.view.set_editor_readonly(True)
         self.view.set_status_message(RUNNING_STATUS_MSG)
-        return self.model.execute_async(text)
+        return self.model.execute_async(code_str)
 
-    def on_exec_success(self):
+    def _get_code_for_execution(self):
+        editor = self.view.editor
+        if editor.hasSelectedText():
+            code_str = editor.selectedText()
+            line_from, _, _, _ = editor.getSelection()
+        else:
+            code_str = editor.text()
+            line_from = 0
+        return code_str, line_from
+
+    def _on_exec_success(self):
         self.view.set_editor_readonly(False)
         self.view.set_status_message(IDLE_STATUS_MSG)
 
-    def on_exec_error(self, task_error):
+    def _on_exec_error(self, task_error):
         if isinstance(task_error.exception, SyntaxError):
             lineno = task_error.exception.lineno
         else:
@@ -119,3 +130,9 @@ class PythonFileInterpreterPresenter(QObject):
         self.view.editor.updateProgressMarker(lineno, True)
         self.view.set_editor_readonly(False)
         self.view.set_status_message(IDLE_STATUS_MSG)
+
+    def _on_progress_update(self, lineno):
+        """Update progress on the view taking into account if a selection of code is
+        running"""
+        self.view.editor.updateProgressMarker(lineno + self._code_start_offset,
+                                              False)
\ No newline at end of file
diff --git a/qt/python/mantidqt/widgets/codeeditor/multifileinterpreter.py b/qt/python/mantidqt/widgets/codeeditor/multifileinterpreter.py
index 4e9ee97534f..7f62d5a02f5 100644
--- a/qt/python/mantidqt/widgets/codeeditor/multifileinterpreter.py
+++ b/qt/python/mantidqt/widgets/codeeditor/multifileinterpreter.py
@@ -51,7 +51,9 @@ class MultiPythonFileInterpreter(QWidget):
         return self._editors.currentWidget()
 
     def execute_current(self):
-        self.current_editor().execute_all_async()
+        """Execute content of the current file. If a selection is active
+        then only this portion of code is executed"""
+        self.current_editor().execute_async()
 
     def append_new_editor(self):
         title = "New"
diff --git a/qt/python/mantidqt/widgets/codeeditor/test/test_codeeditor.py b/qt/python/mantidqt/widgets/codeeditor/test/test_codeeditor.py
index ff465ee2e81..933547f4e0c 100644
--- a/qt/python/mantidqt/widgets/codeeditor/test/test_codeeditor.py
+++ b/qt/python/mantidqt/widgets/codeeditor/test/test_codeeditor.py
@@ -60,6 +60,23 @@ class CodeEditorTest(unittest.TestCase):
         widget.setReadOnly(True)
         self.assertTrue(widget.isReadOnly())
 
+    def test_get_selection_for_empty_selection(self):
+        widget = CodeEditor(TEST_LANG)
+        res = widget.getSelection()
+        self.assertEqual((-1, -1, -1, -1), res)
+
+    def test_get_selection_for_non_empty_selection(self):
+        widget = CodeEditor(TEST_LANG)
+        widget.setText("""first line
+        second line
+        third line
+        fourth line
+        """)
+        selected = (0, 2, 3, 4)
+        widget.setSelection(*selected)
+        res = widget.getSelection()
+        self.assertEqual(selected, res)
+
     # ---------------------------------------------------------------
     # Failure tests
     # ---------------------------------------------------------------
diff --git a/qt/python/mantidqt/widgets/src/_widgetscore.sip b/qt/python/mantidqt/widgets/src/_widgetscore.sip
index 75136926fb4..67752d51f85 100644
--- a/qt/python/mantidqt/widgets/src/_widgetscore.sip
+++ b/qt/python/mantidqt/widgets/src/_widgetscore.sip
@@ -65,7 +65,16 @@ public:
                QWidget *parent /TransferThis/ = 0) throw(std::invalid_argument);
 
   QString fileName() const;
+  SIP_PYTUPLE getSelection() const;
+%MethodCode
+  int lineFrom(-1), indexFrom(-1), lineTo(-1), indexTo(-1);
+  sipCpp->getSelection(&lineFrom, &indexFrom, &lineTo, &indexTo);
+  sipRes = sipBuildResult(nullptr, "(iiii)", lineFrom, indexFrom,
+                          lineTo, indexTo);
+%End
+  bool hasSelectedText() const;
   bool isReadOnly() const;
+  QString selectedText() const;
   QString text() const;
 
   void setCaretLineBackgroundColor (const QColor & col);
@@ -74,6 +83,7 @@ public:
   void setFont(const QFont &f);
   void setMarginWidth(int margin, int width);
   void setReadOnly(bool ro);
+  void setSelection(int lineFrom, int indexFrom, int lineTo, int indexTo);
   void setText(const QString &text);
 
 public slots:
-- 
GitLab