Updated PyQode to automatically reload and execute when an external editor writes to disk.
This commit is contained in:
parent
eca4be1818
commit
c88b89b46e
|
@ -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'
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user