1
0
forked from IPKM/nmreval
added basic syntax check; closes #148

Co-authored-by: Dominik Demuth <dominik.demuth@physik.tu-darmstadt.de>
Reviewed-on: IPKM/nmreval#191
This commit is contained in:
Dominik Demuth 2023-12-28 14:30:33 +00:00
parent 2cf94af2c4
commit 694d47267d
7 changed files with 73 additions and 65 deletions

View File

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'resources/_ui/fitcreationdialog.ui' # Form implementation generated from reading ui file 'resources/_ui/fitcreationdialog.ui'
# #
# Created by: PyQt5 UI code generator 5.15.4 # Created by: PyQt5 UI code generator 5.15.10
# #
# WARNING: Any manual changes made to this file will be lost when pyuic5 is # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.
@ -51,9 +51,8 @@ class Ui_Dialog(object):
self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.namespace_box) self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.namespace_box)
self.verticalLayout_6.setObjectName("verticalLayout_6") self.verticalLayout_6.setObjectName("verticalLayout_6")
self.tabWidget.addTab(self.namespace_box, "") self.tabWidget.addTab(self.namespace_box, "")
self.plainTextEdit = CodeEditor(self.splitter) self.editor = EditorWidget(self.splitter)
self.plainTextEdit.setEnabled(True) self.editor.setObjectName("editor")
self.plainTextEdit.setObjectName("plainTextEdit")
self.verticalLayout.addWidget(self.splitter) self.verticalLayout.addWidget(self.splitter)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog) self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
@ -63,8 +62,8 @@ class Ui_Dialog(object):
self.retranslateUi(Dialog) self.retranslateUi(Dialog)
self.tabWidget.setCurrentIndex(0) self.tabWidget.setCurrentIndex(0)
self.buttonBox.accepted.connect(Dialog.accept) self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
self.buttonBox.rejected.connect(Dialog.reject) self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(Dialog) QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog): def retranslateUi(self, Dialog):
@ -74,4 +73,4 @@ class Ui_Dialog(object):
self.tabWidget.setTabText(self.tabWidget.indexOf(self.args_box), _translate("Dialog", "Variables")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.args_box), _translate("Dialog", "Variables"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.kwargs_box), _translate("Dialog", "Multiple choice")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.kwargs_box), _translate("Dialog", "Multiple choice"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.namespace_box), _translate("Dialog", "Available Functions")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.namespace_box), _translate("Dialog", "Available Functions"))
from ..lib.codeeditor import CodeEditor from ..lib.codeeditor import EditorWidget

View File

@ -2,9 +2,10 @@
# Form implementation generated from reading ui file 'resources/_ui/usermodeleditor.ui' # Form implementation generated from reading ui file 'resources/_ui/usermodeleditor.ui'
# #
# Created by: PyQt5 UI code generator 5.12.3 # Created by: PyQt5 UI code generator 5.15.10
# #
# WARNING! All changes made in this file will be lost! # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
@ -20,15 +21,12 @@ class Ui_MainWindow(object):
self.verticalLayout.setContentsMargins(3, 3, 3, 3) self.verticalLayout.setContentsMargins(3, 3, 3, 3)
self.verticalLayout.setSpacing(3) self.verticalLayout.setSpacing(3)
self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setObjectName("verticalLayout")
self.edit_field = CodeEditor(self.centralwidget) self.widget = EditorWidget(self.centralwidget)
font = QtGui.QFont() self.widget.setObjectName("widget")
font.setPointSize(10) self.verticalLayout.addWidget(self.widget)
self.edit_field.setFont(font)
self.edit_field.setObjectName("edit_field")
self.verticalLayout.addWidget(self.edit_field)
MainWindow.setCentralWidget(self.centralwidget) MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 30)) self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 20))
self.menubar.setObjectName("menubar") self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile") self.menuFile.setObjectName("menuFile")
@ -61,4 +59,4 @@ class Ui_MainWindow(object):
self.actionSave.setText(_translate("MainWindow", "Save")) self.actionSave.setText(_translate("MainWindow", "Save"))
self.actionSave_as.setText(_translate("MainWindow", "Save as...")) self.actionSave_as.setText(_translate("MainWindow", "Save as..."))
self.actionClose.setText(_translate("MainWindow", "Close")) self.actionClose.setText(_translate("MainWindow", "Close"))
from ..lib.codeeditor import CodeEditor from ..lib.codeeditor import EditorWidget

