Skip to content

Commit

Permalink
Add server editor widget
Browse files Browse the repository at this point in the history
  • Loading branch information
ronpandolfi committed Apr 28, 2023
1 parent 1ad369b commit 3991502
Show file tree
Hide file tree
Showing 2 changed files with 246 additions and 4 deletions.
33 changes: 29 additions & 4 deletions tsuchinoko/widgets/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from tsuchinoko.utils.dependencies import check_dependencies
from tsuchinoko.widgets.debugmenubar import DebuggableMenuBar
from tsuchinoko.widgets.server_editor import ServerEditor

try:
from yaml import CLoader as Loader, CDumper as Dumper, dump, load
Expand Down Expand Up @@ -56,10 +57,12 @@ def __init__(self, core_address='localhost'):
open_parameters_action = QAction(self.style().standardIcon(QStyle.SP_DirOpenIcon), 'Open parameters...', parent=file_menu)
save_data_action = QAction(self.style().standardIcon(QStyle.SP_DialogSaveButton), 'Save data as...', parent=file_menu)
save_parameters_action = QAction(self.style().standardIcon(QStyle.SP_DialogSaveButton), 'Save parameters as...', parent=file_menu)
open_server_editor = QAction(self.style().standardIcon(QStyle.SP_DirOpenIcon), 'Open Server Editor', parent=file_menu)
file_menu.addAction(open_data_action)
file_menu.addAction(open_parameters_action)
file_menu.addAction(save_data_action)
file_menu.addAction(save_parameters_action)
file_menu.addAction(open_server_editor)
file_menu.addAction('E&xit', self.close)
self.setMenuBar(menubar)

Expand All @@ -76,6 +79,7 @@ def __init__(self, core_address='localhost'):
open_data_action.triggered.connect(self.open_data)
save_parameters_action.triggered.connect(self.save_parameters)
open_parameters_action.triggered.connect(self.open_parameters)
open_server_editor.triggered.connect(self.open_server_editor)

self.setWindowTitle('Tsuchinoko')
self.setWindowIcon(QIcon(path('tsuchinoko.png')))
Expand Down Expand Up @@ -348,7 +352,7 @@ def close_zmq(self):
self.context = None

def closeEvent(self, event):
if not self.close_demo():
if not self.close_demo(confirm=True):
event.ignore()
return
if self.data and len(self.data):
Expand All @@ -375,16 +379,31 @@ def start_demo(self, demo_key):
self.init_socket()

# if there's a child process server, exit it
if not self.close_demo():
if not self.close_demo(confirm=True):

Check warning on line 382 in tsuchinoko/widgets/mainwindow.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/mainwindow.py#L382

Added line #L382 was not covered by tests
return

suffix = Path(sys.executable).suffix
demo_exe = (Path(sys.executable).parent/'tsuchinoko_demo').with_suffix(suffix if suffix=='.exe' else '')
print(demo_exe)
# print(demo_exe)
self._server = subprocess.Popen([demo_exe, demo_key])

def close_demo(self):
def start_server(self, path):
suffix = Path(sys.executable).suffix
bootstrap_exe = (Path(sys.executable).parent / 'tsuchinoko_bootstrap').with_suffix(suffix if suffix == '.exe' else '')

Check warning on line 392 in tsuchinoko/widgets/mainwindow.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/mainwindow.py#L391-L392

Added lines #L391 - L392 were not covered by tests
# print(bootstrap_exe)
self._server = subprocess.Popen([bootstrap_exe, path])

Check warning on line 394 in tsuchinoko/widgets/mainwindow.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/mainwindow.py#L394

Added line #L394 was not covered by tests

def close_demo(self, confirm=False):
if self._server:
if confirm:
result = QMessageBox.question(self,

Check warning on line 399 in tsuchinoko/widgets/mainwindow.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/mainwindow.py#L398-L399

Added lines #L398 - L399 were not covered by tests
'Shutdown server?',
"A Tsuchinoko experiment server is currently running. Would you like to stop the server?",
buttons=QMessageBox.StandardButtons(
QMessageBox.Yes | QMessageBox.Cancel),
defaultButton=QMessageBox.Yes)
if result != QMessageBox.Yes:
return False

