From c88b89b46ee9bc09727923b2e1e7c0d49e73dee3 Mon Sep 17 00:00:00 2001 From: Jeremy Wright Date: Sat, 27 Dec 2014 13:01:27 -0500 Subject: [PATCH] Updated PyQode to automatically reload and execute when an external editor writes to disk. --- CadQuery/Libs/pyqode/core/__init__.py | 2 +- CadQuery/Libs/pyqode/core/api/client.py | 29 ++++++++------ CadQuery/Libs/pyqode/core/api/code_edit.py | 2 + CadQuery/Libs/pyqode/core/backend/server.py | 2 +- CadQuery/Libs/pyqode/core/managers/backend.py | 21 +++++++--- .../Libs/pyqode/core/modes/filewatcher.py | 3 +- .../core/widgets/filesystem_treeview.py | 38 +++++++++++-------- .../Libs/pyqode/core/widgets/menu_recents.py | 4 +- 8 files changed, 66 insertions(+), 35 deletions(-) diff --git a/CadQuery/Libs/pyqode/core/__init__.py b/CadQuery/Libs/pyqode/core/__init__.py index 4e2446e..8ac5097 100644 --- a/CadQuery/Libs/pyqode/core/__init__.py +++ b/CadQuery/Libs/pyqode/core/__init__.py @@ -8,4 +8,4 @@ a series of modes and panels that might be useful for any kind of code editor widget, i.e. pyqode.core is a generic code editor widget. """ -__version__ = '2.5.dev' +__version__ = '2.5.dev1' diff --git a/CadQuery/Libs/pyqode/core/api/client.py b/CadQuery/Libs/pyqode/core/api/client.py index 93754ff..7def9d7 100644 --- a/CadQuery/Libs/pyqode/core/api/client.py +++ b/CadQuery/Libs/pyqode/core/api/client.py @@ -80,12 +80,17 @@ class JsonTcpClient(QtNetwork.QTcpSocket): self._data_buf = bytes() self._callback = on_receive self.is_connected = False + self._closed = False self.connected.connect(self._on_connected) self.error.connect(self._on_error) self.disconnected.connect(self._on_disconnected) self.readyRead.connect(self._on_ready_read) self._connect() + def close(self): + self._closed = True # fix issue with QTimer.singleShot + super(JsonTcpClient, self).close() + def _send_request(self): """ Sends the request to the backend. @@ -139,10 +144,17 @@ class JsonTcpClient(QtNetwork.QTcpSocket): def _on_error(self, error): if error not in SOCKET_ERROR_STRINGS: # pragma: no cover error = -1 - _logger().debug(SOCKET_ERROR_STRINGS[error]) - if error == 0: + if error == 1 and self.is_connected or ( + not self.is_connected and error == 0 and not self._closed): + log_fct = _logger().debug + else: + log_fct = _logger().warning + + if error == 0 and not self.is_connected and not self._closed: QtCore.QTimer.singleShot(100, self._connect) + log_fct(SOCKET_ERROR_STRINGS[error]) + def _on_disconnected(self): try: _logger().debug('disconnected from backend: %s:%d', @@ -222,7 +234,7 @@ class BackendProcess(QtCore.QProcess): self.running = False self.starting = True self._srv_logger = logging.getLogger('pyqode.backend') - self._test_not_deleted = False + self._prevent_logs = False def _on_process_started(self): """ Logs process started """ @@ -234,18 +246,13 @@ class BackendProcess(QtCore.QProcess): """ Logs process error """ if error not in PROCESS_ERROR_STRING: error = -1 - try: - self._test_not_deleted - except AttributeError: - pass - else: - if self.running: - _logger().debug(PROCESS_ERROR_STRING[error]) + if not self._prevent_logs: + _logger().warning(PROCESS_ERROR_STRING[error]) def _on_process_finished(self, exit_code): """ Logs process exit status """ _logger().debug('backend process finished with exit code %d', - exit_code) + exit_code) try: self.running = False except AttributeError: diff --git a/CadQuery/Libs/pyqode/core/api/code_edit.py b/CadQuery/Libs/pyqode/core/api/code_edit.py index 1188708..cf02c38 100644 --- a/CadQuery/Libs/pyqode/core/api/code_edit.py +++ b/CadQuery/Libs/pyqode/core/api/code_edit.py @@ -512,6 +512,8 @@ class CodeEdit(QtWidgets.QPlainTextEdit): list(self.panels), list(clone.panels)): panel.enabled = original_panel.isEnabled() panel.clone_settings(original_panel) + if not original_panel.isVisible(): + panel.setVisible(False) clone.use_spaces_instead_of_tabs = self.use_spaces_instead_of_tabs clone.tab_length = self.tab_length clone.save_on_focus_out = self.save_on_focus_out diff --git a/CadQuery/Libs/pyqode/core/backend/server.py b/CadQuery/Libs/pyqode/core/backend/server.py index d24675f..6e33619 100644 --- a/CadQuery/Libs/pyqode/core/backend/server.py +++ b/CadQuery/Libs/pyqode/core/backend/server.py @@ -136,7 +136,7 @@ class JsonServer(socketserver.ThreadingTCPServer): ret_val = worker(data['data']) except Exception: _logger().exception('something went bad with worker ' - '%r(data=%r)') + '%r(data=%r)', worker, data['data']) ret_val = None if ret_val is None: ret_val = [] diff --git a/CadQuery/Libs/pyqode/core/managers/backend.py b/CadQuery/Libs/pyqode/core/managers/backend.py index cabbdf4..ab46e33 100644 --- a/CadQuery/Libs/pyqode/core/managers/backend.py +++ b/CadQuery/Libs/pyqode/core/managers/backend.py @@ -86,8 +86,8 @@ class BackendManager(Manager): if error_callback: self._process.error.connect(error_callback) self._process.start(program, pgm_args) - _logger().debug('starting backend process: %s %s', program, - ' '.join(pgm_args)) + _logger().info('starting backend process: %s %s', program, + ' '.join(pgm_args)) def stop(self): """ @@ -95,11 +95,16 @@ class BackendManager(Manager): """ if self._process is None: return + _logger().debug('stopping backend process') # close all sockets for socket in self._sockets: socket._callback = None socket.close() - t = time.time() + + self._sockets[:] = [] + # prevent crash logs from being written if we are busy killing + # the process + self._process._prevent_logs = True while self._process.state() != self._process.NotRunning: self._process.waitForFinished(1) if sys.platform == 'win32': @@ -109,7 +114,7 @@ class BackendManager(Manager): self._process.kill() else: self._process.terminate() - _logger().info('stopping backend took %f [s]', time.time() - t) + self._process._prevent_logs = False _logger().info('backend process terminated') def send_request(self, worker_class_or_function, args, on_receive=None): @@ -128,6 +133,8 @@ class BackendManager(Manager): if not self.running: raise NotRunning() else: + _logger().info('sending request, worker=%r' % + worker_class_or_function) # create a socket, the request will be send as soon as the socket # has connected socket = JsonTcpClient( @@ -137,7 +144,11 @@ class BackendManager(Manager): self._sockets.append(socket) def _rm_socket(self, socket): - self._sockets.remove(socket) + try: + socket.close() + self._sockets.remove(socket) + except ValueError: + pass @property def running(self): diff --git a/CadQuery/Libs/pyqode/core/modes/filewatcher.py b/CadQuery/Libs/pyqode/core/modes/filewatcher.py index 37eabad..4f6f44b 100644 --- a/CadQuery/Libs/pyqode/core/modes/filewatcher.py +++ b/CadQuery/Libs/pyqode/core/modes/filewatcher.py @@ -154,9 +154,10 @@ class FileWatcherMode(Mode, QtCore.QObject): "The file %s has changed externally.\nDo you want to " "reload it?" % os.path.basename(self.editor.file.path)) kwargs = {"expected_action": inner_action} - if self.editor.hasFocus(): + if self.editor.hasFocus() or self.auto_reload: self._notify(*args, **kwargs) else: + # show the reload prompt as soon as the editor has focus self._notification_pending = True self._data = (args, kwargs) diff --git a/CadQuery/Libs/pyqode/core/widgets/filesystem_treeview.py b/CadQuery/Libs/pyqode/core/widgets/filesystem_treeview.py index c31dc45..f2eaed8 100644 --- a/CadQuery/Libs/pyqode/core/widgets/filesystem_treeview.py +++ b/CadQuery/Libs/pyqode/core/widgets/filesystem_treeview.py @@ -34,13 +34,14 @@ class FileSystemTreeView(QtWidgets.QTreeView): Excludes :attr:`ignored_directories` and :attr:`ignored_extensions` from the file system model. """ + #: The list of directories to exclude + ignored_directories = ['__pycache__'] + #: The list of file extension to exclude + ignored_extensions = ['.pyc', '.pyd', '.so', '.dll', '.exe', + '.egg-info', '.coverage', '.DS_Store'] + def __init__(self): super(FileSystemTreeView.FilterProxyModel, self).__init__() - #: The list of directories to exclude - self.ignored_directories = ['__pycache__'] - #: The list of file extension to exclude - self.ignored_extensions = ['.pyc', '.pyd', '.so', '.dll', '.exe', - '.egg-info', '.coverage', '.DS_Store'] self._ignored_unused = [] def set_root_path(self, path): @@ -48,7 +49,7 @@ class FileSystemTreeView(QtWidgets.QTreeView): Sets the root path to watch. :param path: root path (str). """ - self._ignored_unused = [] + self._ignored_unused[:] = [] parent_dir = os.path.dirname(path) for item in os.listdir(parent_dir): item_path = os.path.join(parent_dir, item) @@ -91,10 +92,6 @@ class FileSystemTreeView(QtWidgets.QTreeView): def __init__(self, parent=None): super(FileSystemTreeView, self).__init__(parent) - self._fs_model_source = QtWidgets.QFileSystemModel() - self._fs_model_proxy = self.FilterProxyModel() - self._fs_model_proxy.setSourceModel(self._fs_model_source) - self.setModel(self._fs_model_proxy) self.context_menu = None self.root_path = None self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) @@ -102,7 +99,8 @@ class FileSystemTreeView(QtWidgets.QTreeView): self.helper = FileSystemHelper(self) self.setSelectionMode(self.ExtendedSelection) - def ignore_directories(self, *directories): + @classmethod + def ignore_directories(cls, *directories): """ Adds the specified directories to the list of ignored directories. @@ -111,9 +109,10 @@ class FileSystemTreeView(QtWidgets.QTreeView): :param directories: the directories to ignore """ for d in directories: - self._fs_model_proxy.ignored_directories.append(d) + cls.FilterProxyModel.ignored_directories.append(d) - def ignore_extensions(self, *extensions): + @classmethod + def ignore_extensions(cls, *extensions): """ Adds the specified extensions to the list of ignored directories. @@ -124,7 +123,7 @@ class FileSystemTreeView(QtWidgets.QTreeView): .. note:: extension must have the dot: '.py' and not 'py' """ for d in extensions: - self._fs_model_proxy.ignored_extensions.append(d) + cls.FilterProxyModel.ignored_extensions.append(d) def set_context_menu(self, context_menu): """ @@ -137,13 +136,18 @@ class FileSystemTreeView(QtWidgets.QTreeView): for action in self.context_menu.actions(): self.addAction(action) - def set_root_path(self, path): + def set_root_path(self, path, hide_extra_columns=True): """ Sets the root path to watch :param path: root path - str + :param hide_extra_columns: Hide extra column (size, paths,...) """ if os.path.isfile(path): path = os.path.abspath(os.path.join(path, os.pardir)) + self._fs_model_source = QtWidgets.QFileSystemModel() + self._fs_model_proxy = self.FilterProxyModel() + self._fs_model_proxy.setSourceModel(self._fs_model_source) + self.setModel(self._fs_model_proxy) self._fs_model_proxy.set_root_path(path) self.root_path = os.path.dirname(path) file_root_index = self._fs_model_source.setRootPath(self.root_path) @@ -153,6 +157,10 @@ class FileSystemTreeView(QtWidgets.QTreeView): file_parent_index = self._fs_model_source.index(path) self.setExpanded(self._fs_model_proxy.mapFromSource( file_parent_index), True) + if hide_extra_columns: + self.setHeaderHidden(True) + for i in range(1, 4): + self.hideColumn(i) def filePath(self, index): """ diff --git a/CadQuery/Libs/pyqode/core/widgets/menu_recents.py b/CadQuery/Libs/pyqode/core/widgets/menu_recents.py index 0a76ec1..6968689 100644 --- a/CadQuery/Libs/pyqode/core/widgets/menu_recents.py +++ b/CadQuery/Libs/pyqode/core/widgets/menu_recents.py @@ -43,7 +43,8 @@ class RecentFilesManager(QtCore.QObject): :param default: default value. :return: value """ - return self._settings.value('recent_files/%s' % key, default) + return [os.path.normpath(pth) for pth in + self._settings.value('recent_files/%s' % key, default)] def set_value(self, key, value): """ @@ -51,6 +52,7 @@ class RecentFilesManager(QtCore.QObject): :param key: value key :param value: new value """ + value = [os.path.normpath(pth) for pth in value] self._settings.setValue('recent_files/%s' % key, value) def get_recent_files(self):