0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 11:41:49 +02:00

doc: editor.py and toolbar.py documentation added

This commit is contained in:
wyzula-jan
2023-11-21 23:32:13 +01:00
parent 3cc05cde14
commit 3d9dc5c008
2 changed files with 140 additions and 6 deletions

View File

@ -20,7 +20,15 @@ from bec_widgets.widgets import ModularToolBar
class AutoCompleter(QThread): class AutoCompleter(QThread):
def __init__(self, file_path, api, enable_docstring=False): """Initializes the AutoCompleter thread for handling autocompletion and signature help.
Args:
file_path (str): The path to the file for which autocompletion is required.
api (QsciAPIs): The QScintilla API instance used for managing autocompletions.
enable_docstring (bool, optional): Flag to determine if docstrings should be included in the signatures.
"""
def __init__(self, file_path: str, api: QsciAPIs, enable_docstring: bool = False):
super(AutoCompleter, self).__init__(None) super(AutoCompleter, self).__init__(None)
self.file_path = file_path self.file_path = file_path
self.script: Script = None self.script: Script = None
@ -30,13 +38,20 @@ class AutoCompleter(QThread):
self.index = 0 self.index = 0
self.text = "" self.text = ""
# TODO so far disabled, quite buggy, docstring extraction has to be generalised
self.enable_docstring = enable_docstring self.enable_docstring = enable_docstring
def update_script(self, text: str): def update_script(self, text: str):
"""Updates the script for Jedi completion based on the current editor text.
Args:
text (str): The current text of the editor.
"""
if self.script is None or self.script.path != text: if self.script is None or self.script.path != text:
self.script = Script(text, path=self.file_path) self.script = Script(text, path=self.file_path)
def run(self): def run(self):
"""Runs the thread for generating autocompletions. Overrides QThread.run."""
self.update_script(self.text) self.update_script(self.text)
try: try:
self.completions = self.script.complete(self.line, self.index) self.completions = self.script.complete(self.line, self.index)
@ -46,6 +61,16 @@ class AutoCompleter(QThread):
self.finished.emit() self.finished.emit()
def get_function_signature(self, line: int, index: int, text: str) -> str: def get_function_signature(self, line: int, index: int, text: str) -> str:
"""Fetches the function signature for a given position in the text.
Args:
line (int): The line number in the editor.
index (int): The index (column number) in the line.
text (str): The current text of the editor.
Returns:
str: A string containing the function signature or an empty string if not available.
"""
self.update_script(text) self.update_script(text)
try: try:
signatures = self.script.get_signatures(line, index) signatures = self.script.get_signatures(line, index)
@ -59,19 +84,40 @@ class AutoCompleter(QThread):
print(f"Signature Error:{err}") print(f"Signature Error:{err}")
return "" return ""
def load_autocomplete(self, completions): def load_autocomplete(self, completions: list):
"""Loads the autocomplete suggestions into the QScintilla API.
Args:
completions (list[Completion]): A list of Completion objects to be added to the API.
"""
self.api.clear() self.api.clear()
[self.api.add(i.name) for i in completions] [self.api.add(i.name) for i in completions]
self.api.prepare() self.api.prepare()
def get_completions(self, line: int, index: int, text: str): def get_completions(self, line: int, index: int, text: str):
"""Starts the autocompletion process for a given position in the text.
Args:
line (int): The line number in the editor.
index (int): The index (column number) in the line.
text (str): The current text of the editor.
"""
self.line = line self.line = line
self.index = index self.index = index
self.text = text self.text = text
self.start() self.start()
def get_compact_docstring(self, full_docstring): def get_compact_docstring(self, full_docstring):
"""Generates a compact version of a function's docstring.
Args:
full_docstring (str): The full docstring of a function.
Returns:
str: A compact version of the docstring.
"""
lines = full_docstring.split("\n") lines = full_docstring.split("\n")
# TODO make it also for different docstring styles, now it is only for numpy style
cutoff_indices = [ cutoff_indices = [
i i
for i, line in enumerate(lines) for i, line in enumerate(lines)
@ -86,6 +132,12 @@ class AutoCompleter(QThread):
class ScriptRunnerThread(QThread): class ScriptRunnerThread(QThread):
"""Initializes the thread for running a Python script.
Args:
script (str): The script to be executed.
"""
outputSignal = Signal(str) outputSignal = Signal(str)
def __init__(self, script): def __init__(self, script):
@ -93,6 +145,7 @@ class ScriptRunnerThread(QThread):
self.script = script self.script = script
def run(self): def run(self):
"""Executes the script in a subprocess and emits output through a signal. Overrides QThread.run."""
process = subprocess.Popen( process = subprocess.Popen(
["python", "-u", "-c", self.script], ["python", "-u", "-c", self.script],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -114,12 +167,18 @@ class ScriptRunnerThread(QThread):
class BECEditor(QWidget): class BECEditor(QWidget):
"""Initializes the BEC Editor widget.
Args:
toolbar_enabled (bool, optional): Determines if the toolbar should be enabled. Defaults to True.
"""
def __init__(self, toolbar_enabled=True): def __init__(self, toolbar_enabled=True):
super().__init__() super().__init__()
self.scriptRunnerThread = None self.scriptRunnerThread = None
self.file_path = None self.file_path = None
# Flag to check if the file is a python file #TODO just temporary solution, could be extended to other languages # TODO just temporary solution, could be extended to other languages
self.is_python_file = True self.is_python_file = True
# Initialize the editor and terminal # Initialize the editor and terminal
@ -139,7 +198,7 @@ class BECEditor(QWidget):
self.splitter = QSplitter(Qt.Orientation.Vertical, self) self.splitter = QSplitter(Qt.Orientation.Vertical, self)
self.splitter.addWidget(self.editor) self.splitter.addWidget(self.editor)
self.splitter.addWidget(self.terminal) self.splitter.addWidget(self.terminal)
# self.splitter.setSizes([400, 100]) #TODO optional to set sizes self.splitter.setSizes([400, 200])
# Add Splitter to layout # Add Splitter to layout
self.layout.addWidget(self.splitter) self.layout.addWidget(self.splitter)
@ -148,6 +207,7 @@ class BECEditor(QWidget):
self.setup_editor() self.setup_editor()
def setup_editor(self): def setup_editor(self):
"""Sets up the editor with necessary configurations like lexer, auto indentation, and line numbers."""
# Set the lexer for Python # Set the lexer for Python
self.lexer = QsciLexerPython() self.lexer = QsciLexerPython()
self.editor.setLexer(self.lexer) self.editor.setLexer(self.lexer)
@ -176,12 +236,23 @@ class BECEditor(QWidget):
self.set_editor_style() self.set_editor_style()
def show_call_tip(self, position): def show_call_tip(self, position):
"""Shows a call tip at the given position in the editor.
Args:
position (int): The position in the editor where the call tip should be shown.
"""
line, index = self.editor.lineIndexFromPosition(position) line, index = self.editor.lineIndexFromPosition(position)
signature = self.auto_completer.get_function_signature(line + 1, index, self.editor.text()) signature = self.auto_completer.get_function_signature(line + 1, index, self.editor.text())
if signature: if signature:
self.editor.showUserList(1, [signature]) self.editor.showUserList(1, [signature])
def on_cursor_position_changed(self, line, index): def on_cursor_position_changed(self, line, index):
"""Handles the event of cursor position change in the editor.
Args:
line (int): The current line number where the cursor is.
index (int): The current column index where the cursor is.
"""
# if self.is_python_file: #TODO can be changed depending on supported languages # if self.is_python_file: #TODO can be changed depending on supported languages
# Get completions # Get completions
self.auto_completer.get_completions(line + 1, index, self.editor.text()) self.auto_completer.get_completions(line + 1, index, self.editor.text())
@ -192,10 +263,11 @@ class BECEditor(QWidget):
self.show_call_tip(position) self.show_call_tip(position)
def loaded_autocomplete(self): def loaded_autocomplete(self):
# Placeholder for any action after autocompletion data is loaded """Placeholder method for actions after autocompletion data is loaded."""
pass pass
def set_editor_style(self): def set_editor_style(self):
"""Sets the style and color scheme for the editor."""
# Dracula Theme Colors # Dracula Theme Colors
background_color = QColor("#282a36") background_color = QColor("#282a36")
text_color = QColor("#f8f8f2") text_color = QColor("#f8f8f2")
@ -236,19 +308,26 @@ class BECEditor(QWidget):
# TODO find better way how to do it! # TODO find better way how to do it!
for style in range( for style in range(
128 128
): # QsciScintilla supports 128 styles by default, this set all to transpatrent background ): # QsciScintilla supports 128 styles by default, this set all to transparent background
self.lexer.setPaper(background_color, style) self.lexer.setPaper(background_color, style)
def run_script(self): def run_script(self):
"""Runs the current script in the editor."""
script = self.editor.text() script = self.editor.text()
self.scriptRunnerThread = ScriptRunnerThread(script) self.scriptRunnerThread = ScriptRunnerThread(script)
self.scriptRunnerThread.outputSignal.connect(self.update_terminal) self.scriptRunnerThread.outputSignal.connect(self.update_terminal)
self.scriptRunnerThread.start() self.scriptRunnerThread.start()
def update_terminal(self, text): def update_terminal(self, text):
"""Updates the terminal with new text.
Args:
text (str): The text to be appended to the terminal.
"""
self.terminal.append(text) self.terminal.append(text)
def open_file(self): def open_file(self):
"""Opens a file dialog for selecting and opening a Python file in the editor."""
path, _ = QFileDialog.getOpenFileName(self, "Open file", "", "Python files (*.py)") path, _ = QFileDialog.getOpenFileName(self, "Open file", "", "Python files (*.py)")
if path: if path:
file = QFile(path) file = QFile(path)
@ -258,6 +337,7 @@ class BECEditor(QWidget):
file.close() file.close()
def save_file(self): def save_file(self):
"""Opens a save file dialog for saving the current script in the editor."""
path, _ = QFileDialog.getSaveFileName(self, "Save file", "", "Python files (*.py)") path, _ = QFileDialog.getSaveFileName(self, "Save file", "", "Python files (*.py)")
if path: if path:
file = QFile(path) file = QFile(path)

View File

@ -1,5 +1,6 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
# pylint: disable=no-name-in-module
from qtpy.QtCore import QSize from qtpy.QtCore import QSize
from qtpy.QtWidgets import QToolBar, QStyle, QApplication from qtpy.QtWidgets import QToolBar, QStyle, QApplication
from qtpy.QtCore import QTimer from qtpy.QtCore import QTimer
@ -10,11 +11,29 @@ from qtpy.QtWidgets import QWidget
class ToolBarAction(ABC): class ToolBarAction(ABC):
@abstractmethod @abstractmethod
def create(self, target: QWidget): def create(self, target: QWidget):
"""Creates and returns an action to be added to a toolbar.
This method must be implemented by subclasses.
Args:
target (QWidget): The widget that the action will target.
Returns:
QAction: The action created for the toolbar.
"""
pass pass
class OpenFileAction: # (ToolBarAction): class OpenFileAction: # (ToolBarAction):
def create(self, target: QWidget): def create(self, target: QWidget):
"""Creates an 'Open File' action for the toolbar.
Args:
target (QWidget): The widget that the 'Open File' action will be targeted.
Returns:
QAction: The 'Open File' action created for the toolbar.
"""
icon = QApplication.style().standardIcon(QStyle.StandardPixmap.SP_DialogOpenButton) icon = QApplication.style().standardIcon(QStyle.StandardPixmap.SP_DialogOpenButton)
action = QAction(icon, "Open File", target) action = QAction(icon, "Open File", target)
# action = QAction("Open File", target) # action = QAction("Open File", target)
@ -24,6 +43,14 @@ class OpenFileAction: # (ToolBarAction):
class SaveFileAction: class SaveFileAction:
def create(self, target): def create(self, target):
"""Creates a 'Save File' action for the toolbar.
Args:
target (QWidget): The widget that the 'Save File' action will be targeted.
Returns:
QAction: The 'Save File' action created for the toolbar.
"""
icon = QApplication.style().standardIcon(QStyle.StandardPixmap.SP_DialogSaveButton) icon = QApplication.style().standardIcon(QStyle.StandardPixmap.SP_DialogSaveButton)
action = QAction(icon, "Save File", target) action = QAction(icon, "Save File", target)
# action = QAction("Save File", target) # action = QAction("Save File", target)
@ -33,6 +60,14 @@ class SaveFileAction:
class RunScriptAction: class RunScriptAction:
def create(self, target): def create(self, target):
"""Creates a 'Run Script' action for the toolbar.
Args:
target (QWidget): The widget that the 'Run Script' action will be targeted.
Returns:
QAction: The 'Run Script' action created for the toolbar.
"""
icon = QApplication.style().standardIcon(QStyle.StandardPixmap.SP_MediaPlay) icon = QApplication.style().standardIcon(QStyle.StandardPixmap.SP_MediaPlay)
action = QAction(icon, "Run Script", target) action = QAction(icon, "Run Script", target)
# action = QAction("Run Script", target) # action = QAction("Run Script", target)
@ -41,6 +76,12 @@ class RunScriptAction:
class ModularToolBar(QToolBar): class ModularToolBar(QToolBar):
"""Modular toolbar with optional automatic initialization.
Args:
parent (QWidget, optional): The parent widget of the toolbar. Defaults to None.
auto_init (bool, optional): If True, automatically populates the toolbar based on the parent widget.
"""
def __init__(self, parent=None, auto_init=True): def __init__(self, parent=None, auto_init=True):
super().__init__(parent) super().__init__(parent)
self.auto_init = auto_init self.auto_init = auto_init
@ -56,6 +97,7 @@ class ModularToolBar(QToolBar):
QTimer.singleShot(0, self.auto_detect_and_populate) QTimer.singleShot(0, self.auto_detect_and_populate)
def auto_detect_and_populate(self): def auto_detect_and_populate(self):
"""Automatically detects the parent widget and populates the toolbar with relevant actions."""
if not self.auto_init: if not self.auto_init:
return return
@ -70,12 +112,24 @@ class ModularToolBar(QToolBar):
return return
def populate_toolbar(self, actions, target_widget): def populate_toolbar(self, actions, target_widget):
"""Populates the toolbar with a set of actions.
Args:
actions (list[ToolBarAction]): A list of action creators to populate the toolbar.
target_widget (QWidget): The widget that the actions will target.
"""
self.clear() self.clear()
for action_creator in actions: for action_creator in actions:
action = action_creator.create(target_widget) action = action_creator.create(target_widget)
self.addAction(action) self.addAction(action)
def set_manual_actions(self, actions, target_widget): def set_manual_actions(self, actions, target_widget):
"""Manually sets the actions for the toolbar.
Args:
actions (list[QAction or ToolBarAction]): A list of actions or action creators to populate the toolbar.
target_widget (QWidget): The widget that the actions will target.
"""
self.clear() self.clear()
for action in actions: for action in actions:
if isinstance(action, QAction): if isinstance(action, QAction):