From 599304dabb878ec2b356f946335522345c87ab6b Mon Sep 17 00:00:00 2001 From: AlexandreSajus Date: Wed, 8 Dec 2021 21:34:31 +0100 Subject: [PATCH 1/2] :tada: Adds the ability to cancel execution --- opencodeblocks/blocks/codeblock.py | 100 ++++++++++++++++++----------- opencodeblocks/graphics/kernel.py | 3 +- opencodeblocks/graphics/worker.py | 3 + 3 files changed, 69 insertions(+), 37 deletions(-) diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index ae8c9fc1..13c1bbdd 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -82,14 +82,16 @@ def init_run_button(self): """Initialize the run button""" run_button = QPushButton(">", self.root) run_button.move(int(self.edge_size), int(self.edge_size / 2)) - run_button.setFixedSize(int(3 * self.edge_size), int(3 * self.edge_size)) + run_button.setFixedSize(int(3 * self.edge_size), + int(3 * self.edge_size)) run_button.clicked.connect(self.run_left) return run_button def init_run_all_button(self): """Initialize the run all button""" run_all_button = QPushButton(">>", self.root) - run_all_button.setFixedSize(int(3 * self.edge_size), int(3 * self.edge_size)) + run_all_button.setFixedSize( + int(3 * self.edge_size), int(3 * self.edge_size)) run_all_button.clicked.connect(self.run_right) run_all_button.raise_() @@ -132,48 +134,74 @@ def has_output(self) -> bool: return True return False + def _interrupt_execution(self): + """ Interrupt an execution, reset the blocks in the queue """ + for block, _ in self.source_editor.kernel.execution_queue: + # Reset the blocks that have not been run + block.reset_buttons() + block.has_been_run = False + # Clear the queue + self.source_editor.kernel.execution_queue = [] + # Interrupt the kernel + self.source_editor.kernel.kernel_manager.interrupt_kernel() + def run_left(self, in_right_button=False): """ Run all of the block's dependencies and then run the block """ - # If no dependencies - if not self.has_input(): - return self.run_code() - - # Create the graph from the scene - graph = self.scene().create_graph() - # BFS through the input graph - edges = bfs_edges(graph, self, reverse=True) - # Run the blocks found except self - blocks_to_run: List["OCBCodeBlock"] = [v for _, v in edges] - for block in blocks_to_run[::-1]: - if not block.has_been_run: - block.run_code() - - if in_right_button: - # If run_left was called inside of run_right - # self is not necessarily the block that was clicked - # which means that self does not need to be run - if not self.has_been_run: - self.run_code() + # If the user presses left run when running, cancel the execution + if self.run_button.text() == "..." and not in_right_button: + self._interrupt_execution() + else: - # On the contrary if run_left was called outside of run_right - # self is the block that was clicked - # so self needs to be run - self.run_code() + # If no dependencies + if not self.has_input(): + return self.run_code() + + # Create the graph from the scene + graph = self.scene().create_graph() + # BFS through the input graph + edges = bfs_edges(graph, self, reverse=True) + # Run the blocks found except self + blocks_to_run: List["OCBCodeBlock"] = [v for _, v in edges] + for block in blocks_to_run[::-1]: + if not block.has_been_run: + block.run_code() + + if in_right_button: + # If run_left was called inside of run_right + # self is not necessarily the block that was clicked + # which means that self does not need to be run + if not self.has_been_run: + self.run_code() + else: + # On the contrary if run_left was called outside of run_right + # self is the block that was clicked + # so self needs to be run + self.run_code() def run_right(self): """Run all of the output blocks and all their dependencies""" - # If no output, run left - if not self.has_output(): - return self.run_left(in_right_button=True) - - # Same as run_left but instead of running the blocks, we'll use run_left - graph = self.scene().create_graph() - edges = bfs_edges(graph, self) - blocks_to_run: List["OCBCodeBlock"] = [self] + [v for _, v in edges] - for block in blocks_to_run[::-1]: - block.run_left(in_right_button=True) + # If the user presses right run when running, cancel the execution + if self.run_all_button.text() == "...": + self._interrupt_execution() + + else: + # If no output, run left + if not self.has_output(): + return self.run_left(in_right_button=True) + + # Same as run_left but instead of running the blocks, we'll use run_left + graph = self.scene().create_graph() + edges = bfs_edges(graph, self) + blocks_to_run: List["OCBCodeBlock"] = [ + self] + [v for _, v in edges] + for block in blocks_to_run[::-1]: + block.run_left(in_right_button=True) + + def reset_has_been_run(self): + """ Reset has_been_run, is called when the output is an error """ + self.has_been_run = False def update_title(self): """Change the geometry of the title widget""" diff --git a/opencodeblocks/graphics/kernel.py b/opencodeblocks/graphics/kernel.py index 7563d9fa..a4c4fcfe 100644 --- a/opencodeblocks/graphics/kernel.py +++ b/opencodeblocks/graphics/kernel.py @@ -43,7 +43,7 @@ def message_to_output(self, message: dict) -> Tuple[str, str]: # output a print (print("Hello World")) out = message['text'] elif 'traceback' in message: - message_type = 'text' + message_type = 'error' # output an error out = '\n'.join(message['traceback']) else: @@ -65,6 +65,7 @@ def run_block(self, block, code: str): worker.signals.image.connect(block.handle_image) worker.signals.finished.connect(self.run_queue) worker.signals.finished_block.connect(block.reset_buttons) + worker.signals.error.connect(block.reset_has_been_run) block.source_editor.threadpool.start(worker) def run_queue(self): diff --git a/opencodeblocks/graphics/worker.py b/opencodeblocks/graphics/worker.py index 93a3b5d2..abb6185a 100644 --- a/opencodeblocks/graphics/worker.py +++ b/opencodeblocks/graphics/worker.py @@ -13,6 +13,7 @@ class WorkerSignals(QObject): image = pyqtSignal(str) finished = pyqtSignal() finished_block = pyqtSignal() + error = pyqtSignal() class Worker(QRunnable): @@ -40,6 +41,8 @@ async def run_code(self): self.signals.stdout.emit(output) elif output_type == 'image': self.signals.image.emit(output) + elif output_type == 'error': + self.signals.error.emit() self.signals.finished.emit() self.signals.finished_block.emit() From 42f54e061ba1b8b5e104bfc0c79d3010b77ce31d Mon Sep 17 00:00:00 2001 From: AlexandreSajus Date: Thu, 9 Dec 2021 15:32:26 +0100 Subject: [PATCH 2/2] :wrench: refactor --- opencodeblocks/blocks/codeblock.py | 78 +++++++++++++++--------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/opencodeblocks/blocks/codeblock.py b/opencodeblocks/blocks/codeblock.py index 13c1bbdd..87619798 100644 --- a/opencodeblocks/blocks/codeblock.py +++ b/opencodeblocks/blocks/codeblock.py @@ -152,52 +152,52 @@ def run_left(self, in_right_button=False): # If the user presses left run when running, cancel the execution if self.run_button.text() == "..." and not in_right_button: self._interrupt_execution() - - else: - # If no dependencies - if not self.has_input(): - return self.run_code() - - # Create the graph from the scene - graph = self.scene().create_graph() - # BFS through the input graph - edges = bfs_edges(graph, self, reverse=True) - # Run the blocks found except self - blocks_to_run: List["OCBCodeBlock"] = [v for _, v in edges] - for block in blocks_to_run[::-1]: - if not block.has_been_run: - block.run_code() - - if in_right_button: - # If run_left was called inside of run_right - # self is not necessarily the block that was clicked - # which means that self does not need to be run - if not self.has_been_run: - self.run_code() - else: - # On the contrary if run_left was called outside of run_right - # self is the block that was clicked - # so self needs to be run + return + + # If no dependencies + if not self.has_input(): + return self.run_code() + + # Create the graph from the scene + graph = self.scene().create_graph() + # BFS through the input graph + edges = bfs_edges(graph, self, reverse=True) + # Run the blocks found except self + blocks_to_run: List["OCBCodeBlock"] = [v for _, v in edges] + for block in blocks_to_run[::-1]: + if not block.has_been_run: + block.run_code() + + if in_right_button: + # If run_left was called inside of run_right + # self is not necessarily the block that was clicked + # which means that self does not need to be run + if not self.has_been_run: self.run_code() + else: + # On the contrary if run_left was called outside of run_right + # self is the block that was clicked + # so self needs to be run + self.run_code() def run_right(self): """Run all of the output blocks and all their dependencies""" # If the user presses right run when running, cancel the execution if self.run_all_button.text() == "...": self._interrupt_execution() - - else: - # If no output, run left - if not self.has_output(): - return self.run_left(in_right_button=True) - - # Same as run_left but instead of running the blocks, we'll use run_left - graph = self.scene().create_graph() - edges = bfs_edges(graph, self) - blocks_to_run: List["OCBCodeBlock"] = [ - self] + [v for _, v in edges] - for block in blocks_to_run[::-1]: - block.run_left(in_right_button=True) + return + + # If no output, run left + if not self.has_output(): + return self.run_left(in_right_button=True) + + # Same as run_left but instead of running the blocks, we'll use run_left + graph = self.scene().create_graph() + edges = bfs_edges(graph, self) + blocks_to_run: List["OCBCodeBlock"] = [ + self] + [v for _, v in edges] + for block in blocks_to_run[::-1]: + block.run_left(in_right_button=True) def reset_has_been_run(self): """ Reset has_been_run, is called when the output is an error """