diff --git a/packages/code_editor/code_handlers/base_handler.py b/packages/code_editor/code_handlers/base_handler.py index 6326d3e03eb4fc61c651ff0e6532067bb76261c0..699eac6efe0110e88326bfec3e3f75a41fae67f6 100644 --- a/packages/code_editor/code_handlers/base_handler.py +++ b/packages/code_editor/code_handlers/base_handler.py @@ -37,6 +37,7 @@ class BaseAnalyzer(CodeEditorBaseObject): @cached_property def selected_code(self): + """获取选中的代码或者当前行的代码""" if self.has_selection: return self.code[self.selection_range[0]:self.selection_range[1]] else: @@ -80,3 +81,7 @@ class BaseHandler(CodeEditorBaseObject): hint: 代码的标题 """ self.__not_implemented_error(self.tr('run code')) + + def run_selected_code(self): + """运行选中的代码""" + self.__not_implemented_error(self.tr('run selected code')) diff --git a/packages/code_editor/code_handlers/python_handler.py b/packages/code_editor/code_handlers/python_handler.py index 9401f03fbf67435fd97633c07e7dc13e9f8ecc62..de84e4b7ff897bdce30d9a944f0400c4a078bbdc 100644 --- a/packages/code_editor/code_handlers/python_handler.py +++ b/packages/code_editor/code_handlers/python_handler.py @@ -16,3 +16,7 @@ class PythonHandler(BaseHandler): if hint == '': hint = self.tr('Run code') self.ipython_console.run_command(command=code, hint_text=hint, hidden=False) + + def run_selected_code(self): + code = self.analyzer.selected_code + self.ipython_console.run_command(command=code, hint_text=code, hidden=False) diff --git a/packages/code_editor/main.py b/packages/code_editor/main.py index 2458886c06b6a2ac0a52595230e800204c5a56de..d4ad36cc70d6448771fbaa96531aebb547773bc2 100644 --- a/packages/code_editor/main.py +++ b/packages/code_editor/main.py @@ -79,14 +79,15 @@ class Extension(BaseExtension): [file_tree.add_open_file_callback(ext, self.new_script) for ext in extensions] def load_settings(self): - settings = {'encoding_declaration_text': '# coding = utf-8', - 'check_syntax_background': True, - 'smart_autocomp_on': True, - 'font_size': 12, - 'wrap': True, - 'key_comment': 'Ctrl+/', - 'key_format': 'Ctrl+Alt+L' - } + settings = { + 'encoding_declaration_text': '# coding = utf-8', + 'check_syntax_background': True, + 'smart_autocomp_on': True, + 'font_size': 12, + 'wrap': True, + 'key_comment': 'Ctrl+/', + 'key_format': 'Ctrl+Alt+L' + } config_folder = self.extension_lib.Program.get_plugin_data_path('code_editor') config_path = os.path.join(config_folder, 'settings.json') try: diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 858d3da5d5491069ed234dd39a288ddf805fd2d5..1e92bf009eca08ce6f083557f0de601df5407520 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -84,7 +84,10 @@ class PMBaseEditor(CodeEditorBaseObject, QWidget): self.signal_focused_in = self.text_edit.signal_focused_in self.text_edit.signal_save.connect(self.save) self.text_edit.signal_text_modified.connect(lambda: self.slot_modification_changed(True)) + + # 代码发生变化后,改变代码行号的显示 self.text_edit.cursorPositionChanged.connect(self.show_cursor_pos) + self.text_edit.signal_file_dropped.connect(lambda name: self.signal_new_requested.emit(name, 0)) self.layout().addWidget(self.text_edit) @@ -353,24 +356,6 @@ class PMBaseEditor(CodeEditorBaseObject, QWidget): def slot_code_format(self): """格式化代码""" - def slot_code_run(self): - """运行代码""" - logger.warning('run code') - text = self.text().strip() - if not text: - return - - self._parent.slot_run_script(text) - - def slot_code_sel_run(self): - """运行选中代码""" - # TODO 存在问题,当选中了多行时,会报错 - text = self.text(selected=True).strip() - if not text: - text = self.current_line_text().strip() - - self._parent.slot_run_sel(text) - def slot_find_in_path(self): selected_text = self.text_edit.selected_code self.signal_request_find_in_path.emit(selected_text) diff --git a/packages/code_editor/widgets/editors/markdown_editor.py b/packages/code_editor/widgets/editors/markdown_editor.py index 03c6226a7215726ab982b47f7a44198518458b20..794904ef2c336b7513ed41b5a067d767225fbf67 100644 --- a/packages/code_editor/widgets/editors/markdown_editor.py +++ b/packages/code_editor/widgets/editors/markdown_editor.py @@ -7,9 +7,9 @@ from packages.qt_vditor.client import Window from .base_editor import PMBaseEditor -class PMMarkdownEditorPM(PMBaseEditor): +class PMMarkdownEditor(PMBaseEditor): def __init__(self, parent=None): - super(PMMarkdownEditorPM, self).__init__(parent=parent) + super(PMMarkdownEditor, self).__init__(parent=parent) # TODO 不应该直接引用qt_vditor,而是应该走interface self.textEdit = Window(url='http://127.0.0.1:5000/qt_vditor') self._path = '' diff --git a/packages/code_editor/widgets/tab_widget.py b/packages/code_editor/widgets/tab_widget.py index 1a5664b66ac8e3c760aae091d805d94b69255735..90799cf98d72fa7ff399ace2441f47ba1da8c1bd 100644 --- a/packages/code_editor/widgets/tab_widget.py +++ b/packages/code_editor/widgets/tab_widget.py @@ -2,7 +2,7 @@ import logging import os import sys import time -from typing import Dict, Optional, Tuple, Any, Union +from typing import Dict, Optional, Tuple, Any, Union, Callable, TYPE_CHECKING from PySide2.QtCore import QTimer, QThread, QDir from PySide2.QtGui import QCloseEvent @@ -10,14 +10,18 @@ from PySide2.QtWidgets import QTabWidget, QSizePolicy, QMessageBox, QFileDialog, import pmgwidgets from pmgwidgets import PMDockObject, UndoManager, PMGFileSystemWatchdog, in_unit_test -from .editors.markdown_editor import PMMarkdownEditorPM from .editors.python_editor import PMPythonEditor from .ui.findinpath import FindInPathWidget from ..utils.base_object import CodeEditorBaseObject from ..utils.code_checker.base_code_checker import CodeCheckWorker from ..utils.highlighter.python_highlighter import PythonHighlighter -EDITOR_TYPE = Optional[Union['PMBaseEditor', 'PMCythonEditor', 'PMPythonEditor', 'PMCPPEditor', 'PMMarkdownEditor']] +if TYPE_CHECKING: + from features.extensions.extensionlib.extension_lib import ExtensionLib + from .editors.base_editor import PMBaseEditor + from .editors.markdown_editor import PMMarkdownEditor + + EDITOR_TYPE = Optional[Union[PMBaseEditor, PMPythonEditor, PMMarkdownEditor, QWidget]] logger = logging.getLogger(__name__) @@ -25,7 +29,11 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): """ 多标签页编辑器控件 """ - extension_lib = None + extension_lib: 'ExtensionLib' = None + + if TYPE_CHECKING: + widget: Callable[[int], EDITOR_TYPE] + currentWidget: Callable[[], EDITOR_TYPE] def __init__(self, *args, **kwargs): super(PMCodeEditTabWidget, self).__init__(*args, **kwargs) @@ -35,7 +43,6 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): self._color_scheme = 'light' self._index = 0 self._current_executable = sys.executable # 设置当前可执行文件的路径。 - self._keywords = [] self._old_code = '' self._thread_check = None self._worker_check = None @@ -156,7 +163,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): if time.time() - editor.last_save_time > 1: editor.slot_file_modified_externally() - def get_editor_tab_by_path(self, path) -> EDITOR_TYPE: + def get_editor_tab_by_path(self, path) -> 'EDITOR_TYPE': """ 通过路径获取编辑器。如果没有打开,则返回None Args: @@ -171,50 +178,21 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): return w return None - def keywords(self) -> list: - """ - 返回自定义的关键词 - Returns: - - """ - return self._keywords + @property + def current_editor(self): + return self.currentWidget() - def set_keywords(self, keywords: list): - """ - 增加额外的关键词 - - :param keywords: 关键词列表 - :type: list - :return: - """ - if not isinstance(keywords, (tuple, list)): - return - self._keywords = list(keywords) + @property + def current_text_edit(self): + return self.current_editor.text_edit - def get_current_editor(self) -> Optional['PMPythonEditor']: + def get_current_editor(self) -> 'EDITOR_TYPE': """ get current editor Returns: """ - try: - return self.currentWidget() - except Exception as e: - logger.warning(str(e)) - return None - - def get_current_edit(self) -> Optional['QsciScintilla']: - """ - 返回当前qscintilla texteditor对象 - - Returns: Current Editor(QScintilla) - - """ - try: - return self.currentWidget().textEdit - except Exception as e: - logger.warning(str(e)) - return None + return self.current_editor def get_current_text(self, selected: bool = False) -> str: """ @@ -301,7 +279,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): # elif path.endswith('.pyx'): # widget = PMCythonEditor(parent=self) elif path.endswith('.md'): - widget = PMMarkdownEditorPM(parent=self) + widget = PMMarkdownEditor(parent=self) else: QMessageBox.warning(self, self.tr('Warning'), self.tr('Editor does not support file:\n%s') % path) @@ -388,11 +366,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): current_widget.goto_line(line_no) def slot_open_script(self): - """ - 弹出对话框选择文件 - - :return: - """ + """弹出对话框,打开选中的文件""" path, _ = QFileDialog.getOpenFileName(self, self.tr('Open File'), self.extension_lib.Program.get_work_dir(), filter='*.py') if not path or not os.path.exists(path): @@ -400,86 +374,12 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): self.slot_new_script(path) - def slot_search_for_file(self): - """ - 搜索文件内容 - - :return: - """ - - def slot_clipboard(self): - """ - 剪贴板操作 - - :return: - """ - - def slot_print(self): - """ - 打印预览以及打印 - - :return: - """ - - def slot_search(self): - """ - 文本查找 - - :return: - """ - self.currentWidget().slot_find() - - def slot_replace(self): - """ - 文本替换 - - :return: - """ - def slot_goto_file(self, path: str, row: int, col: int): - """ - goto file - :return - """ - t0 = time.time() self.slot_new_script(path) - current_widget = self.currentWidget() - current_widget.goto_line(row) - logger.warning('goto file %s ,line %d' % (path, row)) - t1 = time.time() - logger.debug('time elapsed:', t1 - t0) - - def slot_goto(self): - """ - 跳转到指定行 - - :return: - """ - self.currentWidget().slot_goto_line() - - def slot_indent(self): - """ - 批量缩进 - (实际上连接的是同一个函数) - :return: - """ - self.get_current_editor().text_edit.on_tab() - - def slot_unindent(self): - """ - 取消缩进 - - :return: - """ - self.get_current_editor().text_edit.on_back_tab() + self.current_text_edit.go_to_line(row) def slot_check_code(self, force_update=False): - """ - 代码检查 - - :return: - """ - + """代码检查""" if not self._thread_check: return widget = self.currentWidget() @@ -503,9 +403,6 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): """ widget.set_indicators(msgs, True) - def slot_toggle_comment(self): - self.get_current_editor().text_edit.comment() - def slot_run_script(self, code: str = '', hint: str = ''): """ 执行文件 @@ -521,7 +418,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): if hint == '': hint = self.tr('Run: %s') % self.get_current_filename() - elif isinstance(self.currentWidget(), PMMarkdownEditorPM): + elif isinstance(self.currentWidget(), PMMarkdownEditor): code = self.currentWidget().get_code() code = code.strip() if hint == '': @@ -535,15 +432,6 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): else: logger.info("In Unit test at method 'slot_run_script'.code is :\n%s,\nhint is :%s" % (code, hint)) - def slot_run_sel(self, sel_text): - """ - 运行选中代码片段或光标所在行 - :param sel_text: - :return: - """ - self.extension_lib.get_interface('ipython_console').run_command(command=sel_text, hint_text=sel_text, - hidden=False) - def slot_tab_close_request(self, index: int): """ 关闭标签页 @@ -581,17 +469,6 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): self.extension_lib.get_interface('applications_toolbar').create_python_file_process(path, self._current_executable) - def run_sys_command(self): - pass - - def slot_save_current_script(self): - """ - 保存当前脚本 - Returns: - - """ - self.currentWidget().slot_save() - def _init_signals(self): # 标签页关闭信号 self.tabCloseRequested.connect(self.slot_tab_close_request) @@ -632,42 +509,20 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): self.update_interpreter_selections(combo_box) self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', 'combobox_interpreter') - # # 保存脚本 - self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', 'button_save').clicked.connect( - self.slot_save_current_script) - # 查找内容&替换 - self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', 'button_search').clicked.connect( - self.slot_search) - # 跳转到行 - self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', 'button_goto').clicked.connect( - self.slot_goto) - # 批量注释 - self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', 'button_comment').clicked.connect( - self.slot_toggle_comment) - # 取消注释 - # self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', 'button_uncomment').clicked.connect( - # self.slot_toggle_comment) - # 增加缩进 - self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', 'button_indent').clicked.connect( - self.slot_indent) - # 减少缩进 - self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', 'button_unindent').clicked.connect( - self.slot_unindent) - # 运行代码 - self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', 'button_run_script').clicked.connect( - self.slot_run_script) - - self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', 'button_run_isolated').clicked.connect( - self.slot_run_isolated) - - self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', - 'button_run_in_terminal').clicked.connect( - self.slot_run_in_terminal) - - self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', 'button_instant_boot').clicked.connect( - self.slot_instant_boot) - # self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', 'button_debug').clicked.connect( - # self.slot_debug) + for widget_name, callback in ( + ('button_save', lambda: self.currentWidget().slot_save()), + ('button_search', lambda: self.currentWidget().slot_find()), + ('button_goto', lambda: self.currentWidget().slot_goto_line()), + ('button_comment', lambda: self.current_text_edit.comment()), + ('button_indent', lambda: self.current_text_edit.on_tab()), + ('button_unindent', lambda: self.current_text_edit.on_back_tab()), + ('button_run_script', self.slot_run_script), + ('button_run_isolated', self.slot_run_isolated), + ('button_run_in_terminal', self.slot_run_in_terminal), + ('button_instant_boot', self.slot_instant_boot), + ): + self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', widget_name) \ + .clicked.connect(callback) except Exception as e: import traceback traceback.print_exc() @@ -766,12 +621,6 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): logger.warning('break_points are:' + str(break_points)) return breakpoints_str - def currentWidget(self) -> Union[EDITOR_TYPE, QWidget]: - return super(PMCodeEditTabWidget, self).currentWidget() - - def widget(self, index) -> Union[EDITOR_TYPE, QWidget]: - return super(PMCodeEditTabWidget, self).widget(index) - def set_debug_widget(self, debug_widget): self.debug_widget = debug_widget diff --git a/packages/code_editor/widgets/text_edit/base_text_edit.py b/packages/code_editor/widgets/text_edit/base_text_edit.py index bef78ce95c111c5636d793f6d585fcdd2741a11d..308c5823f4f641a87b7d8a5829b8aed5bfaa5560 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -28,6 +28,7 @@ if TYPE_CHECKING: from ...utils.highlighter.base_highlighter import BaseHighlighter from ..editors.python_editor import PMPythonEditor from ...utils.auto_complete_thread.base_auto_complete import BaseAutoCompleteThread + from ..editors.base_editor import PMBaseEditor logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -63,6 +64,8 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit): # 获取光标 textCursor: Callable[[], QTextCursor] + parent: Callable[[], PMBaseEditor] + # 其他类型提示 doc_tab_widget: 'PMPythonEditor' highlighter: 'PythonHighlighter' @@ -170,44 +173,45 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit): # TODO 将这些操作全部迁移至这个类下 # 这里代码比较紧凑,以节省行数 self.__menu_operations = [Operation( # 格式化代码 - widget=self, name='format code', label=self.tr('Format Code'), - slot=self.parent().slot_code_format, key='Ctrl+Alt+F', icon_name='format.svg', + widget=self, name='format code', label=self.tr('Format Code'), key='Ctrl+Alt+F', icon_name='format.svg', + slot=self.parent().slot_code_format, conditions=[text_exists], ), Operation( # 运行代码 - widget=self, name='run code', label=self.tr('Run Code'), - slot=self.slot_code_run, key='Ctrl+R', icon_name='run.svg', + widget=self, name='run code', label=self.tr('Run Code'), key='Ctrl+R', icon_name='run.svg', + # TODO 这里的path应该是本对象的属性,而非父对象的属性 + slot=lambda: self.handler.run_code(self.code, self.tr('Running {}').format(self.parent()._path)), conditions=[text_exists], ), Operation( # 运行选中代码 - widget=self, name='run code', label=self.tr('Run Selected Code'), - slot=self.parent().slot_code_sel_run, key='F9', icon_name='python.svg', conditions=[text_exists], - # TODO 添加判别条件:仅当有文本选中时才可用 + widget=self, name='run code', label=self.tr('Run Selected Code'), key='F9', icon_name='python.svg', + slot=self.handler.run_selected_code, + conditions=[text_exists], ), Operation( # 保存代码 - widget=self, name='save code', label=self.tr('Save Code'), - slot=self.parent().slot_save, key='Ctrl+S', icon_name='save.svg', + widget=self, name='save code', label=self.tr('Save Code'), key='Ctrl+S', icon_name='save.svg', + slot=self.parent().slot_save, ), Operation( # 查找代码 - widget=self, name='find code', label=self.tr('Find Code'), - slot=self.parent().slot_find, key='Ctrl+F', + widget=self, name='find code', label=self.tr('Find Code'), key='Ctrl+F', + slot=self.parent().slot_find, ), Operation( # 替换代码 - widget=self, name='replace code', label=self.tr('Replace'), - slot=self.parent().slot_replace, key='Ctrl+H', + widget=self, name='replace code', label=self.tr('Replace'), key='Ctrl+H', + slot=self.parent().slot_replace, ), Operation( # 在路径中查找,暂不理解这个功能的含义 - widget=self, name='find in path', label=self.tr('Find In Path'), - slot=self.parent().slot_find_in_path, key='Ctrl+Shift+F', + widget=self, name='find in path', label=self.tr('Find In Path'), key='Ctrl+Shift+F', + slot=self.parent().slot_find_in_path, ), Operation( # 自动补全功能是每隔一段时间自动显示的,使用快捷键可以立刻显示 - widget=self, name='auto completion', label=self.tr('AutoComp'), - slot=self.parent().auto_completion, key='Ctrl+P', + widget=self, name='auto completion', label=self.tr('AutoComp'), key='Ctrl+P', + slot=self.parent().auto_completion, ), Operation( # 跳转到行 - widget=self, name='goto line', label=self.tr('Goto Line'), - slot=self.parent().slot_goto_line, key='Ctrl+G', + widget=self, name='goto line', label=self.tr('Goto Line'), key='Ctrl+G', + slot=self.parent().slot_goto_line, ), Operation( - widget=self, name='goto definition', label=self.tr('Goto Definition'), - slot=self.parent().slot_goto_definition, key='Ctrl+B', + widget=self, name='goto definition', label=self.tr('Goto Definition'), key='Ctrl+B', + slot=self.parent().slot_goto_definition, ), Operation( # 函数帮助 - widget=self, name='function help', label=self.tr('Function Help'), - slot=self.slot_function_help, key='F1', + widget=self, name='function help', label=self.tr('Function Help'), key='F1', + slot=self.slot_function_help, ), Operation( - widget=self, name='help in console', label=self.tr('Help in Console'), - slot=self.slot_help_in_console, key='F2', + widget=self, name='help in console', label=self.tr('Help in Console'), key='F2', + slot=self.slot_help_in_console, conditions=[always_false], ), Operation( # 添加断点 widget=self, name='add breakpoint', label=self.tr('Add Breakpoint'), icon_name='breakpoint.svg', @@ -423,6 +427,8 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit): mapping = { Qt.Key_Tab: self.on_tab, Qt.Key_Backtab: self.on_back_tab, + (Qt.Key_Backtab, Qt.ShiftModifier): self.on_back_tab, # 经测试,只有这个可以表示Shift+Tab,不过另两个也可保留 + (Qt.Key_Tab, Qt.ShiftModifier): self.on_back_tab, # TODO 迁移至Operation体系 # 这个就比较适合使用Operation来处理,因为可以显示在右键菜单中,而定义在key中就没有了这个优势。 (Qt.Key_Slash, Qt.ControlModifier): self.comment, @@ -466,7 +472,7 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit): # 即分别通过event.text()和event.key()+event.modifier()进行处理 text, key, modifiers = event.text(), event.key(), int(event.modifiers()) no_modifier = modifiers == Qt.NoModifier - if (callback := self.key_press_mapping.get(text, None)) is not None: + if no_modifier and (callback := self.key_press_mapping.get(text, None)) is not None: callback(event) elif no_modifier and (callback := self.key_press_mapping.get(key, None)) is not None: callback(event) @@ -552,7 +558,7 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit): cursor.insertText('\n' + ' ' * indent) event.accept() - def comment(self, _: QKeyEvent): + def comment(self, _: QKeyEvent = None): cursor = self.textCursor() cursor.beginEditBlock() if cursor.hasSelection(): @@ -604,7 +610,7 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit): cursor.endEditBlock() - def on_back_tab(self, _: QKeyEvent): + def on_back_tab(self, _: QKeyEvent = None): cursor = self.textCursor() if cursor.hasSelection(): self.edit_unindent() @@ -619,7 +625,7 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit): break cursor.removeSelectedText() - def on_tab(self, _: QKeyEvent): + def on_tab(self, _: QKeyEvent = None): with self.editing_block_cursor() as cursor: if cursor.hasSelection(): self.edit_indent() @@ -893,7 +899,3 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit): def slot_help_in_console(self): return self.parent().get_help_in_console() - - def slot_code_run(self): - # TODO 这里的path应该是本对象的属性,而非父对象的属性 - self.handler.run_code(self.code, self.tr('Running {}').format(self.parent()._path)) diff --git a/pmgwidgets/utilities/uilogics/undomanager.py b/pmgwidgets/utilities/uilogics/undomanager.py index c71d1af565478a6453af283b140cad67250f6a47..b336a95ece6bf26a951ad7187d1a91fa6840130b 100644 --- a/pmgwidgets/utilities/uilogics/undomanager.py +++ b/pmgwidgets/utilities/uilogics/undomanager.py @@ -6,20 +6,7 @@ from typing import Any -class Stack(): - def __init__(self, stack_size: int = 10): - self.pointer: int = 0 - self.content = [] - self.stack_size = stack_size - - def push(self, obj): - self.content.append(obj) - - def pop(self): - self.content.pop() - - -class UndoManager(): +class UndoManager: """ 用于撤销和重做的管理类 """ @@ -29,7 +16,7 @@ class UndoManager(): self.content = [] self.stack_size = stack_size - def push(self, obj:Any): + def push(self, obj: Any): """ 压栈时指向栈顶,这里就是撤销时候的逻辑。 :param obj: @@ -68,7 +55,7 @@ class UndoManager(): else: return None - def last_value(self)->Any: + def last_value(self) -> Any: try: return self.content[self.pointer] except: @@ -76,29 +63,3 @@ class UndoManager(): def __len__(self): return len(self.content) - - -if __name__ == '__main__': - manager = UndoManager() - manager.push('a') - manager.push('ab') - manager.push('abc') - manager.push('ab') - manager.push('abd') - manager.push('abde') - manager.push('abdef') - manager.push('abdefg') - print(manager.content, manager.pointer, len(manager)) - print(manager.undo(), manager.pointer, len(manager)) - print(manager.undo(), manager.pointer, len(manager)) - manager.push('abdefgh') - manager.push('abdefghi') - manager.push('abdefghij') - manager.push('abdefghijk') - manager.push('abdefghijkl') - manager.push('abdefghijklm') - print(manager.content) - print(manager.undo()) - print(manager.undo()) - print(manager.redo()) - print(manager.redo()) diff --git a/tests/test_pmgwidgets/__init__.py b/tests/test_pmgwidgets/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/test_pmgwidgets/test_utilities/__init__.py b/tests/test_pmgwidgets/test_utilities/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/test_pmgwidgets/test_utilities/test_undomanager.py b/tests/test_pmgwidgets/test_utilities/test_undomanager.py new file mode 100644 index 0000000000000000000000000000000000000000..ac06d8767e3e55f8569610cc8bb1474887877048 --- /dev/null +++ b/tests/test_pmgwidgets/test_utilities/test_undomanager.py @@ -0,0 +1,62 @@ +from pmgwidgets import UndoManager + + +def test_push(): + manager = UndoManager() + manager.push('a') + manager.push('ab') + manager.push('abc') + manager.push('ab') + manager.push('abd') + manager.push('abde') + manager.push('abdef') + manager.push('abdefg') + assert manager.content == ['a', 'ab', 'abc', 'ab', 'abd', 'abde', 'abdef', 'abdefg'] + assert manager.pointer == 7 + assert len(manager) == 8 + + +def test_undo(): + manager = UndoManager() + manager.push('a') + manager.push('ab') + manager.push('abc') + manager.push('ab') + assert manager.content == ['a', 'ab', 'abc', 'ab'] + assert manager.pointer == 3 + assert len(manager) == 4 + assert manager.undo() == 'ab' + assert manager.pointer == 2 + # 撤销后不会改变长度 + assert len(manager) == 4 + + +def test_redo(): + manager = UndoManager() + manager.push('a') + manager.push('ab') + manager.push('abc') + manager.push('ab') + assert manager.content == ['a', 'ab', 'abc', 'ab'] + assert manager.undo() == 'ab' + assert manager.undo() == 'abc' + assert manager.redo() == 'abc' + assert manager.redo() == 'ab' + + +def test_last_value(): + manager = UndoManager() + manager.push('a') + manager.push('ab') + manager.push('abc') + manager.push('ab') + assert manager.content == ['a', 'ab', 'abc', 'ab'] + assert manager.last_value() == 'ab' + assert manager.undo() == 'ab' + assert manager.last_value() == 'abc' + assert manager.undo() == 'abc' + assert manager.last_value() == 'ab' + assert manager.redo() == 'abc' + assert manager.last_value() == 'abc' + assert manager.redo() == 'ab' + assert manager.last_value() == 'ab'