Updated PyQode to automatically reload and execute when an external editor writes to disk.

This commit is contained in:
Jeremy Wright 2014-12-27 13:01:27 -05:00
parent eca4be1818
commit c88b89b46e
8 changed files with 66 additions and 35 deletions

View File

@ -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. widget, i.e. pyqode.core is a generic code editor widget.
""" """
__version__ = '2.5.dev' __version__ = '2.5.dev1'

View File

@ -80,12 +80,17 @@ class JsonTcpClient(QtNetwork.QTcpSocket):
self._data_buf = bytes() self._data_buf = bytes()
self._callback = on_receive self._callback = on_receive
self.is_connected = False self.is_connected = False
self._closed = False
self.connected.connect(self._on_connected) self.connected.connect(self._on_connected)
self.error.connect(self._on_error) self.error.connect(self._on_error)
self.disconnected.connect(self._on_disconnected) self.disconnected.connect(self._on_disconnected)
self.readyRead.connect(self._on_ready_read) self.readyRead.connect(self._on_ready_read)
self._connect() self._connect()
def close(self):
self._closed = True # fix issue with QTimer.singleShot
super(JsonTcpClient, self).close()
def _send_request(self): def _send_request(self):
""" """
Sends the request to the backend. Sends the request to the backend.
@ -139,10 +144,17 @@ class JsonTcpClient(QtNetwork.QTcpSocket):
def _on_error(self, error): def _on_error(self, error):
if error not in SOCKET_ERROR_STRINGS: # pragma: no cover if error not in SOCKET_ERROR_STRINGS: # pragma: no cover
error = -1 error = -1
_logger().debug(SOCKET_ERROR_STRINGS[error]) if error == 1 and self.is_connected or (
if error == 0: 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) QtCore.QTimer.singleShot(100, self._connect)
log_fct(SOCKET_ERROR_STRINGS[error])
def _on_disconnected(self): def _on_disconnected(self):
try: try:
_logger().debug('disconnected from backend: %s:%d', _logger().debug('disconnected from backend: %s:%d',
@ -222,7 +234,7 @@ class BackendProcess(QtCore.QProcess):
self.running = False self.running = False
self.starting = True self.starting = True
self._srv_logger = logging.getLogger('pyqode.backend') self._srv_logger = logging.getLogger('pyqode.backend')
self._test_not_deleted = False self._prevent_logs = False
def _on_process_started(self): def _on_process_started(self):
""" Logs process started """ """ Logs process started """
@ -234,18 +246,13 @@ class BackendProcess(QtCore.QProcess):
""" Logs process error """ """ Logs process error """
if error not in PROCESS_ERROR_STRING: if error not in PROCESS_ERROR_STRING:
error = -1 error = -1
try: if not self._prevent_logs:
self._test_not_deleted _logger().warning(PROCESS_ERROR_STRING[error])
except AttributeError:
pass
else:
if self.running:
_logger().debug(PROCESS_ERROR_STRING[error])
def _on_process_finished(self, exit_code): def _on_process_finished(self, exit_code):
""" Logs process exit status """ """ Logs process exit status """
_logger().debug('backend process finished with exit code %d', _logger().debug('backend process finished with exit code %d',
exit_code) exit_code)
try: try:
self.running = False self.running = False
except AttributeError: except AttributeError:

View File

@ -512,6 +512,8 @@ class CodeEdit(QtWidgets.QPlainTextEdit):
list(self.panels), list(clone.panels)): list(self.panels), list(clone.panels)):
panel.enabled = original_panel.isEnabled() panel.enabled = original_panel.isEnabled()
panel.clone_settings(original_panel) 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.use_spaces_instead_of_tabs = self.use_spaces_instead_of_tabs
clone.tab_length = self.tab_length clone.tab_length = self.tab_length
clone.save_on_focus_out = self.save_on_focus_out clone.save_on_focus_out = self.save_on_focus_out

View File

@ -136,7 +136,7 @@ class JsonServer(socketserver.ThreadingTCPServer):
ret_val = worker(data['data']) ret_val = worker(data['data'])
except Exception: except Exception:
_logger().exception('something went bad with worker ' _logger().exception('something went bad with worker '
'%r(data=%r)') '%r(data=%r)', worker, data['data'])
ret_val = None ret_val = None
if ret_val is None: if ret_val is None:
ret_val = [] ret_val = []

View File

