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

refactor: editor.py signature tooltip process moved to AutoCompleter; simpler logic for signature tooltip

This commit is contained in:
wyzula-jan
2023-11-21 19:45:52 +01:00
parent 045b1baa60
commit d7a2c6830f
2 changed files with 61 additions and 109 deletions

View File

@ -1,12 +1,13 @@
import os import os
import subprocess import subprocess
import tempfile import tempfile
import qdarktheme
from qtpy.QtCore import Qt import qdarktheme
from qtpy.QtWidgets import QSplitter from jedi import Script
from jedi.api import Completion
from qtpy.Qsci import QsciScintilla, QsciLexerPython, QsciAPIs from qtpy.Qsci import QsciScintilla, QsciLexerPython, QsciAPIs
from qtpy.QtCore import QFile, QTextStream, Signal, QThread from qtpy.QtCore import QFile, QTextStream, Signal, QThread
from qtpy.QtCore import Qt
from qtpy.QtGui import QColor, QFont from qtpy.QtGui import QColor, QFont
from qtpy.QtWidgets import ( from qtpy.QtWidgets import (
QApplication, QApplication,
@ -15,9 +16,7 @@ from qtpy.QtWidgets import (
QVBoxLayout, QVBoxLayout,
QWidget, QWidget,
) )
from qtpy.QtWidgets import QSplitter
from jedi import Script
from jedi.api import Completion
from bec_widgets.widgets import ModularToolBar from bec_widgets.widgets import ModularToolBar
@ -43,6 +42,16 @@ class AutoCompleter(QThread):
self.finished.emit() self.finished.emit()
def get_function_signature(self, line: int, index: int, text: str) -> str:
try:
script = Script(text, path=self.file_path)
signatures = script.get_signatures(line, index)
if signatures:
return signatures[0].to_string()
except Exception as err:
print(err)
return ""
def load_autocomplete(self, completions): def load_autocomplete(self, completions):
self.api.clear() self.api.clear()
[self.api.add(i.name) for i in completions] [self.api.add(i.name) for i in completions]
@ -87,6 +96,7 @@ class BECEditor(QWidget):
def __init__(self, toolbar_enabled=True): def __init__(self, toolbar_enabled=True):
super().__init__() super().__init__()
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 # Flag to check if the file is a python file #TODO just temporary solution, could be extended to other languages
self.is_python_file = True self.is_python_file = True
@ -114,14 +124,12 @@ class BECEditor(QWidget):
self.layout.addWidget(self.splitter) self.layout.addWidget(self.splitter)
self.setLayout(self.layout) self.setLayout(self.layout)
self.setupEditor() self.setup_editor()
def setupEditor(self): def setup_editor(self):
# 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)
# Set up for call tips
self.editor.SendScintilla(QsciScintilla.SCI_SETMOUSEDWELLTIME, 500) # Example dwell time
# Enable auto indentation and competition within the editor # Enable auto indentation and competition within the editor
self.editor.setAutoIndent(True) self.editor.setAutoIndent(True)
@ -132,7 +140,7 @@ class BECEditor(QWidget):
# Autocomplete for python file # Autocomplete for python file
# Connect cursor position change signal for autocompletion # Connect cursor position change signal for autocompletion
self.editor.cursorPositionChanged.connect(self.onCursorPositionChanged) self.editor.cursorPositionChanged.connect(self.on_cursor_position_changed)
# 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
self.__api = QsciAPIs(self.lexer) self.__api = QsciAPIs(self.lexer)
@ -143,94 +151,37 @@ class BECEditor(QWidget):
self.editor.setMarginType(0, QsciScintilla.MarginType.NumberMargin) self.editor.setMarginType(0, QsciScintilla.MarginType.NumberMargin)
self.editor.setMarginWidth(0, "0000") # Adjust the width as needed self.editor.setMarginWidth(0, "0000") # Adjust the width as needed
# Set up call tips
# self.editor.setCallTipsStyle(QsciScintilla.CallTipsNo
# CallTipsNoContext)
# self.editor.setCallTipsVisible(0) # Show all applicable call tips
# self.editor.setCallTipsPosition(QsciScintilla.CallTipsBelowText)
# self.editor.setCallTipsBackgroundColor(QColor(0x20, 0x30, 0xFF, 0xFF))
# self.editor.setCallTipsForegroundColor(Qt.black)
# self.editor.setCallTipsHighlightColor(Qt.red)
#
# Connect signals for autocompletion and call tips
self.editor.cursorPositionChanged.connect(self.onCursorPositionChanged)
self.editor.SCN_CHARADDED.connect(self.onCharacterAdded)
# Additional UI elements like menu for load/save can be added here # Additional UI elements like menu for load/save can be added here
self.setEditorStyle() self.set_editor_style()
def onCharacterAdded(self, char_added): def show_call_tip(self, position):
# Check if the added character is an opening parenthesis for call tips line, index = self.editor.lineIndexFromPosition(position)
if chr(char_added) == "(": signature = self.auto_completer.get_function_signature(line + 1, index, self.editor.text())
cursor_line, cursor_index = self.editor.getCursorPosition() if signature:
self.showCallTip(cursor_line, cursor_index) self.editor.showUserList(1, [signature])
def create_temporary_file(self, content): def on_cursor_position_changed(self, line, index):
"""Creates a temporary file with the given content."""
# Create a new temporary file
with tempfile.NamedTemporaryFile(
delete=False, suffix=".py", mode="w+t", encoding="utf-8"
) as temp_file:
# Write the content to the temporary file
temp_file.write(content)
# The file is automatically closed when exiting the 'with' block
# Return the path of the temporary file
return temp_file.name
def showCallTip(self, line, index):
editor_text = self.editor.text()
line_text_up_to_cursor = editor_text.split("\n")[line][:index]
last_open_paren = line_text_up_to_cursor.rfind("(")
if last_open_paren != -1:
file_path = (
self.file_path if self.file_path else self.create_temporary_file(editor_text)
)
try:
script = Script(code=editor_text, path=file_path)
call_signatures = script.get_signatures(line + 1, index)
if call_signatures:
signature = call_signatures[0]
calltip = signature.to_string()
# Encode the call tip string to bytes
calltip_bytes = calltip.encode("utf-8")
# Show the call tip using sendScintilla
self.editor.SendScintilla(
QsciScintilla.SCI_CALLTIPSHOW,
self.editor.positionFromLineIndex(line, last_open_paren),
calltip_bytes,
)
except Exception as e:
print(f"Error getting calltip information: {e}")
finally:
if not self.file_path and file_path:
os.unlink(file_path)
def onCursorPositionChanged(self, line, index):
# 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
self.auto_completer.get_completions(line + 1, index, self.editor.text()) self.auto_completer.get_completions(line + 1, index, self.editor.text())
self.editor.autoCompleteFromAPIs() self.editor.autoCompleteFromAPIs()
# Call tip logic (you may need to adjust this logic based on when you want to show call tips) # Show call tip - signature
self.showCallTip(line, index) position = self.editor.positionFromLineIndex(line, index)
self.show_call_tip(position)
def loaded_autocomplete(self): def loaded_autocomplete(self):
# Placeholder for any action after autocompletion data is loaded # Placeholder for any action after autocompletion data is loaded
pass pass
def setEditorStyle(self): def set_editor_style(self):
# Dracula Theme Colors # Dracula Theme Colors
backgroundColor = QColor("#282a36") background_color = QColor("#282a36")
textColor = QColor("#f8f8f2") text_color = QColor("#f8f8f2")
keywordColor = QColor("#8be9fd") keyword_color = QColor("#8be9fd")
stringColor = QColor("#f1fa8c") string_color = QColor("#f1fa8c")
commentColor = QColor("#6272a4") comment_color = QColor("#6272a4")
classFunctionColor = QColor("#50fa7b") class_function_color = QColor("#50fa7b")
# Set Font # Set Font
font = QFont() font = QFont()
@ -240,42 +191,43 @@ class BECEditor(QWidget):
self.editor.setMarginsFont(font) self.editor.setMarginsFont(font)
# Set Editor Colors # Set Editor Colors
self.editor.setMarginsBackgroundColor(backgroundColor) self.editor.setMarginsBackgroundColor(background_color)
self.editor.setMarginsForegroundColor(textColor) self.editor.setMarginsForegroundColor(text_color)
self.editor.setCaretForegroundColor(textColor) self.editor.setCaretForegroundColor(text_color)
self.editor.setCaretLineBackgroundColor(QColor("#44475a")) self.editor.setCaretLineBackgroundColor(QColor("#44475a"))
self.editor.setPaper(backgroundColor) # Set the background color for the entire paper self.editor.setPaper(background_color) # Set the background color for the entire paper
self.editor.setColor(textColor) self.editor.setColor(text_color)
#
# Set editor
# Syntax Highlighting Colors # Syntax Highlighting Colors
lexer = self.editor.lexer() lexer = self.editor.lexer()
if lexer: if lexer:
lexer.setDefaultPaper(backgroundColor) # Set the background color for the text area lexer.setDefaultPaper(background_color) # Set the background color for the text area
lexer.setDefaultColor(textColor) lexer.setDefaultColor(text_color)
lexer.setColor(keywordColor, QsciLexerPython.Keyword) lexer.setColor(keyword_color, QsciLexerPython.Keyword)
lexer.setColor(stringColor, QsciLexerPython.DoubleQuotedString) lexer.setColor(string_color, QsciLexerPython.DoubleQuotedString)
lexer.setColor(stringColor, QsciLexerPython.SingleQuotedString) lexer.setColor(string_color, QsciLexerPython.SingleQuotedString)
lexer.setColor(commentColor, QsciLexerPython.Comment) lexer.setColor(comment_color, QsciLexerPython.Comment)
lexer.setColor(classFunctionColor, QsciLexerPython.ClassName) lexer.setColor(class_function_color, QsciLexerPython.ClassName)
lexer.setColor(classFunctionColor, QsciLexerPython.FunctionMethodName) lexer.setColor(class_function_color, QsciLexerPython.FunctionMethodName)
# Set the style for all text to have a transparent background # Set the style for all text to have a transparent background
# 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 transpatrent background
self.lexer.setPaper(backgroundColor, style) self.lexer.setPaper(background_color, style)
def runScript(self): def run_script(self):
script = self.editor.text() script = self.editor.text()
self.scriptRunnerThread = ScriptRunnerThread(script) self.scriptRunnerThread = ScriptRunnerThread(script)
self.scriptRunnerThread.outputSignal.connect(self.updateTerminal) self.scriptRunnerThread.outputSignal.connect(self.update_terminal)
self.scriptRunnerThread.start() self.scriptRunnerThread.start()
def updateTerminal(self, text): def update_terminal(self, text):
self.terminal.append(text) self.terminal.append(text)
def openFile(self): def open_file(self):
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)
@ -284,7 +236,7 @@ class BECEditor(QWidget):
self.editor.setText(text) self.editor.setText(text)
file.close() file.close()
def saveFile(self): def save_file(self):
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

@ -18,7 +18,7 @@ class OpenFileAction: # (ToolBarAction):
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)
action.triggered.connect(target.openFile) action.triggered.connect(target.open_file)
return action return action
@ -27,7 +27,7 @@ class SaveFileAction:
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)
action.triggered.connect(target.saveFile) action.triggered.connect(target.save_file)
return action return action
@ -36,7 +36,7 @@ class RunScriptAction:
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)
action.triggered.connect(target.runScript) action.triggered.connect(target.run_script)
return action return action