View File

@ -54,7 +54,7 @@ class QUserFitCreator(QtWidgets.QDialog, Ui_Dialog):
return self return self
def update_function(self): def update_function(self):
prev_text = self.plainTextEdit.toPlainText().split('\n') prev_text = self.editor.toPlainText().split('\n')
func_body = '' func_body = ''
in_body = False in_body = False
for line in prev_text: for line in prev_text:
@ -89,9 +89,12 @@ class QUserFitCreator(QtWidgets.QDialog, Ui_Dialog):
else: else:
k += f' def func(x):\n' k += f' def func(x):\n'
k += func_body if func_body:
k += func_body
else:
k += ' return x'
self.plainTextEdit.setPlainText(k) self.editor.setPlainText(k)
except Exception as e: except Exception as e:
QtWidgets.QMessageBox.warning(self, 'Failure', f'Error found: {e.args[0]}') QtWidgets.QMessageBox.warning(self, 'Failure', f'Error found: {e.args[0]}')
@ -215,7 +218,7 @@ class KwargsWidget(QtWidgets.QWidget):
for i in range(self.choices.count()): for i in range(self.choices.count()):
kwargs.append(self.choices.widget(i).get_strings()) kwargs.append(self.choices.widget(i).get_strings())
if kwargs: if kwargs:
return f" choices = {', '.join(kwargs)}\n" return f" choices = [{', '.join(kwargs)}]\n"
else: else:
return '' return ''
@ -475,12 +478,3 @@ class DescWidget(QtWidgets.QWidget):
f" equation = r'{self.eq_lineedit.text()}'\n" f" equation = r'{self.eq_lineedit.text()}'\n"
return stringi return stringi
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication([])
win = QUserFitCreator()
win.show()
sys.exit(app.exec())

View File

