diff --git a/packages/code_editor/assets/languages/en.ts b/packages/code_editor/assets/languages/en.ts
index b34577a5a4794c95257857a77da6da7203a726f1..8c10755ad3ba9ce9f1d36602f1896b8bada78ea5 100644
--- a/packages/code_editor/assets/languages/en.ts
+++ b/packages/code_editor/assets/languages/en.ts
@@ -1,5 +1,31 @@
+
+ BaseHandler
+
+
+ run code
+
+
+
+
+ run selected code
+
+
+
+
+ CodeEditorBaseObject
+
+
+ Not Implemented Error
+
+
+
+
+ {} is not implemented
+
+
+
FindInPathWidget
@@ -31,154 +57,194 @@
PMBaseCodeEdit
-
+
Format Code
-
+
Run Code
-
+
Run Selected Code
-
+
Save Code
-
+
Find Code
-
+
Replace
-
+
Find In Path
-
+
AutoComp
-
+
Goto Line
-
+
Goto Definition
-
+
Function Help
-
+
Help in Console
-
+
Add Breakpoint
-
+
Remove Breakpoint
-
+
View BreakPoints
+
+
+ Running {}
+
+
+
+
+ Undo
+
+
+
+
+ Redo
+
+
+
+
+ Cut
+
+
+
+
+ Copy
+
+
+
+
+ Paste
+
+
+
+
+ Delete
+
+
+
+
+ Select All
+
+
PMBaseEditor
-
+
Save
-
+
Save file "{0}"?
-
+
Save file
+
+
+ instant boot
+
+
PMCodeEditTabWidget
-
+
Warning
-
+
Editor does not support file:
%s
-
+
Open File
-
+
Run: %s
-
+
Run Python Code inside %s
-
+
Script
-
+
Builtin (3.8.5)
-
- This Editor does not support instant boot.
-
-
-
-
+
Builtin (%s)
-
+
Editor
-
+
Find In Path
@@ -348,21 +414,24 @@
PMPythonEditor
-
+
Help
-
+
Cannot get name.
Maybe There is:
1、Syntax error in your code.
2、No word under text cursor.
+
+
+ PythonHandler
-
- Error
+
+ Run code
diff --git a/packages/code_editor/assets/languages/zh_CN.qm b/packages/code_editor/assets/languages/zh_CN.qm
index 7f5950d5ffa96412ee63e191c5f4cd0ba7624149..c506b20fd6c3789b11039ce042b42ca3627a79e8 100644
Binary files a/packages/code_editor/assets/languages/zh_CN.qm and b/packages/code_editor/assets/languages/zh_CN.qm differ
diff --git a/packages/code_editor/assets/languages/zh_CN.ts b/packages/code_editor/assets/languages/zh_CN.ts
index 09ca3f8809135c690b242a982149e7ee51087d55..2e1644f776813febe99b86185203aa9f44bcd681 100644
--- a/packages/code_editor/assets/languages/zh_CN.ts
+++ b/packages/code_editor/assets/languages/zh_CN.ts
@@ -1,5 +1,31 @@
+
+ BaseHandler
+
+
+ run code
+ 运行代码
+
+
+
+ run selected code
+ 运行选中的代码
+
+
+
+ CodeEditorBaseObject
+
+
+ Not Implemented Error
+ 功能未实现
+
+
+
+ {} is not implemented
+ 该编辑器不支持{}
+
+
FindInPathWidget
@@ -31,80 +57,120 @@
PMBaseCodeEdit
-
+
Format Code
代码格式化
-
+
Run Code
运行代码
-
+
Run Selected Code
运行选中的代码
-
+
Save Code
保存代码
-
+
Find Code
查找代码
-
+
Replace
替换
-
+
Find In Path
在路径中查找
-
+
AutoComp
自动补全
-
+
Goto Line
跳转到行
-
+
Goto Definition
转到定义
-
+
Function Help
函数帮助
-
+
Help in Console
在控制台中显示帮助
-
+
Add Breakpoint
添加断点
-
+
Remove Breakpoint
移除断点
-
+
View BreakPoints
查看所有断点
+
+
+ Running {}
+ 运行{}
+
+
+
+ Undo
+ 撤销
+
+
+
+ Redo
+ 重做
+
+
+
+ Cut
+ 剪切
+
+
+
+ Copy
+ 复制
+
+
+
+ Paste
+ 粘贴
+
+
+
+ Delete
+ 删除
+
+
+
+ Select All
+ 全选
+
PMBaseEditor
@@ -169,77 +235,82 @@
查看所有断点
-
+
Save
保存
-
+
Save file "{0}"?
保存文件“{0}”?
-
+
Save file
保存文件
+
+
+ instant boot
+ 快速启动
+
PMCodeEditTabWidget
-
+
Warning
警告
-
+
Editor does not support file:
%s
编辑器不支持的文件类型:
%s
-
+
Open File
打开文件
-
+
Run: %s
运行:%s
-
+
Run Python Code inside %s
在%s中运行Python代码
-
+
Script
脚本
-
+
Builtin (3.8.5)
内置(3.8.5)
This Editor does not support instant boot.
- 本编辑器不支持立即启动。
+ 本编辑器不支持立即启动。
-
+
Builtin (%s)
内置(%s)
-
+
Editor
编辑器
-
+
Find In Path
在路径中查找
@@ -414,7 +485,7 @@
转到定义
-
+
Help
帮助
@@ -432,8 +503,8 @@
Cannot get name.
Maybe There is:
-1ÃÂÃÂÃÂÃÂÃÂÃÂÃÂãÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂSyntax error in your code.
-2ÃÂÃÂÃÂÃÂÃÂÃÂÃÂãÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂNo word under text cursor.
+1ÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂãÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂSyntax error in your code.
+2ÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂãÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂNo word under text cursor.
无法获取名称。
可能出现的问题:
1、语法错误;
@@ -442,18 +513,36 @@ Maybe There is:
Error
- 错误
+ 错误
Cannot get name.
Maybe There is:
-1ãSyntax error in your code.
-2ãNo word under text cursor.
- 无法获取名称。
+1ãÃÂÃÂSyntax error in your code.
+2ãÃÂÃÂNo word under text cursor.
+ 无法获取名称。
可能的问题:
1、代码中存在语法错误;
2、游标下没有单词。
+
+
+ Cannot get name.
+Maybe There is:
+1ãSyntax error in your code.
+2ãNo word under text cursor.
+ 无法获取名称。可能的问题:
+1、代码中存在语法错误;
+2、光标下没有单词。
+
+
+
+ PythonHandler
+
+
+ Run code
+ 运行代码
+
diff --git a/packages/code_editor/assets/languages/zh_TW.ts b/packages/code_editor/assets/languages/zh_TW.ts
index b34577a5a4794c95257857a77da6da7203a726f1..8c10755ad3ba9ce9f1d36602f1896b8bada78ea5 100644
--- a/packages/code_editor/assets/languages/zh_TW.ts
+++ b/packages/code_editor/assets/languages/zh_TW.ts
@@ -1,5 +1,31 @@
+
+ BaseHandler
+
+
+ run code
+
+
+
+
+ run selected code
+
+
+
+
+ CodeEditorBaseObject
+
+
+ Not Implemented Error
+
+
+
+
+ {} is not implemented
+
+
+
FindInPathWidget
@@ -31,154 +57,194 @@
PMBaseCodeEdit
-
+
Format Code
-
+
Run Code
-
+
Run Selected Code
-
+
Save Code
-
+
Find Code
-
+
Replace
-
+
Find In Path
-
+
AutoComp
-
+
Goto Line
-
+
Goto Definition
-
+
Function Help
-
+
Help in Console
-
+
Add Breakpoint
-
+
Remove Breakpoint
-
+
View BreakPoints
+
+
+ Running {}
+
+
+
+
+ Undo
+
+
+
+
+ Redo
+
+
+
+
+ Cut
+
+
+
+
+ Copy
+
+
+
+
+ Paste
+
+
+
+
+ Delete
+
+
+
+
+ Select All
+
+
PMBaseEditor
-
+
Save
-
+
Save file "{0}"?
-
+
Save file
+
+
+ instant boot
+
+
PMCodeEditTabWidget
-
+
Warning
-
+
Editor does not support file:
%s
-
+
Open File
-
+
Run: %s
-
+
Run Python Code inside %s
-
+
Script
-
+
Builtin (3.8.5)
-
- This Editor does not support instant boot.
-
-
-
-
+
Builtin (%s)
-
+
Editor
-
+
Find In Path
@@ -348,21 +414,24 @@
PMPythonEditor
-
+
Help
-
+
Cannot get name.
Maybe There is:
1、Syntax error in your code.
2、No word under text cursor.
+
+
+ PythonHandler
-
- Error
+
+ Run code
diff --git a/packages/code_editor/code_handlers/base_handler.py b/packages/code_editor/code_handlers/base_handler.py
index 699eac6efe0110e88326bfec3e3f75a41fae67f6..6de71f2d399f93af5ae766ff407f276f5bc1fa7b 100644
--- a/packages/code_editor/code_handlers/base_handler.py
+++ b/packages/code_editor/code_handlers/base_handler.py
@@ -1,8 +1,6 @@
from functools import cached_property
from typing import Tuple, Type, List, Optional
-from PySide2.QtWidgets import QMessageBox
-
from packages.code_editor.utils.base_object import CodeEditorBaseObject
@@ -25,6 +23,10 @@ class BaseAnalyzer(CodeEditorBaseObject):
def lines(self) -> List[str]:
return self.code.split('\n')
+ @cached_property
+ def lines_with_end(self):
+ return [f'{line}\n' for line in self.lines]
+
@cached_property
def current_line_index(self) -> int:
"""行的位置,用于进行索引,从0开始"""
@@ -53,6 +55,9 @@ class BaseHandler(CodeEditorBaseObject):
analyzer_class: Type[BaseAnalyzer] = BaseAnalyzer
analyzer: BaseAnalyzer = None
+ def __init__(self, path='Untitled'):
+ self.path = path
+
def feed(self, code: str, position: int, selection_range: Tuple[int, int]):
"""输入代码,以用于分析等操作
@@ -68,11 +73,6 @@ class BaseHandler(CodeEditorBaseObject):
return
self.analyzer = self.analyzer_class(code, position, selection_range)
- def __not_implemented_error(self, name):
- title = self.tr('Not Implemented Error')
- message = self.tr('{} is not implemented').format(name)
- QMessageBox.critical(None, title, message)
-
def run_code(self, code: str, hint: str = 'run code'):
"""运行一段代码
@@ -80,8 +80,11 @@ class BaseHandler(CodeEditorBaseObject):
code: 代码
hint: 代码的标题
"""
- self.__not_implemented_error(self.tr('run code'))
+ self._not_implemented_error(self.tr('run code'))
def run_selected_code(self):
"""运行选中的代码"""
- self.__not_implemented_error(self.tr('run selected code'))
+ self._not_implemented_error(self.tr('run selected code'))
+
+ def format_code(self) -> analyzer_class:
+ return self.analyzer
diff --git a/packages/code_editor/code_handlers/python_handler.py b/packages/code_editor/code_handlers/python_handler.py
index de84e4b7ff897bdce30d9a944f0400c4a078bbdc..d48d022efb99309aed7edfea8c7c43f6cc64b9df 100644
--- a/packages/code_editor/code_handlers/python_handler.py
+++ b/packages/code_editor/code_handlers/python_handler.py
@@ -1,22 +1,27 @@
-from functools import cached_property
-from typing import TYPE_CHECKING
+import logging
+
+from yapf.yapflib import yapf_api
from packages.code_editor.code_handlers.base_handler import BaseHandler
-if TYPE_CHECKING:
- from packages.ipython_console.main import ConsoleInterface
+logger = logging.getLogger(__name__)
class PythonHandler(BaseHandler):
- @cached_property
- def ipython_console(self) -> 'ConsoleInterface':
- return self.extension_lib.get_interface('ipython_console')
-
def run_code(self, code: str, hint: str = ''):
if hint == '':
hint = self.tr('Run code')
- self.ipython_console.run_command(command=code, hint_text=hint, hidden=False)
+ self.interfaces.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)
+ self.interfaces.ipython_console.run_command(command=code, hint_text=code, hidden=False)
+
+ def format_code(self):
+ try:
+ code, _ = yapf_api.FormatCode(
+ self.analyzer.code, style_config=str(self.settings.assets_dir / '.style.yapf'))
+ except Exception as e:
+ logger.exception(e)
+ code = self.analyzer.code
+ return self.analyzer_class(code, self.analyzer.cursor, self.analyzer.selection_range)
diff --git a/packages/code_editor/main.py b/packages/code_editor/main.py
index d4ad36cc70d6448771fbaa96531aebb547773bc2..d6033185c6ffcc9e45650844a98b7b3bb68431db 100644
--- a/packages/code_editor/main.py
+++ b/packages/code_editor/main.py
@@ -2,8 +2,10 @@ import logging
import os
from typing import Dict, Union, TYPE_CHECKING
+from .utils.base_object import CodeEditorBaseObject
+
if TYPE_CHECKING:
- from packages.file_tree.main import Interface as FileTreeInterface
+ pass
from features.extensions.extensionlib import BaseInterface, BaseExtension
from .widgets.tab_widget import PMCodeEditTabWidget
@@ -15,7 +17,7 @@ __prevent_from_ide_optimization = PMEditorToolbar # 这一行的目的是防止
logger = logging.getLogger('code_editor')
-class Extension(BaseExtension):
+class Extension(CodeEditorBaseObject, BaseExtension):
editor_widget: 'PMCodeEditTabWidget'
debuggers_widget: 'PMDebugConsoleTabWidget'
@@ -75,8 +77,7 @@ class Extension(BaseExtension):
def bind_event(self):
extensions = ('.py', '.c', '.cpp', '.h', '.pyx', '.md')
- file_tree: 'FileTreeInterface' = self.extension_lib.get_interface('file_tree')
- [file_tree.add_open_file_callback(ext, self.new_script) for ext in extensions]
+ [self.interfaces.file_tree.add_open_file_callback(ext, self.new_script) for ext in extensions]
def load_settings(self):
settings = {
diff --git a/packages/code_editor/scripts/translation/code_editor.pro b/packages/code_editor/scripts/translation/code_editor.pro
index 07719d69aa23c9eab68d2e0d7bd115d1a4e3d7c9..58e09bbc725dbdc0b553ae40c081287260119cc4 100644
--- a/packages/code_editor/scripts/translation/code_editor.pro
+++ b/packages/code_editor/scripts/translation/code_editor.pro
@@ -1,6 +1,9 @@
SOURCES = ..\..\main.py\
..\..\settings.py\
..\..\__init__.py\
+ ..\..\code_handlers\base_handler.py\
+ ..\..\code_handlers\python_handler.py\
+ ..\..\code_handlers\__init__.py\
..\..\utils\base_object.py\
..\..\utils\font_config.py\
..\..\utils\operation.py\
diff --git a/packages/code_editor/utils/base_object.py b/packages/code_editor/utils/base_object.py
index b026790ef8d5db20bf5a4780d3a2406bbeaf6a70..ba577dcf9c8a3218bdb9f88481662982f3dbed27 100644
--- a/packages/code_editor/utils/base_object.py
+++ b/packages/code_editor/utils/base_object.py
@@ -3,12 +3,17 @@ from pathlib import Path
from typing import Optional, Callable, TYPE_CHECKING
from PySide2.QtCore import QTranslator, QLocale
+from PySide2.QtWidgets import QMessageBox
from features.extensions.extensionlib import extension_lib
from ..settings import Settings
if TYPE_CHECKING:
from features.extensions.extensionlib.extension_lib import ExtensionLib
+ from ...ipython_console.main import ConsoleInterface
+ from ...applications_toolbar.main import ApplicationsInterface
+ from ...embedded_browser.main import Interface as EmbeddedBrowserInterface
+ from ...file_tree.main import Interface as FileTreeInterface
def _get_translator() -> Optional[QTranslator]:
@@ -51,3 +56,27 @@ class CodeEditorBaseObject:
return translations[0] if translations else source
return wrapper
+
+ def _not_implemented_error(self, name):
+ title = self.tr('Not Implemented Error')
+ message = self.tr('{} is not implemented').format(name)
+ QMessageBox.critical(None, title, message)
+
+ class __Interfaces:
+ @cached_property
+ def ipython_console(self) -> 'ConsoleInterface':
+ return CodeEditorBaseObject.extension_lib.get_interface('ipython_console')
+
+ @cached_property
+ def application_toolbar(self) -> 'ApplicationsInterface':
+ return CodeEditorBaseObject.extension_lib.get_interface('applications_toolbar')
+
+ @cached_property
+ def embedded_browser(self) -> 'EmbeddedBrowserInterface':
+ return CodeEditorBaseObject.extension_lib.get_interface('embedded_browser')
+
+ @cached_property
+ def file_tree(self) -> 'FileTreeInterface':
+ return CodeEditorBaseObject.extension_lib.get_interface('file_tree')
+
+ interfaces: __Interfaces = __Interfaces()
diff --git a/packages/code_editor/utils/operation.py b/packages/code_editor/utils/operation.py
index 996de192664f43c9e22453ee797f73e692134899..1dd3cddc64db1c172604dea04bfa21f07017aa8b 100644
--- a/packages/code_editor/utils/operation.py
+++ b/packages/code_editor/utils/operation.py
@@ -38,7 +38,7 @@ class Operation(CodeEditorBaseObject):
else:
icon = QIcon(self.settings.get_icon(icon_name))
self.__action: QAction = QAction(icon, label, widget)
- # noinspection PyUnresolvedReferences
+ # noinspection PyUnresolvedReferences
has_slot and self.__action.triggered.connect(slot)
# 设置QAction和QShortcut快捷键
diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py
index 1e92bf009eca08ce6f083557f0de601df5407520..4f3ef1d4384963436bdad7278dbe5560c40bb16f 100644
--- a/packages/code_editor/widgets/editors/base_editor.py
+++ b/packages/code_editor/widgets/editors/base_editor.py
@@ -100,10 +100,16 @@ class PMBaseEditor(CodeEditorBaseObject, QWidget):
self.goto_line_dialog = PMGotoLineDialog(parent=self)
self.last_save_time = 0
- self._path = ''
- self._modified = False
self._indicator_dict: Dict[str, str] = {}
+ @property
+ def _path(self):
+ return self.text_edit.path
+
+ @_path.setter
+ def _path(self, value):
+ self.text_edit.path = value
+
def set_lib(self, extension_lib):
self.extension_lib = extension_lib
@@ -172,7 +178,6 @@ class PMBaseEditor(CodeEditorBaseObject, QWidget):
:type: bool
:return: None
"""
- self._modified = modified
self.text_edit.modified = modified
self.slot_modification_changed(modified)
@@ -208,7 +213,7 @@ class PMBaseEditor(CodeEditorBaseObject, QWidget):
:param save_all: 当整个窗口关闭时增加是否全部关闭
:return:QMessageBox.StandardButton
"""
- if not self.modified():
+ if not self.is_modified:
return QMessageBox.Discard
buttons = QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
if save_all:
@@ -283,6 +288,10 @@ class PMBaseEditor(CodeEditorBaseObject, QWidget):
logger.warning(str(e))
return False
+ @property
+ def is_modified(self):
+ return self.text_edit.modified
+
def modified(self) -> bool:
"""
返回内容是否被修改
@@ -290,7 +299,7 @@ class PMBaseEditor(CodeEditorBaseObject, QWidget):
:rtype: bool
:return: 返回内容是否被修改
"""
- return self._modified
+ return self.is_modified
def is_temp_file(self) -> bool:
"""
@@ -340,7 +349,7 @@ class PMBaseEditor(CodeEditorBaseObject, QWidget):
if selected:
return self.text_edit.selected_code
else:
- return self.text_edit.toPlainText()
+ return self.text_edit.code
def slot_file_modified_externally(self):
self.last_save_time = time.time()
@@ -353,9 +362,6 @@ class PMBaseEditor(CodeEditorBaseObject, QWidget):
else:
raise ValueError('unrecognized input color scheme name %s' % color_scheme_name)
- def slot_code_format(self):
- """格式化代码"""
-
def slot_find_in_path(self):
selected_text = self.text_edit.selected_code
self.signal_request_find_in_path.emit(selected_text)
@@ -381,3 +387,7 @@ class PMBaseEditor(CodeEditorBaseObject, QWidget):
:return:
"""
pass
+
+ def instant_boot(self):
+ """快速启动,暂不理解其作用"""
+ self._not_implemented_error(self.tr('instant boot'))
diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py
index 5c1efe4035eb0c054969325c9938530858aca8b8..e64bd59b0b797a11f527cc593e9b94e488ae3a76 100644
--- a/packages/code_editor/widgets/editors/python_editor.py
+++ b/packages/code_editor/widgets/editors/python_editor.py
@@ -39,7 +39,6 @@ from typing import List, Tuple, Optional, TYPE_CHECKING, Callable
from PySide2.QtCore import SignalInstance, Signal, QDir
from PySide2.QtGui import QCloseEvent
from PySide2.QtWidgets import QMessageBox
-from yapf.yapflib import py3compat, yapf_api
from pmgwidgets import in_unit_test, PMGOneShotThreadRunner, run_python_file_in_terminal, parse_simplified_pmgjson, \
PMGPanelDialog
@@ -221,8 +220,7 @@ class PMPythonEditor(PMBaseEditor):
def show_help(help_namelist):
if len(help_namelist) > 0:
full_name: str = help_namelist[0].full_name
- self.extension_lib.get_interface('ipython_console').run_command('help(\'%s\')' % full_name,
- hidden=False)
+ self.interfaces.ipython_console.run_command(f'help(\'{full_name}\')', hidden=False)
else:
return
@@ -271,10 +269,9 @@ class PMPythonEditor(PMBaseEditor):
if not in_unit_test():
path = 'https://cn.bing.com/search?q=%s' % full_name
if self.browser_id is None:
- self.browser_id = self.extension_lib.get_interface('embedded_browser').open_url(url=path,
- side='right')
+ self.browser_id = self.interfaces.embedded_browser.open_url(url=path, side='right')
else:
- self.browser_id = self.extension_lib.get_interface('embedded_browser').open_url(
+ self.browser_id = self.interfaces.embedded_browser.open_url(
url=path, browser_id=self.browser_id, side='right')
else:
@@ -298,44 +295,6 @@ class PMPythonEditor(PMBaseEditor):
self.signal_goto_definition.emit(path, line, column)
logger.debug(f'{jedi_name_list},{path},{line},{column}')
- def slot_code_format(self):
- """格式化代码
-
- 注意,格式化之前需要保存光标的位置,格式化之后再将光标设置回当前的位置。
- """
- text = self.text().strip()
- first_line = self.text_edit.textCursor().blockNumber()
- if not text:
- return
- text = py3compat.removeBOM(text)
- try:
- reformatted_source, _ = yapf_api.FormatCode(
- text,
- filename=self.filename(),
- # print_diff=True,
- style_config=str(self.settings.assets_dir / '.style.yapf')
- )
- self.set_text(reformatted_source)
- # TODO 重构代码后需要保证光标的位置不动,可以考虑使用parso实现,这里目前的bug有些多
- self.text_edit.go_to_line(first_line + 1) # 跳转回开始时的行
- # self.text_edit.goToLine(first_line)
- except Exception as e:
- logger.warning(str(e))
- lines = re.findall(r'line (\d+)\)', str(e))
- row = -1
- if lines:
- # 跳转到指定行
- row = int(lines[0])
- row = row - 1 if row else 0
- col = self.text_edit.lineLength(row)
- self.text_edit.setCursorPosition(row, col - 1)
- # 标记波浪线
- self.text_edit.fillIndicatorRange(row, 0, row, col, self._indicator_error2)
- QMessageBox.critical(self, self.tr('Error'), str(e))
- if row > -1:
- # 清除被标记波浪线
- self.text_edit.clearIndicatorRange(row, 0, row, col, self._indicator_error2)
-
def slot_run_in_terminal(self):
"""在终端中运行代码
@@ -441,3 +400,6 @@ class PMPythonEditor(PMBaseEditor):
def closeEvent(self, event: QCloseEvent) -> None:
self.stop_auto_complete_thread()
+
+ def instant_boot(self):
+ self.interfaces.application_toolbar.create_instant_boot_python_file_process(self.path())
diff --git a/packages/code_editor/widgets/tab_widget.py b/packages/code_editor/widgets/tab_widget.py
index 90799cf98d72fa7ff399ace2441f47ba1da8c1bd..6ed28988b8b0e1b5963d8eb552ff3e6b0167df0e 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, Callable, TYPE_CHECKING
+from typing import Dict, Optional, Tuple, Any, Union, Callable, TYPE_CHECKING, Iterator
from PySide2.QtCore import QTimer, QThread, QDir
from PySide2.QtGui import QCloseEvent
@@ -25,11 +25,12 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
-class PMCodeEditTabWidget(QTabWidget, PMDockObject):
+class PMCodeEditTabWidget(CodeEditorBaseObject, QTabWidget, PMDockObject):
"""
多标签页编辑器控件
"""
extension_lib: 'ExtensionLib' = None
+ watchdog: Optional['PMGFileSystemWatchdog'] = None
if TYPE_CHECKING:
widget: Callable[[int], EDITOR_TYPE]
@@ -37,7 +38,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject):
def __init__(self, *args, **kwargs):
super(PMCodeEditTabWidget, self).__init__(*args, **kwargs)
- # 设置其尺寸政策为x,y轴均膨胀。
+ # 设置其尺寸策略为x,y轴均膨胀。
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.setMinimumWidth(200)
self._color_scheme = 'light'
@@ -52,31 +53,21 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject):
self.settings: Dict[str, object] = {}
self.debug_widget: Optional['PMDebugConsoleTabWidget'] = None
- self.watchdog: Optional['PMGFileSystemWatchdog'] = None
self.cursor_pos_manager = UndoManager(stack_size=30)
def set_extension_lib(self, extension_lib):
self.extension_lib = extension_lib
- CodeEditorBaseObject.extension_lib = extension_lib
self.extension_lib.Data.add_data_changed_callback(lambda name, var, source: self.slot_check_code(True))
self.extension_lib.Data.add_data_deleted_callback(lambda name, provider: self.slot_check_code(True))
def setup_ui(self) -> None:
- """
- `setup_ui`方法继承于PMDockObject,将被插件管理器直接调用。
- Returns:
-
- """
- # 文档模式
- self.setDocumentMode(True)
- # 标签页可关闭
- self.setTabsClosable(True)
- # 标签页可移动
- self.setMovable(True)
+ """将被插件管理器直接调用,不需要手动调用"""
+ self.setDocumentMode(True) # 文档模式
+ self.setTabsClosable(True) # 标签页可关闭
+ self.setMovable(True) # 标签页可移动
self._init_signals()
- # 创建默认空白页
- self.slot_new_script()
+ self.slot_new_script() # 创建默认空白页
# 初始化后台检测代码线程
self._timer_check = QTimer(self)
@@ -92,37 +83,26 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject):
self._timer_check.start(2000)
def on_work_dir_changed(self, path: str) -> None:
- """
- 处理工作路径改变时候的信号
- Deal with signal when work dir changed
-
- Args:
- path: new work directory
+ """处理工作路径改变时候的信号
- Returns:None
+ TODO 存在问题,watchdog的检测情况不够准鹕,有几种情况需要特殊考虑:
+ 文件在工作空间下的移动需要在工作空间文件夹下进行检测;
+ 外部的文件需要仅对该文件进行监测
+ Args:
+ path: 新的工作路径
"""
last_path = ''
if self.watchdog is not None:
last_path = self.watchdog.path
if os.path.normcase(last_path) != os.path.normcase(path):
- if self.watchdog is not None:
- self.watchdog.observer.stop()
- self.watchdog.deleteLater()
+ self.watchdog is not None and self.watchdog.stop()
self.watchdog = PMGFileSystemWatchdog(path)
self.watchdog.signal_file_modified.connect(self.on_file_modified)
self.watchdog.signal_file_moved.connect(self.signal_file_moved)
def signal_file_moved(self, path1, path2):
- """
- 文件被移动(或者重命名)时触发的事件。
- Args:
- path1:
- path2:
-
- Returns:
-
- """
+ """文件被移动(或者重命名)时触发的事件。"""
for i in range(self.count()):
editor = self.widget(i)
if os.path.normcase(path1) == os.path.normcase(editor._path):
@@ -150,33 +130,18 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject):
If the `on_file_Modified` method is called within 1 second of the internal timestamp `last_save_time` of the editor, this event is ignored (because it is highly likely to have been issued by the editor).
Since the watchdog can send multiple signals to the same file change operation, this function can be called to the same file multiple times in a short period of time (at the millisecond level), so each time the event is called, last_save_time is refreshed to prevent it
Problems with multiple calls in a short period of time occur.
-
- Args:
- path:
-
- Returns:
-
"""
- logger.warning('file modified:' + path + ' at time:' + str(time.time()))
+ logger.warning(f'file modified:{path} at time:{str(time.time())}')
editor = self.get_editor_tab_by_path(path)
if editor is not None:
if time.time() - editor.last_save_time > 1:
editor.slot_file_modified_externally()
def get_editor_tab_by_path(self, path) -> 'EDITOR_TYPE':
- """
- 通过路径获取编辑器。如果没有打开,则返回None
- Args:
- path:
-
- Returns:
-
- """
- for i in range(self.count()):
- w = self.widget(i)
- if os.path.normcase(w.path()) == os.path.normcase(path):
- return w
- return None
+ """通过路径获取编辑器。如果没有打开,则返回None"""
+ path = os.path.normcase(path)
+ editors = [w for w in self.all_editors if os.path.normcase(w.path()) == path]
+ return editors[0] if editors else None
@property
def current_editor(self):
@@ -186,6 +151,15 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject):
def current_text_edit(self):
return self.current_editor.text_edit
+ @property
+ def all_editors(self) -> Iterator['EDITOR_TYPE']:
+ yield from (self.widget(i) for i in range(self.count()))
+
+ @property
+ def all_other_editors(self):
+ current = self.current_editor
+ yield from (e for e in self.all_editors if e is not current)
+
def get_current_editor(self) -> 'EDITOR_TYPE':
"""
get current editor
@@ -301,7 +275,6 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject):
if hasattr(widget, 'signal_request_find_in_path'):
widget.signal_request_find_in_path.connect(self.slot_find_in_path)
- pass
self.addTab(widget, widget.filename())
self.setCurrentWidget(widget)
if not pmgwidgets.in_unit_test(): # 如果不在单元测试,则切换工具条。
@@ -428,7 +401,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject):
if not code:
return
if not in_unit_test():
- self.extension_lib.get_interface('ipython_console').run_command(command=code, hint_text=hint, hidden=False)
+ self.interfaces.ipython_console.run_command(command=code, hint_text=hint, hidden=False)
else:
logger.info("In Unit test at method 'slot_run_script'.code is :\n%s,\nhint is :%s" % (code, hint))
@@ -440,34 +413,25 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject):
:type index: int
:return:
"""
- widget = self.widget(index)
- if not widget:
+ editor = self.widget(index)
+ if not editor:
return
- if self.count() == 1 and not widget._modified() and not widget.text():
+ if self.count() == 1 and not editor.is_modified and not editor.text():
# 不关闭
return
- if widget.slot_about_close() == QMessageBox.Cancel:
+ if editor.slot_about_close() == QMessageBox.Cancel:
return
self.removeTab(index)
- widget.close()
- widget.deleteLater()
+ editor.close()
+ editor.deleteLater()
if self.count() == 0:
self._index = 0
self.slot_new_script()
- def slot_run_in_terminal(self):
- """
- 在终端中运行
- :return:
- """
- editor: 'PMPythonEditor' = self.currentWidget()
- editor.slot_run_in_terminal()
-
def slot_run_isolated(self):
editor: 'PMPythonEditor' = self.currentWidget()
path = editor.path()
- self.extension_lib.get_interface('applications_toolbar').create_python_file_process(path,
- self._current_executable)
+ self.interfaces.application_toolbar.create_python_file_process(path, self._current_executable)
def _init_signals(self):
# 标签页关闭信号
@@ -518,8 +482,8 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject):
('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),
+ ('button_run_in_terminal', lambda: self.current_editor.slot_run_in_terminal()),
+ ('button_instant_boot', lambda: self.current_editor.instant_boot()),
):
self.extension_lib.UI.get_toolbar_widget('code_editor_toolbar', widget_name) \
.clicked.connect(callback)
@@ -528,24 +492,8 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject):
traceback.print_exc()
logger.warning(str(e))
- def slot_instant_boot(self):
- """
- 快速启动
- Returns:
-
- """
- if isinstance(self.get_current_editor(), PMPythonEditor):
- path = self.get_current_editor().path()
- self.extension_lib.get_interface('applications_toolbar').create_instant_boot_python_file_process(path)
- else:
- QMessageBox.warning(self, self.tr('Warning'), self.tr('This Editor does not support instant boot.'))
-
def update_interpreter_selections(self, combo: QComboBox):
- """
- 刷新所有解释器状态
- Returns:
-
- """
+ """刷新所有解释器状态"""
interpreters = self.extension_lib.Program.get_settings_item_from_file("config.ini", 'RUN/EXTERNAL_INTERPRETERS')
combo.clear()
combo.addItem(self.tr('Builtin (%s)' % sys.version.split()[0]))
@@ -553,11 +501,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject):
combo.addItem(interpreter['name'] + ' (%s)' % interpreter['version'])
def change_current_interpreter(self, interpreter_index: int):
- """
- 切换当前解释器
- Returns:
-
- """
+ """切换当前解释器"""
if interpreter_index == -1:
return
elif interpreter_index == 0:
@@ -568,21 +512,14 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject):
interpreter_index - 1]['path']
def on_tab_switched(self, index: int) -> None:
- for i in range(self.count()):
- if i != index:
- w: 'PMBaseEditor' = self.widget(i)
- if hasattr(w, 'find_dialog') and w.find_dialog is not None:
- w.find_dialog.hide()
+ current = self.widget(index)
+ for editor in self.all_editors:
+ if editor is not current and hasattr(editor, 'find_dialog') and editor.find_dialog is not None:
+ editor.find_dialog.hide()
def set_background_syntax_checking(self, checking: bool):
self._worker_check.background_checking = checking
- def set_smart_autocomp_stat(self, smart_autocomp_on: bool):
- for i in range(self.count()):
- # if i != index:
- w: 'PMBaseEditor' = self.widget(i)
- w.set_smart_autocomp_stat(smart_autocomp_on)
-
def update_settings(self, settings: Dict[str, Any]):
self.settings = settings
self.set_background_syntax_checking(settings['check_syntax_background'])
@@ -592,21 +529,15 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject):
self._worker_check.stop()
self._thread_check.quit()
self._thread_check.wait(500)
- for i in range(self.count()):
- self.widget(i).close() # TODO:这里结构不行!
- widgets = [self.widget(i) for i in range(self.count()) if self.widget(i).modified()]
- if not widgets:
- return
+ [editor.close() for editor in self.all_editors]
+ # TODO:这里结构不行!
+ widgets = [widget for widget in self.all_editors if widget.is_modified]
save_all = False
for widget in widgets:
- if save_all:
- # 保存全部则直接进入保存文件流程
+ if save_all: # 保存全部则直接进入保存文件流程
widget.slot_save()
- continue
-
- ret = widget.slot_about_close(True)
-
- save_all = ret == QMessageBox.SaveAll
+ else:
+ save_all = widget.slot_about_close(True) == QMessageBox.SaveAll
def get_all_breakpoints(self, language='python') -> str:
if language == 'python':
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 308c5823f4f641a87b7d8a5829b8aed5bfaa5560..9451721b3611656794d5303b88e5dc5f39696fa3 100644
--- a/packages/code_editor/widgets/text_edit/base_text_edit.py
+++ b/packages/code_editor/widgets/text_edit/base_text_edit.py
@@ -8,16 +8,16 @@ from itertools import groupby
from queue import Queue
from typing import Callable, Tuple, Dict, List, TYPE_CHECKING, Type, Any
-from PySide2.QtCore import SignalInstance, Signal, Qt, QTimer, QModelIndex, QUrl, QRect, QPoint, QCoreApplication
+from PySide2.QtCore import SignalInstance, Signal, Qt, QTimer, QModelIndex, QUrl, QRect, QPoint
from PySide2.QtGui import QFocusEvent, QTextCursor, QMouseEvent, QKeyEvent, QDragEnterEvent, QDropEvent, QPainter, \
QColor, QTextFormat, QFontDatabase, QFont, QTextDocument
-from PySide2.QtWidgets import QPlainTextEdit, QWidget, QApplication, QTextEdit, QLabel, QMenu
+from PySide2.QtWidgets import QPlainTextEdit, QWidget, QApplication, QTextEdit, QLabel, QMenu, QAction
from jedi.api.classes import Completion as CompletionResult
import utils
from .line_number_area import QLineNumberArea
from ..auto_complete_dropdown.base_auto_complete_dropdown import BaseAutoCompleteDropdownWidget
-from ...code_handlers.base_handler import BaseHandler
+from ...code_handlers.base_handler import BaseAnalyzer, BaseHandler
from ...utils.base_object import CodeEditorBaseObject
from ...utils.grammar_analyzer.get_indent import get_indent
from ...utils.grammar_analyzer.grammar_analyzer import GrammarAnalyzer
@@ -94,12 +94,12 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit):
fontId = QFontDatabase.addApplicationFont(
os.path.join(utils.get_root_dir(), 'resources', 'fonts', 'SourceCodePro-Regular.ttf'))
- fontFamilies = QFontDatabase.applicationFontFamilies(fontId)
+ font_families = QFontDatabase.applicationFontFamilies(fontId)
self.font = QFont()
self.font.setPointSize(15) # 设置行号的字体大小
# font.setFamily("Microsoft YaHei UI") # 设置行号的字体
- self.font.setFamily(fontFamilies[0]) # 设置行号的字体
+ self.font.setFamily(font_families[0]) # 设置行号的字体
self.setFont(self.font)
self.line_number_area = QLineNumberArea(self)
@@ -111,7 +111,6 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit):
self.setLineWrapMode(QPlainTextEdit.NoWrap)
self.doc_tab_widget = parent
- self.path = ''
self.modified = False
self._last_text = ''
self.text_modified_signal_allowed = True
@@ -130,6 +129,14 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit):
self.__bind_signals()
self.__create_operations()
+ @property
+ def path(self):
+ return self.handler.path
+
+ @path.setter
+ def path(self, value):
+ self.handler.path = value
+
# noinspection PyUnresolvedReferences
def __bind_signals(self):
# 定时触发的事件
@@ -174,7 +181,7 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit):
# 这里代码比较紧凑,以节省行数
self.__menu_operations = [Operation( # 格式化代码
widget=self, name='format code', label=self.tr('Format Code'), key='Ctrl+Alt+F', icon_name='format.svg',
- slot=self.parent().slot_code_format,
+ slot=lambda: self.update_from_analyzer(self.handler.format_code()),
conditions=[text_exists],
), Operation( # 运行代码
widget=self, name='run code', label=self.tr('Run Code'), key='Ctrl+R', icon_name='run.svg',
@@ -221,10 +228,18 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit):
widget=self, name='view breakpoints', label=self.tr('View BreakPoints'),
)]
+ def __only_for_qt_linguist_translation_and_never_need_to_be_called(self):
+ self.tr('Undo'), self.tr('Redo'), self.tr('Cut'), self.tr('Copy'), self.tr('Copy'), self.tr('Paste')
+ self.tr('Delete'), self.tr('Select All')
+
def createStandardContextMenu(self) -> QMenu:
menu = super().createStandardContextMenu()
- # 遍历本身已有的菜单项做翻译处理,前提是要加载了Qt自带的翻译文件
- [action.setText(QCoreApplication.translate('QTextControl', action.text())) for action in menu.actions()]
+ # 翻译原有的菜单项
+ actions: List[QAction] = menu.actions()
+ for action in actions:
+ text = action.text()
+ first, second = text.split('\t') if '\t' in text else (text, '')
+ action.setText(f"{self.tr(first.replace('&', ''))}\t{second}")
# 添加额外菜单
menu.addSeparator(), [menu.addAction(operation.action) for operation in self.__menu_operations]
return menu
@@ -342,6 +357,12 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit):
cursor = self.textCursor()
self.handler.feed(self.code, cursor.position(), (cursor.selectionStart(), cursor.selectionEnd()))
+ def update_from_analyzer(self, analyzer: 'BaseAnalyzer'):
+ self.code = analyzer.code
+ cursor = self.textCursor()
+ cursor.setPosition(analyzer.cursor)
+ self.setTextCursor(cursor)
+
def on_text_changed(self):
"""文字发生改变时的方法"""
if not self.modified:
@@ -496,6 +517,10 @@ class PMBaseCodeEdit(CodeEditorBaseObject, QPlainTextEdit):
def code(self):
return self.toPlainText()
+ @code.setter
+ def code(self, value: str):
+ self.setPlainText(value)
+
def on_right_parenthesis(self, event: QKeyEvent):
left, right = {
Qt.Key_ParenRight: ('(', ')'),
diff --git a/pmgwidgets/utilities/platform/filesyswatchdog.py b/pmgwidgets/utilities/platform/filesyswatchdog.py
index 65d0e687c4c5905de8080291b2b50774cde157bb..58835757bdbfd27ec1b8b6f66776c1bcb9465b15 100644
--- a/pmgwidgets/utilities/platform/filesyswatchdog.py
+++ b/pmgwidgets/utilities/platform/filesyswatchdog.py
@@ -5,26 +5,21 @@
https://stackoverflow.com/questions/35874217/watchdog-pythons-library-how-to-send-signal-when-a-file-is-modified
源代码为PyQt4,本人整理、移植到qtpy。(Originally code was in PyQt4 and I transplanted to PySide2.)
"""
-import os
-from PySide2.QtCore import Signal, QThread
-from PySide2.QtWidgets import QMainWindow, QApplication, QLabel
+from PySide2.QtCore import Signal, QThread, SignalInstance
from watchdog.events import FileSystemEventHandler, FileModifiedEvent, FileMovedEvent, \
FileCreatedEvent, FileDeletedEvent
from watchdog.observers import Observer
class MyEventHandler(FileSystemEventHandler, QThread):
- signal_file_modified = Signal(str)
- signal_file_created = Signal(str)
- signal_file_deleted = Signal(str)
- signal_file_moved = Signal(str, str)
-
- def __init__(self):
- super(MyEventHandler, self).__init__()
+ signal_file_modified: SignalInstance = Signal(str)
+ signal_file_created: SignalInstance = Signal(str)
+ signal_file_deleted: SignalInstance = Signal(str)
+ signal_file_moved: SignalInstance = Signal(str, str) # 仅当在本文件夹内才会触发,如果移动到了其他文件夹则触发删除信号
def on_deleted(self, event: FileDeletedEvent):
- self.signal_file_modified.emit(event.src_path)
+ self.signal_file_deleted.emit(event.src_path)
def on_modified(self, event: FileModifiedEvent):
self.signal_file_modified.emit(event.src_path)
@@ -37,46 +32,26 @@ class MyEventHandler(FileSystemEventHandler, QThread):
class PMGFileSystemWatchdog(QThread):
+ """
+ Watch Dog 的一切操作都是在path这个文件夹下完成的,其余位置的文件变更并不会被监测到。
+ 因此,如果将一个文件移动出这个文件夹,触发的并不是“修改”信号,而是触发“删除”信号。
+
+ # TODO 目前仅支持监控一整个文件夹,需要添加监控单一文件的方法
+ """
+
def __init__(self, path):
super(PMGFileSystemWatchdog, self).__init__()
self.path = path
self.observer = Observer()
self.event_handler = MyEventHandler()
- self.signal_file_modified: Signal = self.event_handler.signal_file_modified
- self.signal_file_created: Signal = self.event_handler.signal_file_created
- self.signal_file_deleted: Signal = self.event_handler.signal_file_deleted
- self.signal_file_moved: Signal = self.event_handler.signal_file_moved
+ self.signal_file_modified: SignalInstance = self.event_handler.signal_file_modified
+ self.signal_file_created: SignalInstance = self.event_handler.signal_file_created
+ self.signal_file_deleted: SignalInstance = self.event_handler.signal_file_deleted
+ self.signal_file_moved: SignalInstance = self.event_handler.signal_file_moved
self.observer.schedule(self.event_handler, self.path, recursive=True)
self.observer.start()
- # self.event_handler.signal_file_modified.connect(self.on_modified)
-
- def run(self):
- pass
-
- # def on_modified(self, event):
- # print('modified!', event)
- # self.emit(QtCore.SIGNAL("fileModified1"))
-
-
-if __name__ == '__main__':
- class MainWindow(QMainWindow):
- def __init__(self):
- super(MainWindow, self).__init__()
- d = os.path.dirname(__file__)
- path = os.path.join(d, 'test') # 对其下的test文件夹进行看门狗监控
- self.label = QLabel('aaaa')
- self.fileWatcher = PMGFileSystemWatchdog(path)
- # self.fileWatcher.start()
- self.setCentralWidget(self.label)
- self.fileWatcher.event_handler.signal_file_modified.connect(self.fileModified)
- self.show()
-
- def fileModified(self, text: str):
- self.label.setText(text)
-
- app = QApplication([])
- window = MainWindow()
- window.show()
- app.exec_()
+ def stop(self):
+ self.observer.stop()
+ self.deleteLater()
diff --git a/tests/test_pmgwidgets/test_utilities/test_filesyswatchdog.py b/tests/test_pmgwidgets/test_utilities/test_filesyswatchdog.py
new file mode 100644
index 0000000000000000000000000000000000000000..683447fb4324013426a511c42fa133eb9a719e5e
--- /dev/null
+++ b/tests/test_pmgwidgets/test_utilities/test_filesyswatchdog.py
@@ -0,0 +1,69 @@
+import os.path
+import shutil
+import tempfile
+
+import pytest
+import pytestqt.exceptions
+
+from pmgwidgets.utilities.platform.filesyswatchdog import PMGFileSystemWatchdog
+
+
+def test_all(qtbot):
+ with tempfile.TemporaryDirectory() as directory:
+ watcher = PMGFileSystemWatchdog(directory)
+ file = os.path.join(directory, 'created.txt')
+
+ def check(path):
+ return path == file
+
+ # 创建文件可以触发信号
+ with qtbot.wait_signal(watcher.signal_file_created, check_params_cb=check):
+ with open(file, 'w', encoding='utf-8') as f:
+ f.write('created a file')
+
+ # 修改文件可以触发信号
+ with qtbot.wait_signal(watcher.signal_file_modified, check_params_cb=check):
+ with open(file, 'a', encoding='utf-8') as f:
+ f.write('\nnext line')
+
+ def check_move_params(src, dst):
+ return src == file and dst == moved_file
+
+ # 同文件夹内移动文件可以触发移动信号
+ moved_file = os.path.join(directory, 'moved.txt')
+ with qtbot.wait_signal(watcher.signal_file_moved, check_params_cb=check_move_params):
+ shutil.move(file, moved_file)
+
+ # 移动到其它文件夹将触发删除信号
+ with open(file, 'w', encoding='utf-8') as f:
+ f.write('created again')
+ with tempfile.TemporaryDirectory() as another_directory:
+ with qtbot.wait_signal(watcher.signal_file_deleted, check_params_cb=check, timeout=1000):
+ shutil.move(file, os.path.join(another_directory, 'file.txt'))
+
+ # 删除文件将触发删除信号
+ with open(file, 'w', encoding='utf-8') as f:
+ f.write('created again')
+ with qtbot.wait_signal(watcher.signal_file_deleted, check_params_cb=check, timeout=1000):
+ os.remove(file)
+
+
+def test_stop(qtbot):
+ with tempfile.TemporaryDirectory() as directory:
+ watcher = PMGFileSystemWatchdog(directory)
+ file = os.path.join(directory, 'created.txt')
+ with qtbot.wait_signal(watcher.signal_file_created):
+ with open(file, 'w', encoding='utf-8') as f:
+ f.write('created')
+ watcher.stop()
+ with pytest.raises(pytestqt.exceptions.TimeoutError):
+ with qtbot.wait_signal(watcher.signal_file_deleted, timeout=100):
+ os.remove(file)
+#
+# def test_check_a_single_file(qtbot):
+# with tempfile.TemporaryDirectory() as directory:
+# file = os.path.join(directory, 'file.txt')
+# watcher = PMGFileSystemWatchdog(file)
+# with qtbot.wait_signal(watcher.signal_file_created):
+# with open(file, 'w', encoding='utf-8') as f:
+# f.write('created')