Check warning on line 406 in tsuchinoko/widgets/mainwindow.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/mainwindow.py#L405-L406

Added lines #L405 - L406 were not covered by tests
self.message_queue.put(ExitRequest())
try:
self._server.wait(3)
Expand All @@ -402,3 +421,9 @@ def close_demo(self):
elif result == QMessageBox.Cancel:
return False
return True

stop_server = close_demo

def open_server_editor(self):
self.editor_window = ServerEditor(main_window=self)
self.editor_window.show()

Check warning on line 429 in tsuchinoko/widgets/mainwindow.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/mainwindow.py#L428-L429

Added lines #L428 - L429 were not covered by tests
217 changes: 217 additions & 0 deletions tsuchinoko/widgets/server_editor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
from pathlib import Path

from qtpy.QtGui import QIcon, QKeySequence
from qtpy.QtWidgets import QAction, QStyle, QMessageBox, QFileDialog, QVBoxLayout
from pyqode.python.folding import PythonFoldDetector
from qtpy.QtWidgets import QWidget, QApplication
from qtpy.QtCore import Qt
from pyqode.core import panels, api, modes
from pyqode.python import widgets, panels as pypanels, modes as pymodes
from pyqode.python.backend import server

from tsuchinoko import assets
from tsuchinoko.examples import server_demo
from pyqode.python.backend.workers import defined_names
from pyqode.python import managers as pymanagers

import inspect

from tsuchinoko.widgets.debugmenubar import DebuggableMenuBar


class PythonEditor(widgets.PyCodeEditBase):
def __init__(self):
super(PythonEditor, self).__init__()

Check warning on line 24 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L24

Added line #L24 was not covered by tests

# starts the default pyqode.python server (which enable the jedi code
# completion worker).
self.backend.start(server.__file__)

Check warning on line 28 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L28

Added line #L28 was not covered by tests

# some other modes/panels require the analyser mode, the best is to
# install it first
self.modes.append(modes.OutlineMode(defined_names))

Check warning on line 32 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L32

Added line #L32 was not covered by tests

# --- core panels
self.panels.append(panels.FoldingPanel())
self.panels.append(panels.LineNumberPanel())
self.panels.append(panels.CheckerPanel())
self.panels.append(panels.SearchAndReplacePanel(),

Check warning on line 38 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L35-L38

Added lines #L35 - L38 were not covered by tests
panels.SearchAndReplacePanel.Position.BOTTOM)
self.panels.append(panels.EncodingPanel(), api.Panel.Position.TOP)

Check warning on line 40 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L40

Added line #L40 was not covered by tests
# add a context menu separator between editor's
# builtin action and the python specific actions
self.add_separator()

Check warning on line 43 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L43

Added line #L43 was not covered by tests

# --- python specific panels
self.panels.append(pypanels.QuickDocPanel(), api.Panel.Position.BOTTOM)

Check warning on line 46 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L46

Added line #L46 was not covered by tests

# --- core modes
self.modes.append(modes.CaretLineHighlighterMode())
self.modes.append(modes.CodeCompletionMode())
self.modes.append(modes.ExtendedSelectionMode())
self.modes.append(modes.FileWatcherMode())
self.modes.append(modes.OccurrencesHighlighterMode())
self.modes.append(modes.RightMarginMode())
self.modes.append(modes.SmartBackSpaceMode())
self.modes.append(modes.SymbolMatcherMode())
self.modes.append(modes.ZoomMode())

Check warning on line 57 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L49-L57

Added lines #L49 - L57 were not covered by tests
# self.modes.append(modes.PygmentsSyntaxHighlighter(self.document()))

