Bring up to date with mainline CQ-Editor 774c3c6

This commit is contained in:
jdegenstein
2022-09-16 14:27:06 -05:00
committed by GitHub
parent e219eb917d
commit 16a589125a

303
cq_editor/editor.py Normal file
View File

@@ -0,0 +1,303 @@
import os
import spyder.utils.encoding
from modulefinder import ModuleFinder
from spyder.plugins.editor.widgets.codeeditor import CodeEditor
from PyQt5.QtCore import pyqtSignal, QFileSystemWatcher, QTimer
from PyQt5.QtWidgets import QAction, QFileDialog
from PyQt5.QtGui import QFontDatabase
from path import Path
import sys
from pyqtgraph.parametertree import Parameter
from ..mixins import ComponentMixin
from ..utils import get_save_filename, get_open_filename, confirm
from ..icons import icon
class Editor(CodeEditor,ComponentMixin):
name = 'Code Editor'
# This signal is emitted whenever the currently-open file changes and
# autoreload is enabled.
triggerRerender = pyqtSignal(bool)
sigFilenameChanged = pyqtSignal(str)
preferences = Parameter.create(name='Preferences',children=[
{'name': 'Font size', 'type': 'int', 'value': 12},
{'name': 'Autoreload', 'type': 'bool', 'value': False},
{'name': 'Autoreload delay', 'type': 'int', 'value': 50},
{'name': 'Autoreload: watch imported modules', 'type': 'bool', 'value': False},
{'name': 'Line wrap', 'type': 'bool', 'value': False},
{'name': 'Color scheme', 'type': 'list',
'values': ['Spyder','Monokai','Zenburn'], 'value': 'Spyder'}])
EXTENSIONS = 'py'
def __init__(self,parent=None):
self._watched_file = None
super(Editor,self).__init__(parent)
ComponentMixin.__init__(self)
self.setup_editor(linenumbers=True,
markers=True,
edge_line=False,
tab_mode=False,
show_blanks=True,
font=QFontDatabase.systemFont(QFontDatabase.FixedFont),
language='Python',
filename='')
self._actions = \
{'File' : [QAction(icon('new'),
'New',
self,
shortcut='ctrl+N',
triggered=self.new),
QAction(icon('open'),
'Open',
self,
shortcut='ctrl+O',
triggered=self.open),
QAction(icon('save'),
'Save',
self,
shortcut='ctrl+S',
triggered=self.save),
QAction(icon('save_as'),
'Save as',
self,
shortcut='ctrl+shift+S',
triggered=self.save_as),
QAction(icon('autoreload'),
'Automatic reload and preview',
self,triggered=self.autoreload,
checkable=True,
checked=False,
objectName='autoreload'),
]}
for a in self._actions.values():
self.addActions(a)
self._fixContextMenu()
# autoreload support
self._file_watcher = QFileSystemWatcher(self)
# we wait for 50ms after a file change for the file to be written completely
self._file_watch_timer = QTimer(self)
self._file_watch_timer.setInterval(self.preferences['Autoreload delay'])
self._file_watch_timer.setSingleShot(True)
self._file_watcher.fileChanged.connect(
lambda val: self._file_watch_timer.start())
self._file_watch_timer.timeout.connect(self._file_changed)
self.updatePreferences()
def _fixContextMenu(self):
menu = self.menu
menu.removeAction(self.run_cell_action)
menu.removeAction(self.run_cell_and_advance_action)
menu.removeAction(self.run_selection_action)
menu.removeAction(self.re_run_last_cell_action)
def updatePreferences(self,*args):
self.set_color_scheme(self.preferences['Color scheme'])
font = self.font()
font.setPointSize(self.preferences['Font size'])
self.set_font(font)
self.findChild(QAction, 'autoreload') \
.setChecked(self.preferences['Autoreload'])
self._file_watch_timer.setInterval(self.preferences['Autoreload delay'])
self.toggle_wrap_mode(self.preferences['Line wrap'])
self._clear_watched_paths()
self._watch_paths()
def confirm_discard(self):
if self.modified:
rv = confirm(self,'Please confirm','Current document is not saved - do you want to continue?')
else:
rv = True
return rv
def new(self):
if not self.confirm_discard(): return
self.set_text('')
self.filename = ''
self.reset_modified()
def open(self):
if not self.confirm_discard(): return
curr_dir = Path(self.filename).abspath().dirname()
fname = get_open_filename(self.EXTENSIONS, curr_dir)
if fname != '':
self.load_from_file(fname)
def load_from_file(self,fname):
self.set_text_from_file(fname)
self.filename = fname
self.reset_modified()
def determine_encoding(self, fname):
if os.path.exists(fname):
# this function returns the encoding spyder used to read the file
_, encoding = spyder.utils.encoding.read(fname)
# spyder returns a -guessed suffix in some cases
return encoding.replace('-guessed', '')
else:
return 'utf-8'
def save(self):
if self._filename != '':
if self.preferences['Autoreload']:
self._file_watcher.blockSignals(True)
self._file_watch_timer.stop()
encoding = self.determine_encoding(self._filename)
encoded = self.toPlainText().encode(encoding)
with open(self._filename, 'wb') as f:
f.write(encoded)
if self.preferences['Autoreload']:
self._file_watcher.blockSignals(False)
self.triggerRerender.emit(True)
self.reset_modified()
else:
self.save_as()
def save_as(self):
fname = get_save_filename(self.EXTENSIONS)
if fname != '':
encoded = self.toPlainText().encode('utf-8')
with open(fname, 'wb') as f:
f.write(encoded)
self.filename = fname
self.reset_modified()
def _update_filewatcher(self):
if self._watched_file and (self._watched_file != self.filename or not self.preferences['Autoreload']):
self._clear_watched_paths()
self._watched_file = None
if self.preferences['Autoreload'] and self.filename and self.filename != self._watched_file:
self._watched_file = self._filename
self._watch_paths()
@property
def filename(self):
return self._filename
@filename.setter
def filename(self, fname):
self._filename = fname
self._update_filewatcher()
self.sigFilenameChanged.emit(fname)
def _clear_watched_paths(self):
paths = self._file_watcher.files()
if paths:
self._file_watcher.removePaths(paths)
def _watch_paths(self):
if Path(self._filename).exists():
self._file_watcher.addPath(self._filename)
if self.preferences['Autoreload: watch imported modules']:
module_paths = self.get_imported_module_paths(self._filename)
if module_paths:
self._file_watcher.addPaths(module_paths)
# callback triggered by QFileSystemWatcher
def _file_changed(self):
# neovim writes a file by removing it first so must re-add each time
self._watch_paths()
self.set_text_from_file(self._filename)
self.triggerRerender.emit(True)
# Turn autoreload on/off.
def autoreload(self, enabled):
self.preferences['Autoreload'] = enabled
self._update_filewatcher()
def reset_modified(self):
self.document().setModified(False)
@property
def modified(self):
return self.document().isModified()
def saveComponentState(self,store):
if self.filename != '':
store.setValue(self.name+'/state',self.filename)
def restoreComponentState(self,store):
filename = store.value(self.name+'/state')
if filename and self.filename == '':
try:
self.load_from_file(filename)
except IOError:
self._logger.warning(f'could not open {filename}')
def get_imported_module_paths(self, module_path):
finder = ModuleFinder([os.path.dirname(module_path)])
imported_modules = []
try:
finder.run_script(module_path)
except SyntaxError as err:
self._logger.warning(f'Syntax error in {module_path}: {err}')
except Exception as err:
self._logger.warning(
f'Cannot determine imported modules in {module_path}: {type(err).__name__} {err}'
)
else:
for module_name, module in finder.modules.items():
if module_name != '__main__':
path = getattr(module, '__file__', None)
if path is not None and os.path.isfile(path):
imported_modules.append(path)
return imported_modules
if __name__ == "__main__":
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
editor = Editor()
editor.show()
sys.exit(app.exec_())