diff --git a/bec_widgets/widgets/editors/monaco/monaco_widget.py b/bec_widgets/widgets/editors/monaco/monaco_widget.py index efc8bd4e..b37de7db 100644 --- a/bec_widgets/widgets/editors/monaco/monaco_widget.py +++ b/bec_widgets/widgets/editors/monaco/monaco_widget.py @@ -6,8 +6,8 @@ import black import isort import qtmonaco from bec_lib.logger import bec_logger -from qtpy.QtCore import Signal -from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget +from PySide6.QtCore import Signal +from qtpy.QtWidgets import QApplication, QDialog, QVBoxLayout, QWidget from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.colors import get_theme_name @@ -59,6 +59,8 @@ class MonacoWidget(BECWidget, QWidget): self.editor.text_changed.connect(self.text_changed.emit) self.editor.text_changed.connect(self._check_save_status) self.editor.initialized.connect(self.apply_theme) + self.editor.initialized.connect(self._setup_context_menu) + self.editor.context_menu_action_triggered.connect(self._handle_context_menu_action) self._current_file = None self._original_content = "" @@ -109,7 +111,7 @@ class MonacoWidget(BECWidget, QWidget): content = self.get_text() try: formatted_content = black.format_str(content, mode=black.Mode(line_length=100)) - except black.NothingChanged: + except Exception: # black.NothingChanged or other formatting exceptions formatted_content = content config = isort.Config( @@ -288,6 +290,36 @@ class MonacoWidget(BECWidget, QWidget): """ return self.editor.get_lsp_header() + def _setup_context_menu(self): + """Setup custom context menu actions for the Monaco editor.""" + # Add the "Insert Scan" action to the context menu + self.editor.add_action("insert_scan", "Insert Scan", "python") + # Add the "Format Code" action to the context menu + self.editor.add_action("format_code", "Format Code", "python") + + def _handle_context_menu_action(self, action_id: str): + """Handle context menu action triggers.""" + if action_id == "insert_scan": + self._show_scan_control_dialog() + elif action_id == "format_code": + self._format_code() + + def _show_scan_control_dialog(self): + """Show the scan control dialog and insert the generated scan code.""" + # Import here to avoid circular imports + from bec_widgets.widgets.editors.monaco.scan_control_dialog import ScanControlDialog + + dialog = ScanControlDialog(self, client=self.client) + if dialog.exec_() == QDialog.DialogCode.Accepted: + scan_code = dialog.get_scan_code() + if scan_code: + # Insert the scan code at the current cursor position + self.insert_text(scan_code) + + def _format_code(self): + """Format the current code in the editor.""" + self.format() + if __name__ == "__main__": # pragma: no cover qapp = QApplication([]) diff --git a/bec_widgets/widgets/editors/monaco/scan_control_dialog.py b/bec_widgets/widgets/editors/monaco/scan_control_dialog.py new file mode 100644 index 00000000..7c3fa1df --- /dev/null +++ b/bec_widgets/widgets/editors/monaco/scan_control_dialog.py @@ -0,0 +1,167 @@ +""" +Scan Control Dialog for Monaco Editor + +This module provides a dialog wrapper around the ScanControl widget, +allowing users to configure and generate scan code that can be inserted +into the Monaco editor. +""" + +from bec_lib.device import Device +from PySide6.QtCore import QSize +from qtpy.QtCore import Qt +from qtpy.QtWidgets import QDialog, QDialogButtonBox, QPushButton, QVBoxLayout + +from bec_widgets.widgets.control.scan_control import ScanControl + + +class ScanControlDialog(QDialog): + """ + Dialog window containing the ScanControl widget for generating scan code. + + This dialog allows users to configure scan parameters and generates + Python code that can be inserted into the Monaco editor. + """ + + def __init__(self, parent=None, client=None): + super().__init__(parent) + self.setWindowTitle("Insert Scan") + + # Store the client for passing to ScanControl + self.client = client + self._scan_code = "" + + self._setup_ui() + + def sizeHint(self) -> QSize: + return QSize(600, 800) + + def _setup_ui(self): + """Setup the dialog UI with ScanControl widget and buttons.""" + layout = QVBoxLayout(self) + + # Create the scan control widget + self.scan_control = ScanControl(parent=self, client=self.client) + self.scan_control.show_scan_control_buttons(False) + layout.addWidget(self.scan_control) + + # Create dialog buttons + button_box = QDialogButtonBox(Qt.Orientation.Horizontal, self) + + # Create custom buttons with appropriate text + insert_button = QPushButton("Insert") + cancel_button = QPushButton("Cancel") + + button_box.addButton(insert_button, QDialogButtonBox.ButtonRole.AcceptRole) + button_box.addButton(cancel_button, QDialogButtonBox.ButtonRole.RejectRole) + + layout.addWidget(button_box) + + # Connect button signals + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + + def _generate_scan_code(self): + """Generate Python code for the configured scan.""" + try: + # Get scan parameters from the scan control widget + args, kwargs = self.scan_control.get_scan_parameters() + scan_name = self.scan_control.current_scan + + if not scan_name: + self._scan_code = "" + return + + # Process arguments and add device prefix where needed + processed_args = self._process_arguments_for_code_generation(args) + processed_kwargs = self._process_kwargs_for_code_generation(kwargs) + + # Generate the Python code string + code_parts = [] + + # Add scan function call + if processed_args and processed_kwargs: + # Format arguments + args_str = ", ".join(processed_args) + # Format keyword arguments + kwargs_str = ", ".join( + f"{k}={v}" for k, v in processed_kwargs.items() if k != "metadata" + ) + + if args_str and kwargs_str: + code_parts.append(f"scans.{scan_name}({args_str}, {kwargs_str})") + elif args_str: + code_parts.append(f"scans.{scan_name}({args_str})") + elif kwargs_str: + code_parts.append(f"scans.{scan_name}({kwargs_str})") + else: + code_parts.append(f"scans.{scan_name}()") + elif processed_args: + args_str = ", ".join(processed_args) + code_parts.append(f"scans.{scan_name}({args_str})") + elif processed_kwargs: + kwargs_str = ", ".join( + f"{k}={v}" for k, v in processed_kwargs.items() if k != "metadata" + ) + if kwargs_str: + code_parts.append(f"scans.{scan_name}({kwargs_str})") + else: + code_parts.append(f"scans.{scan_name}()") + else: + code_parts.append(f"scans.{scan_name}()") + + self._scan_code = "\n".join(code_parts) + + except Exception as e: + print(f"Error generating scan code: {e}") + self._scan_code = f"# Error generating scan code: {e}\n" + + def _process_arguments_for_code_generation(self, args): + """Process arguments to add device prefixes and proper formatting.""" + processed = [] + + for arg in args: + if isinstance(arg, Device): + processed.append(f"dev.{arg.name}") + else: + # Regular argument - format appropriately + processed.append(repr(arg)) + + return processed + + def _process_kwargs_for_code_generation(self, kwargs): + """Process keyword arguments to add device prefixes and proper formatting.""" + processed = {} + + for key, value in kwargs.items(): + + if isinstance(value, Device): + processed[key] = f"dev.{value.name}" + else: + processed[key] = repr(value) + + return processed + + def get_scan_code(self) -> str: + """ + Get the generated scan code. + + Returns: + str: The Python code for the configured scan. + """ + return self._scan_code + + def accept(self): + """Override accept to generate code before closing.""" + self._generate_scan_code() + super().accept() + + +if __name__ == "__main__": # pragma: no cover + import sys + + from qtpy.QtWidgets import QApplication + + app = QApplication(sys.argv) + dialog = ScanControlDialog() + dialog.show() + sys.exit(app.exec_())