# --- python specific modes
self.modes.append(pymodes.CommentsMode())
self.modes.append(pymodes.CalltipsMode())
self.modes.append(pymodes.PyFlakesChecker())
self.modes.append(pymodes.PEP8CheckerMode())
self.modes.append(pymodes.PyAutoCompleteMode())
self.modes.append(pymodes.PyAutoIndentMode())
self.modes.append(pymodes.PyIndenterMode())
self.modes.append(pymodes.PythonSH(self.document()))
self.syntax_highlighter.fold_detector = PythonFoldDetector()

Check warning on line 69 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L61-L69

Added lines #L61 - L69 were not covered by tests

self.syntax_highlighter.color_scheme = api.ColorScheme('darcula')
self.save_on_focus_out = True

Check warning on line 72 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L71-L72

Added lines #L71 - L72 were not covered by tests


class ServerEditor(QWidget):
def __init__(self, main_window):
super().__init__()

Check warning on line 77 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L77

Added line #L77 was not covered by tests

self.main_window = main_window
self.editor = PythonEditor()

Check warning on line 80 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L79-L80

Added lines #L79 - L80 were not covered by tests

menubar = DebuggableMenuBar()
file_menu = menubar.addMenu("&File")
file_menu.addAction('&New', self.new)
open_action = QAction(self.style().standardIcon(QStyle.SP_DirOpenIcon), 'Open...', parent=file_menu)
save_action = QAction(self.style().standardIcon(QStyle.SP_DialogSaveButton), 'Save',

Check warning on line 86 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L82-L86

Added lines #L82 - L86 were not covered by tests
parent=file_menu)
save_action.setShortcut(QKeySequence(Qt.CTRL | Qt.Key_S))
save_as_action = QAction(self.style().standardIcon(QStyle.SP_DialogSaveButton), 'Save As...',

Check warning on line 89 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L88-L89

Added lines #L88 - L89 were not covered by tests
parent=file_menu)
server_menu = menubar.addMenu("&Server")
start_server = QAction(self.style().standardIcon(QStyle.SP_MediaPlay), 'Start Server', parent=server_menu)
stop_server = QAction(self.style().standardIcon(QStyle.SP_MediaStop), 'Stop Server', parent=server_menu)

Check warning on line 93 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L91-L93

Added lines #L91 - L93 were not covered by tests

file_menu.addAction(open_action)
file_menu.addAction(save_action)
file_menu.addAction(save_as_action)
file_menu.addAction('E&xit', self.exit)
server_menu.addAction(start_server)
server_menu.addAction(stop_server)

Check warning on line 100 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L95-L100

Added lines #L95 - L100 were not covered by tests

open_action.triggered.connect(self.open)
save_action.triggered.connect(self.save)
save_as_action.triggered.connect(self.save_as)
start_server.triggered.connect(self.start_server)
stop_server.triggered.connect(self.stop_server)

Check warning on line 106 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L102-L106

Added lines #L102 - L106 were not covered by tests

self.setWindowTitle('Tsuchinoko Server Editor')
self.setWindowIcon(QIcon(assets.path('tsuchinoko.png')))
self.resize(1700, 1000)

Check warning on line 110 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L108-L110

Added lines #L108 - L110 were not covered by tests

self.has_unsaved_changes = False
self.editor.textChanged.connect(self._set_has_changes)

Check warning on line 113 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L112-L113

Added lines #L112 - L113 were not covered by tests

QApplication.instance().aboutToQuit.connect(self.close_by_quit) # TODO: use this approach in Xi-cam

Check warning on line 115 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L115

Added line #L115 was not covered by tests

layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(menubar)
layout.addWidget(self.editor)
self.setLayout(layout)

Check warning on line 122 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L117-L122

Added lines #L117 - L122 were not covered by tests

self.new()
self.editor.setPlainText(inspect.getsource(server_demo))

Check warning on line 125 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L124-L125

Added lines #L124 - L125 were not covered by tests

def _set_has_changes(self):
self.has_unsaved_changes = True

Check warning on line 128 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L128

Added line #L128 was not covered by tests