@ -72,7 +72,8 @@ class PythonHighlighter(QtGui.QSyntaxHighlighter):
(r'\bdef\b\s*(\w+)', 1, STYLES['defclass']), (r'\bdef\b\s*(\w+)', 1, STYLES['defclass']),
# 'class' followed by an identifier # 'class' followed by an identifier
(r'\bclass\b\s*(\w+)', 1, STYLES['defclass']), (r'\bclass\b\s*(\w+)', 1, STYLES['defclass']),
# @ followed by a word
# decorator @ followed by a word
(r'\s*@(\w+)\s*', 0, STYLES['property']), (r'\s*@(\w+)\s*', 0, STYLES['property']),
# Numeric literals # Numeric literals
@ -80,7 +81,6 @@ class PythonHighlighter(QtGui.QSyntaxHighlighter):
(r'\b[+-]?0[xX][\dA-Fa-f]+[lL]?\b', 0, STYLES['numbers']), (r'\b[+-]?0[xX][\dA-Fa-f]+[lL]?\b', 0, STYLES['numbers']),
(r'\b[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b', 0, STYLES['numbers']), (r'\b[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b', 0, STYLES['numbers']),
# Double-quoted string, possibly containing escape sequences # Double-quoted string, possibly containing escape sequences
(r'[rf]?"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']), (r'[rf]?"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']),
# Single-quoted string, possibly containing escape sequences # Single-quoted string, possibly containing escape sequences
@ -185,7 +185,6 @@ class CodeEditor(QtWidgets.QPlainTextEdit):
self.update_width_linenumber(0) self.update_width_linenumber(0)
self.highlight = PythonHighlighter(self.document()) self.highlight = PythonHighlighter(self.document())
self.textChanged.connect(self._check_syntax)
def keyPressEvent(self, evt): def keyPressEvent(self, evt):
if evt.key() == QtCore.Qt.Key_Tab: if evt.key() == QtCore.Qt.Key_Tab:
@ -263,22 +262,48 @@ class CodeEditor(QtWidgets.QPlainTextEdit):
self.setExtraSelections(extra_selections) self.setExtraSelections(extra_selections)
def color_line(self, color):
# is_valid, exception = self._check_syntax() class EditorWidget(QtWidgets.QWidget):
# if is_valid == 1: def __init__(self, parent=None):
doc = self.document() super().__init__(parent=parent)
print(doc.findBlockByLineNumber(color))
layout = QtWidgets.QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
self.editor = CodeEditor(self)
layout.addWidget(self.editor)
self.error_label = QtWidgets.QLabel(self)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.error_label.setFont(font)
self.error_label.setVisible(False)
layout.addWidget(self.error_label)
self.setLayout(layout)
for attr in ['appendPlainText', 'toPlainText', 'insertPlainText', 'setPlainText']:
setattr(self, attr, getattr(self.editor, attr))
self.editor.textChanged.connect(self._check_syntax)
def _check_syntax(self) -> (int, tuple[typing.Any]): def _check_syntax(self) -> (int, tuple[typing.Any]):
is_valid = True
# Compile into an AST and check for syntax errors. # Compile into an AST and check for syntax errors.
try: try:
_ = parse(self.toPlainText(), filename='<string>') _ = parse(self.toPlainText(), filename='<string>')
except SyntaxError as e:
print('SyntaxError', e, e.args[0], e.lineno, e.offset, e.text)
self.color_line(e.lineno)
return 1, (e.lineno, e.offset)
except Exception as e:
print('Unexpected error', e)
return 2, (e.args[0],)
return 0, tuple() except SyntaxError as e:
self.error_label.setText(f'Syntax error in line {e.lineno}: {e.args[0]}')
is_valid = False
except Exception as e:
self.error_label.setText(f'Unexpected error: {e.args[0]}')
is_valid = False
self.error_label.setVisible(not is_valid)

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from pathlib import Path from pathlib import Path
from ..Qt import QtWidgets, QtCore, QtGui from ..Qt import QtWidgets, QtCore, QtGui
from ..lib.codeeditor import CodeEditor from ..lib.codeeditor import EditorWidget
class QUsermodelEditor(QtWidgets.QMainWindow): class QUsermodelEditor(QtWidgets.QMainWindow):
@ -26,7 +26,7 @@ class QUsermodelEditor(QtWidgets.QMainWindow):
layout.setContentsMargins(3, 3, 3, 3) layout.setContentsMargins(3, 3, 3, 3)
layout.setSpacing(3) layout.setSpacing(3)
self.edit_field = CodeEditor(self.centralwidget) self.edit_field = EditorWidget(self.centralwidget)
font = QtGui.QFont('default') font = QtGui.QFont('default')
font.setStyleHint(font.Monospace) font.setStyleHint(font.Monospace)
font.setPointSize(10) font.setPointSize(10)

View File

@ -86,11 +86,7 @@
<layout class="QVBoxLayout" name="verticalLayout_6"/> <layout class="QVBoxLayout" name="verticalLayout_6"/>
</widget> </widget>
</widget> </widget>
<widget class="CodeEditor" name="plainTextEdit"> <widget class="EditorWidget" name="editor" native="true"/>
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</widget> </widget>
</item> </item>
<item> <item>
@ -107,9 +103,10 @@
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
<class>CodeEditor</class> <class>EditorWidget</class>
<extends>QPlainTextEdit</extends> <extends>QWidget</extends>
<header>..lib.codeeditor</header> <header>..lib.codeeditor</header>
<container>1</container>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>

View File

@ -31,13 +31,7 @@
<number>3</number> <number>3</number>
</property> </property>
<item> <item>
<widget class="CodeEditor" name="edit_field"> <widget class="EditorWidget" name="widget" native="true"/>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -47,7 +41,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>800</width>
<height>30</height> <height>20</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuFile"> <widget class="QMenu" name="menuFile">
@ -85,9 +79,10 @@
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
<class>CodeEditor</class> <class>EditorWidget</class>
<extends>QPlainTextEdit</extends> <extends>QWidget</extends>
<header>..lib.codeeditor</header> <header>..lib.codeeditor</header>
<container>1</container>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>