@ -86,8 +86,8 @@ class BackendManager(Manager):
if error_callback: if error_callback:
self._process.error.connect(error_callback) self._process.error.connect(error_callback)
self._process.start(program, pgm_args) self._process.start(program, pgm_args)
_logger().debug('starting backend process: %s %s', program, _logger().info('starting backend process: %s %s', program,
' '.join(pgm_args)) ' '.join(pgm_args))
def stop(self): def stop(self):
""" """
@ -95,11 +95,16 @@ class BackendManager(Manager):
""" """
if self._process is None: if self._process is None:
return return
_logger().debug('stopping backend process')
# close all sockets # close all sockets
for socket in self._sockets: for socket in self._sockets:
socket._callback = None socket._callback = None
socket.close() 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: while self._process.state() != self._process.NotRunning:
self._process.waitForFinished(1) self._process.waitForFinished(1)
if sys.platform == 'win32': if sys.platform == 'win32':
@ -109,7 +114,7 @@ class BackendManager(Manager):
self._process.kill() self._process.kill()
else: else:
self._process.terminate() self._process.terminate()
_logger().info('stopping backend took %f [s]', time.time() - t) self._process._prevent_logs = False
_logger().info('backend process terminated') _logger().info('backend process terminated')
def send_request(self, worker_class_or_function, args, on_receive=None): def send_request(self, worker_class_or_function, args, on_receive=None):
@ -128,6 +133,8 @@ class BackendManager(Manager):
if not self.running: if not self.running:
raise NotRunning() raise NotRunning()
else: else:
_logger().info('sending request, worker=%r' %
worker_class_or_function)
# create a socket, the request will be send as soon as the socket # create a socket, the request will be send as soon as the socket
# has connected # has connected
socket = JsonTcpClient( socket = JsonTcpClient(
@ -137,7 +144,11 @@ class BackendManager(Manager):
self._sockets.append(socket) self._sockets.append(socket)
def _rm_socket(self, socket): def _rm_socket(self, socket):
self._sockets.remove(socket) try:
socket.close()
self._sockets.remove(socket)
except ValueError:
pass
@property @property
def running(self): def running(self):

View File

@ -154,9 +154,10 @@ class FileWatcherMode(Mode, QtCore.QObject):
"The file <i>%s</i> has changed externally.\nDo you want to " "The file <i>%s</i> has changed externally.\nDo you want to "
"reload it?" % os.path.basename(self.editor.file.path)) "reload it?" % os.path.basename(self.editor.file.path))
kwargs = {"expected_action": inner_action} kwargs = {"expected_action": inner_action}
if self.editor.hasFocus(): if self.editor.hasFocus() or self.auto_reload:
self._notify(*args, **kwargs) self._notify(*args, **kwargs)
else: else:
# show the reload prompt as soon as the editor has focus
self._notification_pending = True self._notification_pending = True
self._data = (args, kwargs) self._data = (args, kwargs)

View File

@ -34,13 +34,14 @@ class FileSystemTreeView(QtWidgets.QTreeView):
Excludes :attr:`ignored_directories` and :attr:`ignored_extensions` Excludes :attr:`ignored_directories` and :attr:`ignored_extensions`
from the file system model. 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): def __init__(self):
super(FileSystemTreeView.FilterProxyModel, self).__init__() 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 = [] self._ignored_unused = []
def set_root_path(self, path): def set_root_path(self, path):
@ -48,7 +49,7 @@ class FileSystemTreeView(QtWidgets.QTreeView):
Sets the root path to watch. Sets the root path to watch.
:param path: root path (str). :param path: root path (str).
""" """
self._ignored_unused = [] self._ignored_unused[:] = []
parent_dir = os.path.dirname(path) parent_dir = os.path.dirname(path)
for item in os.listdir(parent_dir): for item in os.listdir(parent_dir):
item_path = os.path.join(parent_dir, item) item_path = os.path.join(parent_dir, item)
@ -91,10 +92,6 @@ class FileSystemTreeView(QtWidgets.QTreeView):
def __init__(self, parent=None): def __init__(self, parent=None):
super(FileSystemTreeView, self).__init__(parent) 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.context_menu = None
self.root_path = None self.root_path = None
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
@ -102,7 +99,8 @@ class FileSystemTreeView(QtWidgets.QTreeView):
self.helper = FileSystemHelper(self) self.helper = FileSystemHelper(self)
self.setSelectionMode(self.ExtendedSelection) 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. Adds the specified directories to the list of ignored directories.
@ -111,9 +109,10 @@ class FileSystemTreeView(QtWidgets.QTreeView):
:param directories: the directories to ignore :param directories: the directories to ignore
""" """
for d in directories: 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. 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' .. note:: extension must have the dot: '.py' and not 'py'
""" """
for d in extensions: 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): def set_context_menu(self, context_menu):
""" """
@ -137,13 +136,18 @@ class FileSystemTreeView(QtWidgets.QTreeView):
for action in self.context_menu.actions(): for action in self.context_menu.actions():
self.addAction(action) 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 Sets the root path to watch
:param path: root path - str :param path: root path - str
:param hide_extra_columns: Hide extra column (size, paths,...)
""" """
if os.path.isfile(path): if os.path.isfile(path):
path = os.path.abspath(os.path.join(path, os.pardir)) 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._fs_model_proxy.set_root_path(path)
self.root_path = os.path.dirname(path) self.root_path = os.path.dirname(path)
file_root_index = self._fs_model_source.setRootPath(self.root_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) file_parent_index = self._fs_model_source.index(path)
self.setExpanded(self._fs_model_proxy.mapFromSource( self.setExpanded(self._fs_model_proxy.mapFromSource(
file_parent_index), True) file_parent_index), True)
if hide_extra_columns:
self.setHeaderHidden(True)
for i in range(1, 4):
self.hideColumn(i)
def filePath(self, index): def filePath(self, index):
""" """

View File

@ -43,7 +43,8 @@ class RecentFilesManager(QtCore.QObject):
:param default: default value. :param default: default value.
:return: 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): def set_value(self, key, value):
""" """
@ -51,6 +52,7 @@ class RecentFilesManager(QtCore.QObject):
:param key: value key :param key: value key
:param value: new value :param value: new value
""" """
value = [os.path.normpath(pth) for pth in value]
self._settings.setValue('recent_files/%s' % key, value) self._settings.setValue('recent_files/%s' % key, value)
def get_recent_files(self): def get_recent_files(self):