def _prompt_unsaved_changes(self, buttons=QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel):
result = None
if self.has_unsaved_changes:
result = QMessageBox.question(self,

Check warning on line 133 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L131-L133

Added lines #L131 - L133 were not covered by tests
'Unsaved changes',
f"Do you want to save changes to {Path(self.editor.file.path).name or 'Untitled'}?",
buttons=QMessageBox.StandardButtons(buttons),
defaultButton=QMessageBox.Yes)

if not result:
raise ValueError("WHAT?!")

Check warning on line 140 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L139-L140

Added lines #L139 - L140 were not covered by tests

return result

Check warning on line 142 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L142

Added line #L142 was not covered by tests

def _prompt_save_path(self):
name, filter = QFileDialog.getSaveFileName(filter=("Python (*.py)"))
if not name:
return False
return name

Check warning on line 148 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L145-L148

Added lines #L145 - L148 were not covered by tests

def new(self):
if self._prompt_unsaved_changes() == QMessageBox.Cancel:
return False
self.editor.file = pymanagers.PyFileManager(self.editor)
self.editor.setPlainText('')
self.has_unsaved_changes = False
return True

Check warning on line 156 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L151-L156

Added lines #L151 - L156 were not covered by tests

def open(self):
if self._prompt_unsaved_changes() == QMessageBox.Cancel:
return False
name, filter = QFileDialog.getOpenFileName(filter=("Python (*.py)"))
if not name:
return False
self.editor.file = pymanagers.PyFileManager(self.editor)
self.editor.file.open(name)
self.has_unsaved_changes = False
return True

Check warning on line 167 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L159-L167

Added lines #L159 - L167 were not covered by tests
# with open(name, 'r') as f:
# state = f.read()
# self.editor.setPlainText(state)

def save(self):
path = self.editor.file.path
return self.save_as(path)

Check warning on line 174 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L173-L174

Added lines #L173 - L174 were not covered by tests

def save_as(self, path=None):
if not path:
path = self._prompt_save_path()
if not path: return False

Check warning on line 179 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L177-L179

Added lines #L177 - L179 were not covered by tests

# state = self.editor.toPlainText()
# with open(path, 'w') as f:
# f.write(state)

self.editor.file.save(path)
self.has_unsaved_changes = False

Check warning on line 186 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L185-L186

Added lines #L185 - L186 were not covered by tests
# self.path = path
return True

Check warning on line 188 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L188

Added line #L188 was not covered by tests

def exit(self, buttons=QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel):
if self._prompt_unsaved_changes(buttons) == QMessageBox.Cancel:
return False

Check warning on line 192 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L191-L192

Added lines #L191 - L192 were not covered by tests

self.editor.file.close()
self.editor.backend.stop()
self.has_unsaved_changes = False
self.close()

Check warning on line 197 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L194-L197

Added lines #L194 - L197 were not covered by tests

return True

Check warning on line 199 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L199

Added line #L199 was not covered by tests

def closeEvent(self, event):
if self.exit():
event.accept() # let the window close

Check warning on line 203 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L202-L203

Added lines #L202 - L203 were not covered by tests
else:
event.ignore()

Check warning on line 205 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L205

Added line #L205 was not covered by tests

def close_by_quit(self):
self.exit(buttons=QMessageBox.Yes | QMessageBox.No)

Check warning on line 208 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L208

Added line #L208 was not covered by tests

def start_server(self):
if self._prompt_unsaved_changes(buttons=QMessageBox.Yes | QMessageBox.Cancel) == QMessageBox.Cancel:
return False
self.save()
self.main_window.start_server(path=self.editor.file.path)

Check warning on line 214 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L211-L214

Added lines #L211 - L214 were not covered by tests

def stop_server(self):
self.main_window.stop_server(confirm=True)

Check warning on line 217 in tsuchinoko/widgets/server_editor.py

View check run for this annotation

Codecov / codecov/patch

tsuchinoko/widgets/server_editor.py#L217

Added line #L217 was not covered by tests

0 comments on commit 3991502

Please sign in to comment.