From 97639c70d22049b01c4d926bf705c00f6856274b Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 11:22:02 +0800 Subject: [PATCH 001/108] =?UTF-8?q?=E5=BE=AE=E8=B0=83=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/codeeditor/pythoneditor.py | 2 +- .../qtpyeditor/codeeditor/abstracteditor.py | 12 ++------ .../qtpyeditor/codeeditor/baseeditor.py | 30 ++++++++----------- 3 files changed, 16 insertions(+), 28 deletions(-) diff --git a/packages/code_editor/codeeditor/pythoneditor.py b/packages/code_editor/codeeditor/pythoneditor.py index df3bcd3e..14efad54 100644 --- a/packages/code_editor/codeeditor/pythoneditor.py +++ b/packages/code_editor/codeeditor/pythoneditor.py @@ -394,7 +394,7 @@ class PMPythonEditor(PMGPythonEditor): :return: """ - text = self.text(True).strip() + text = self.text(selected=True).strip() if not text: text = self.current_line_text().strip() diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py index 810b8735..42abf9eb 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py @@ -33,20 +33,12 @@ Created on 2020/9/7 __version__ = '0.1' -import ast -import json import logging import os -import re -import time -from itertools import groupby -from typing import TYPE_CHECKING, List, Iterable, Dict, Set, Tuple, Any +from typing import Dict, Any -from PySide2.QtCore import QDir from PySide2.QtWidgets import QWidget, QMessageBox -from pmgwidgets import in_unit_test - logger = logging.getLogger(__name__) @@ -253,7 +245,7 @@ class PMAbstractEditor(QWidget): Returns: """ - return '' + raise NotImplementedError('该编辑器不支持获取代码文本') def slot_file_modified_externally(self): return diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py index 4305e25c..15581d40 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py @@ -33,23 +33,19 @@ Created on 2020/9/7 __version__ = '0.1' -import ast -import json import logging import os -import re import time -from itertools import groupby -from typing import TYPE_CHECKING, List, Iterable, Dict, Set, Tuple, Any -from PySide2.QtGui import QIcon, QKeySequence, QTextDocument, QTextCursor, QTextBlock, QDropEvent -from PySide2.QtCore import QDir, QCoreApplication, Qt, QPoint, Signal, QTranslator, QLocale, QUrl, SignalInstance +from typing import TYPE_CHECKING, List, Dict, Any + +from PySide2.QtCore import QDir, QCoreApplication, Qt, QPoint, Signal, QTranslator, QLocale, SignalInstance +from PySide2.QtGui import QIcon, QKeySequence, QTextDocument, QTextCursor from PySide2.QtWidgets import QWidget, QMessageBox, QFileDialog, QAction, QShortcut, QDialog, QVBoxLayout, QPushButton, \ QHBoxLayout, QApplication, QLabel -from pmgwidgets import in_unit_test -from pmgwidgets.widgets.composited import PMGPanel -from packages.code_editor.codeeditor.qtpyeditor.ui.gotoline import Ui_DialogGoto from packages.code_editor.codeeditor.qtpyeditor.codeeditor.abstracteditor import PMAbstractEditor +from packages.code_editor.codeeditor.qtpyeditor.ui.gotoline import Ui_DialogGoto +from pmgwidgets.widgets.composited import PMGPanel if TYPE_CHECKING: from packages.code_editor.codeeditor.qtpyeditor.codeedit import PMBaseCodeEdit @@ -516,18 +512,18 @@ class PMGBaseEditor(PMAbstractEditor): self.setWindowTitle(new_title) def text(self, selected: bool = False) -> str: - """ - 返回编辑器选中或者全部内容 + """返回编辑器选中或者全部内容。 + Args: - selected: + selected: True则返回选中的内容,False则返回全部内容 Returns: - + str, 选中的或全部的代码 """ - if not selected: - return self.text_edit.toPlainText() + if selected: + return self.text_edit.getSelectedText() else: - pass + return self.text_edit.toPlainText() def slot_file_modified_externally(self): return -- Gitee From a697a9f51a691541502a8ebb4fa5e0ed3308cdd1 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 11:42:02 +0800 Subject: [PATCH 002/108] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E5=88=9D=E6=AD=A5=E7=9A=84pyside2=E6=B5=8B=E8=AF=95=E7=94=A8?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codeeditor/qtpyeditor/codeedit/pythonedit.py | 5 +++-- requirements_dev.txt | 1 + tests/test_code_editor/test_gui/__init__.py | 0 tests/test_code_editor/test_gui/test_python_edit.py | 12 ++++++++++++ 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 tests/test_code_editor/test_gui/__init__.py create mode 100644 tests/test_code_editor/test_gui/test_python_edit.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py index 78976dde..bc3cd154 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py @@ -16,11 +16,12 @@ from packages.code_editor.codeeditor.qtpyeditor.Utilities import AutoCompThread from packages.code_editor.codeeditor.qtpyeditor.codeedit import PMBaseCodeEdit from packages.code_editor.codeeditor.qtpyeditor.highlighters import PythonHighlighter -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) if TYPE_CHECKING: from jedi.api import Completion +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + class PMPythonCodeEdit(PMBaseCodeEdit): def __init__(self, parent=None): diff --git a/requirements_dev.txt b/requirements_dev.txt index 417dc8a6..ca98cd0d 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -5,3 +5,4 @@ recommonmark flake8 pytest numpydoc +pytest-qt diff --git a/tests/test_code_editor/test_gui/__init__.py b/tests/test_code_editor/test_gui/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_code_editor/test_gui/test_python_edit.py b/tests/test_code_editor/test_gui/test_python_edit.py new file mode 100644 index 00000000..74f6c35e --- /dev/null +++ b/tests/test_code_editor/test_gui/test_python_edit.py @@ -0,0 +1,12 @@ +from packages.code_editor.codeeditor.qtpyeditor.codeedit import PMPythonCodeEdit + + +def test_myapp(qtbot): + window = PMPythonCodeEdit() + qtbot.addWidget(window) + window.show() + qtbot.waitForWindowShown(window) + window.setPlainText('a = 123') + qtbot.wait(1000) + assert window.toPlainText() == 'a = 123' + window.close() -- Gitee From ab565ecd12936fd6a6c452e3086210fb667daadb Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 16:28:07 +0800 Subject: [PATCH 003/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qtpyeditor/codeedit/basecodeedit.py | 51 ++++++++----------- .../test_gui/test_python_edit.py | 2 - 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py index 85ccb4ac..858e03c4 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py @@ -9,9 +9,9 @@ import re import time from itertools import groupby from queue import Queue -from typing import List, Tuple, Dict, TYPE_CHECKING +from typing import List, Tuple, Dict, TYPE_CHECKING, Callable -from PySide2.QtCore import Qt, QModelIndex, Signal, QTimer, QUrl +from PySide2.QtCore import Qt, QModelIndex, Signal, QTimer, QUrl, SignalInstance from PySide2.QtGui import QDropEvent, QPixmap from PySide2.QtGui import QTextCursor, QKeyEvent, QMouseEvent, QIcon, QFocusEvent from PySide2.QtWidgets import QApplication, QWidget, QPlainTextEdit, QTableWidget, QTableWidgetItem, QHeaderView @@ -25,6 +25,7 @@ from packages.code_editor.codeeditor.qtpyeditor.syntaxana import getIndent if TYPE_CHECKING: from jedi.api import Completion + from packages.code_editor.codeeditor.qtpyeditor import PMGPythonEditor logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -164,26 +165,31 @@ class AutoCompList(QTableWidget): class PMBaseCodeEdit(QCodeEditor): # cursorPositionChanged = Signal() - signal_save = Signal() - signal_focused_in = Signal( - QFocusEvent) # Signal Focused in . But as it was too often triggered, I use click event instead. - signal_idle = Signal() - signal_text_modified = Signal() # If status changed from unmodified to modified, this signal emits. - signal_file_dropped = Signal(str) + signal_save: SignalInstance = Signal() + # Signal Focused in . But as it was too often triggered, I use click event instead. + signal_focused_in: SignalInstance = Signal(QFocusEvent) + signal_idle: SignalInstance = Signal() + signal_text_modified: SignalInstance = Signal() # If status changed from unmodified to modified, this signal emits. + signal_file_dropped: SignalInstance = Signal(str) + textChanged: SignalInstance + UPDATE_CODE_HIGHLIGHT = 1 + textCursor: Callable[[], QTextCursor] + doc_tab_widget: 'PMGPythonEditor' + highlighter: 'PythonHighlighter' + def __init__(self, parent=None): super(PMBaseCodeEdit, self).__init__(parent) self._last_operation: float = 0.0 # 记录上次操作的时间 self.update_request_queue = Queue() self.setLineWrapMode(QPlainTextEdit.NoWrap) - self.doc_tab_widget: 'PMGPythonEditor' = parent + self.doc_tab_widget = parent self.filename = '*' self.path = '' self.modified = False self._last_text = '' - self.highlighter: 'PythonHighlighter' = None self.text_modified_signal_allowed = True self.setTabChangesFocus(False) @@ -201,10 +207,7 @@ class PMBaseCodeEdit(QCodeEditor): self.textChanged.connect(self.update_last_operation_time) def update_last_operation_time(self): - """ - 更新上一次操作的时间 - :return: - """ + """更新上一次操作的时间""" self._last_operation = time.time() def update_ui(self): @@ -223,12 +226,7 @@ class PMBaseCodeEdit(QCodeEditor): focus_widget.setFocus() def on_autocomp_signal_received(self, text_cursor_pos: tuple, completions: List['jedi.api.classes.Completion']): - ''' - 当收到自动补全提示信号时,执行的函数。 - :param text_cursor_pos: - :param completions: - :return: - ''' + """当收到自动补全提示信号时,执行的函数。""" current_cursor_pos = self._get_textcursor_pos() if current_cursor_pos[0] + 1 == text_cursor_pos[0] and current_cursor_pos[1] == text_cursor_pos[1]: if len(completions) == 1: @@ -244,10 +242,7 @@ class PMBaseCodeEdit(QCodeEditor): self.popup_hint_widget.hide_autocomp() def on_text_changed(self): - """ - 文字发生改变时的方法 - :return: - """ + """文字发生改变时的方法""" if self.modified == True: pass else: @@ -273,8 +268,7 @@ class PMBaseCodeEdit(QCodeEditor): return '' col = self.textCursor().columnNumber() nearby_text = block_text[:col] - hint = re.split( - '[.:;,?!\s \+ \- = \* \\ \/ \( \)\[\]\{\} ]', nearby_text)[-1] + hint = re.split('[.:;,?!\s \+ \- = \* \\ \/ \( \)\[\]\{\} ]', nearby_text)[-1] return hint def _request_autocomp(self): @@ -693,9 +687,6 @@ class PMBaseCodeEdit(QCodeEditor): def rehighlight(self): self.update_request_queue.put(self.UPDATE_CODE_HIGHLIGHT) - def textCursor(self) -> QTextCursor: - return super(PMBaseCodeEdit, self).textCursor() - def dragEnterEvent(self, QDragEnterEvent): # 3 print('Drag Enter') if QDragEnterEvent.mimeData().hasText(): @@ -711,7 +702,7 @@ class PMBaseCodeEdit(QCodeEditor): pass def dropEvent(self, drop_event: QDropEvent): # 6 - url: QUrl = None + url: QUrl urls = drop_event.mimeData().urls() for url in urls: try: diff --git a/tests/test_code_editor/test_gui/test_python_edit.py b/tests/test_code_editor/test_gui/test_python_edit.py index 74f6c35e..0e84171f 100644 --- a/tests/test_code_editor/test_gui/test_python_edit.py +++ b/tests/test_code_editor/test_gui/test_python_edit.py @@ -8,5 +8,3 @@ def test_myapp(qtbot): qtbot.waitForWindowShown(window) window.setPlainText('a = 123') qtbot.wait(1000) - assert window.toPlainText() == 'a = 123' - window.close() -- Gitee From ee6336c790ee9f63ffb7aefa6eeb2c49287982db Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 17:11:20 +0800 Subject: [PATCH 004/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codeeditor/qtpyeditor/codeedit/basecodeedit.py | 13 +++++++++---- .../codeeditor/qtpyeditor/codeedit/pythonedit.py | 10 +++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py index 858e03c4..fe13a7e3 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py @@ -164,6 +164,10 @@ class AutoCompList(QTableWidget): class PMBaseCodeEdit(QCodeEditor): + """ + 与语言无关的编辑器相关操作应该定义在这里。 + """ + # cursorPositionChanged = Signal() signal_save: SignalInstance = Signal() # Signal Focused in . But as it was too often triggered, I use click event instead. @@ -227,8 +231,8 @@ class PMBaseCodeEdit(QCodeEditor): def on_autocomp_signal_received(self, text_cursor_pos: tuple, completions: List['jedi.api.classes.Completion']): """当收到自动补全提示信号时,执行的函数。""" - current_cursor_pos = self._get_textcursor_pos() - if current_cursor_pos[0] + 1 == text_cursor_pos[0] and current_cursor_pos[1] == text_cursor_pos[1]: + position = self.cursor_position + if position[0] + 1 == text_cursor_pos[0] and position[1] == text_cursor_pos[1]: if len(completions) == 1: if completions[0].name == self._get_hint(): self.hide_autocomp() @@ -272,7 +276,7 @@ class PMBaseCodeEdit(QCodeEditor): return hint def _request_autocomp(self): - pos = self._get_textcursor_pos() + pos = self.cursor_position nearby_text = self._get_nearby_text() hint = self._get_hint() @@ -285,7 +289,8 @@ class PMBaseCodeEdit(QCodeEditor): def autocomp_show(self, completions: list): raise NotImplementedError - def _get_textcursor_pos(self) -> Tuple[int, int]: + @property + def cursor_position(self) -> Tuple[int, int]: return self.textCursor().blockNumber(), self.textCursor().columnNumber() def mousePressEvent(self, a0: QMouseEvent) -> None: diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py index bc3cd154..a3bb0f08 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py @@ -6,7 +6,7 @@ import logging import re import time -from typing import Tuple, List, TYPE_CHECKING +from typing import List, TYPE_CHECKING from PySide2.QtCore import QPoint, QModelIndex from PySide2.QtGui import QTextCursor, QMouseEvent, QKeyEvent @@ -69,7 +69,6 @@ class PMPythonCodeEdit(PMBaseCodeEdit): def on_text_changed(self): super(PMPythonCodeEdit, self).on_text_changed() - self._get_textcursor_pos() cursor_pos = self.cursorRect() self.popup_hint_widget.setGeometry( cursor_pos.x() + 5, cursor_pos.y() + 20, @@ -113,14 +112,14 @@ class PMPythonCodeEdit(PMBaseCodeEdit): return hint def _request_autocomp(self): - pos = self._get_textcursor_pos() + position = self.cursor_position nearby_text = self._get_nearby_text() hint = self._get_hint() if hint == '' and not nearby_text.endswith(('.', '\\\\', '/')): self.popup_hint_widget.hide_autocomp() return - self.autocomp_thread.text_cursor_pos = (pos[0] + 1, pos[1]) + self.autocomp_thread.text_cursor_pos = (position[0] + 1, position[1]) self.autocomp_thread.text = self.toPlainText() def autocomp_show(self, completions: List['Completion']): @@ -131,9 +130,6 @@ class PMPythonCodeEdit(PMBaseCodeEdit): self.popup_hint_widget.hide() self.popup_hint_widget.autocomp_list = l - def _get_textcursor_pos(self) -> Tuple[int, int]: - return self.textCursor().blockNumber(), self.textCursor().columnNumber() - def mousePressEvent(self, a0: QMouseEvent) -> None: # PluginInterface.show_tool_bar('code_editor_toolbar') if self.popup_hint_widget.isVisible(): -- Gitee From 3c210b5814ab075b18ea2652a1335b9fc587eb10 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 17:27:51 +0800 Subject: [PATCH 005/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qtpyeditor/codeedit/basecodeedit.py | 111 +++++++----------- .../qtpyeditor/codeedit/pythonedit.py | 11 +- 2 files changed, 51 insertions(+), 71 deletions(-) diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py index fe13a7e3..958527ef 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py @@ -3,6 +3,7 @@ # @Author: Zhanyi Hou # @Email: 1295752786@qq.com # @File: basecodeedit.py +import contextlib import logging import os import re @@ -12,7 +13,7 @@ from queue import Queue from typing import List, Tuple, Dict, TYPE_CHECKING, Callable from PySide2.QtCore import Qt, QModelIndex, Signal, QTimer, QUrl, SignalInstance -from PySide2.QtGui import QDropEvent, QPixmap +from PySide2.QtGui import QDropEvent, QPixmap, QDragMoveEvent, QDragLeaveEvent, QDragEnterEvent from PySide2.QtGui import QTextCursor, QKeyEvent, QMouseEvent, QIcon, QFocusEvent from PySide2.QtWidgets import QApplication, QWidget, QPlainTextEdit, QTableWidget, QTableWidgetItem, QHeaderView @@ -21,11 +22,9 @@ from packages.code_editor.codeeditor.qtpyeditor.highlighters.python import Pytho from packages.code_editor.codeeditor.qtpyeditor.linenumber import QCodeEditor from packages.code_editor.codeeditor.qtpyeditor.syntaxana import getIndent -# from pmgwidgets import create_icon - if TYPE_CHECKING: - from jedi.api import Completion from packages.code_editor.codeeditor.qtpyeditor import PMGPythonEditor + from jedi.api.classes import Completion as CompletionResult logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -60,7 +59,7 @@ class AutoCompList(QTableWidget): self.verticalHeader().setDefaultSectionSize(20) self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.horizontalHeader().hide() - self.setStyleSheet("AutoCompList{selection-background-color: #999999;}"); + self.setStyleSheet("AutoCompList{selection-background-color: #999999;}") self.verticalHeader().setMinimumWidth(20) # self.horizontalHeader().setMinimumWidth(300) # self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) @@ -120,7 +119,7 @@ class AutoCompList(QTableWidget): super().keyPressEvent(e) e.ignore() - def set_completions(self, completions: List['Completion']): + def set_completions(self, completions: List['CompletionResult']): """ module, class, instance, function, param, path, keyword, property and statement. :param completions: @@ -128,7 +127,6 @@ class AutoCompList(QTableWidget): """ t0 = time.time() self.setRowCount(0) - self.items_list = [] self.setRowCount(len(completions)) self.setColumnCount(1) labels = [] @@ -205,7 +203,7 @@ class PMBaseCodeEdit(QCodeEditor): self.setContextMenuPolicy(Qt.CustomContextMenu) self.ui_update_timer = QTimer() self.ui_update_timer.start(300) - + # noinspection PyUnresolvedReferences self.ui_update_timer.timeout.connect(self.update_ui) self.textChanged.connect(self.update_last_operation_time) @@ -229,7 +227,7 @@ class PMBaseCodeEdit(QCodeEditor): if focus_widget is not None: focus_widget.setFocus() - def on_autocomp_signal_received(self, text_cursor_pos: tuple, completions: List['jedi.api.classes.Completion']): + def on_autocomp_signal_received(self, text_cursor_pos: tuple, completions: 'List[CompletionResult]'): """当收到自动补全提示信号时,执行的函数。""" position = self.cursor_position if position[0] + 1 == text_cursor_pos[0] and position[1] == text_cursor_pos[1]: @@ -247,7 +245,7 @@ class PMBaseCodeEdit(QCodeEditor): def on_text_changed(self): """文字发生改变时的方法""" - if self.modified == True: + if self.modified: pass else: if self.toPlainText() != self._last_text: @@ -371,23 +369,25 @@ class PMBaseCodeEdit(QCodeEditor): cursor.deletePreviousChar() cursor.endEditBlock() - def on_return_pressed(self): - """ - 按回车换行的方法 - :return: - """ + @contextlib.contextmanager + def editing_block_cursor(self): cursor = self.textCursor() cursor.beginEditBlock() - text = cursor.block().text() - text, indent = getIndent(text) - - if text.endswith(':'): + yield cursor + cursor.endEditBlock() - cursor.insertText('\n' + ' ' * (indent + 4)) - else: + def on_return_pressed(self): + """按回车换行的方法 - cursor.insertText('\n' + ' ' * indent) - cursor.endEditBlock() + TODO 使用parso进行解析并更新 + """ + with self.editing_block_cursor() as cursor: + text = cursor.block().text() + text, indent = getIndent(text) + if text.endswith(':'): + cursor.insertText('\n' + ' ' * (indent + 4)) + else: + cursor.insertText('\n' + ' ' * indent) def comment(self): cursor = self.textCursor() @@ -403,7 +403,6 @@ class PMBaseCodeEdit(QCodeEditor): cursor.setPosition(start) cursor.movePosition(QTextCursor.StartOfLine) - start_line = cursor.blockNumber() start = cursor.position() # 将光标移动到行首,获取行首的位置 cursor.setPosition(end) # 将光标设置到末尾 @@ -446,13 +445,10 @@ class PMBaseCodeEdit(QCodeEditor): cursor = self.textCursor() if cursor.hasSelection(): self.editUnindent() - else: cursor = self.textCursor() cursor.clearSelection() - cursor.movePosition(QTextCursor.StartOfBlock) - for i in range(4): cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, 1) @@ -464,19 +460,18 @@ class PMBaseCodeEdit(QCodeEditor): cursor.removeSelectedText() def on_tab(self): - cursor = self.textCursor() - if cursor.hasSelection(): - self.editIndent() - return - else: - nearby_text = self._get_nearby_text() - hint = self._get_hint() - - if hint == '' and not nearby_text.endswith(('.', '\\\\', '/')): - cursor = self.textCursor() - cursor.insertText(" ") + with self.editing_block_cursor() as cursor: + if cursor.hasSelection(): + self.editIndent() else: - self._request_autocomp() + nearby_text = self._get_nearby_text() + hint = self._get_hint() + + if hint == '' and not nearby_text.endswith(('.', '\\\\', '/')): + cursor = self.textCursor() + cursor.insertText(" ") + else: + self._request_autocomp() def editIndent(self): cursor = self.textCursor() @@ -572,10 +567,7 @@ class PMBaseCodeEdit(QCodeEditor): return '' def getSelectedRows(self) -> Tuple[int, int]: - """ - 返回选中的行号范围 - :return: - """ + """返回选中的行号范围""" start = self.textCursor().selectionStart() end = self.textCursor().selectionEnd() start_block_id = self.document().findBlock(start).blockNumber() @@ -584,11 +576,7 @@ class PMBaseCodeEdit(QCodeEditor): return (start_block_id, end_block_id) def set_eol_status(self): - """ - 根据文件内容中的换行符设置底部状态 - - :return: - """ + """根据文件内容中的换行符设置底部状态""" eols = re.findall(r'\r\n|\r|\n', self.toPlainText()) if not eols: # self.label_status_eol.setText('Unix(LF)') @@ -632,10 +620,7 @@ class PMBaseCodeEdit(QCodeEditor): self.setTextCursor(cursor) def get_word(self, row=-1, col=0) -> str: - """ - 获取某个行列位置下的文本.若row=-1则获取光标之下的文本 - :return: - """ + """获取某个行列位置下的文本.若row=-1则获取光标之下的文本""" if row == -1: line_no = self.currentLine() text_cursor: QTextCursor = self.textCursor() @@ -665,10 +650,8 @@ class PMBaseCodeEdit(QCodeEditor): break word = text[col_forward:col_backward + 1].strip(seps_set) return word - except: - import traceback - traceback.print_exc() - return '' + except Exception as exception: + logger.exception(exception) def register_highlight(self, line: int, start: int, length: int, marker: int, hint: str): """ @@ -692,18 +675,14 @@ class PMBaseCodeEdit(QCodeEditor): def rehighlight(self): self.update_request_queue.put(self.UPDATE_CODE_HIGHLIGHT) - def dragEnterEvent(self, QDragEnterEvent): # 3 - print('Drag Enter') - if QDragEnterEvent.mimeData().hasText(): - QDragEnterEvent.acceptProposedAction() - print() + def dragEnterEvent(self, event: QDragEnterEvent): # 3 + if event.mimeData().hasText(): + event.acceptProposedAction() - def dragMoveEvent(self, QDragMoveEvent): # 4 - # print('Drag Move') + def dragMoveEvent(self, event: QDragMoveEvent): # 4 pass - def dragLeaveEvent(self, QDragLeaveEvent): # 5 - # print('Drag Leave') + def dragLeaveEvent(self, event: QDragLeaveEvent): # 5 pass def dropEvent(self, drop_event: QDropEvent): # 6 @@ -724,7 +703,7 @@ if __name__ == '__main__': e.show() - class A(): + class A: pass diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py index a3bb0f08..0c4e48cc 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py @@ -18,12 +18,15 @@ from packages.code_editor.codeeditor.qtpyeditor.highlighters import PythonHighli if TYPE_CHECKING: from jedi.api import Completion + from jedi.api.classes import Completion as CompletionResult logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) class PMPythonCodeEdit(PMBaseCodeEdit): + last_mouse_position: QPoint + def __init__(self, parent=None): super(PMPythonCodeEdit, self).__init__(parent) # self.setLineWrapMode(QPlainTextEdit.NoWrap) @@ -38,14 +41,13 @@ class PMPythonCodeEdit(PMBaseCodeEdit): self.autocomp_thread.start() self.setMouseTracking(True) - self.last_mouse_position: QPoint = None self.last_mouse_moved = time.time() self.hint_widget = QLabel('', parent=self) # 提示框标签。 self.hint_widget.setVisible(False) self.hint_widget.setStyleSheet("background-color:#d8d8d8") - def on_autocomp_signal_received(self, text_cursor_content: tuple, completions: List['jedi.api.Completion']): + def on_autocomp_signal_received(self, text_cursor_content: tuple, completions: List['CompletionResult']): """ 当收到自动补全提示信号时,执行的函数。 :param text_cursor_content:(row,col,hint_when_completion_triggered) @@ -107,8 +109,7 @@ class PMPythonCodeEdit(PMBaseCodeEdit): return '' col = self.textCursor().columnNumber() nearby_text = block_text[:col] - hint = re.split( - '[.:;,?!\s \+ \- = \* \\ \/ \( \)\[\]\{\} ]', nearby_text)[-1] + hint = re.split('[.:;,?!\s \+ \- = \* \\ \/ \( \)\[\]\{\} ]', nearby_text)[-1] return hint def _request_autocomp(self): @@ -122,7 +123,7 @@ class PMPythonCodeEdit(PMBaseCodeEdit): self.autocomp_thread.text_cursor_pos = (position[0] + 1, position[1]) self.autocomp_thread.text = self.toPlainText() - def autocomp_show(self, completions: List['Completion']): + def autocomp_show(self, completions: List['CompletionResult']): l = [] if len(completions) != 0: self.popup_hint_widget.set_completions(completions) -- Gitee From abc750efec33765980851e5633ba5b46fb210f16 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 17:39:32 +0800 Subject: [PATCH 006/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qtpyeditor/codeedit/basecodeedit.py | 10 +- .../qtpyeditor/codeedit/pythonedit.py | 93 ++----------------- pyminer_comm/base/network.py | 5 +- 3 files changed, 15 insertions(+), 93 deletions(-) diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py index 958527ef..3280faf0 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py @@ -274,14 +274,14 @@ class PMBaseCodeEdit(QCodeEditor): return hint def _request_autocomp(self): - pos = self.cursor_position + position = self.cursor_position nearby_text = self._get_nearby_text() hint = self._get_hint() if hint == '' and not nearby_text.endswith(('.', '\\\\', '/')): self.popup_hint_widget.hide_autocomp() return - self.autocomp_thread.text_cursor_pos = (pos[0] + 1, pos[1]) + self.autocomp_thread.text_cursor_pos = (position[0] + 1, position[1]) self.autocomp_thread.text = self.toPlainText() def autocomp_show(self, completions: list): @@ -450,11 +450,9 @@ class PMBaseCodeEdit(QCodeEditor): cursor.clearSelection() cursor.movePosition(QTextCursor.StartOfBlock) for i in range(4): - cursor.movePosition(QTextCursor.NextCharacter, - QTextCursor.KeepAnchor, 1) + cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, 1) if not cursor.selectedText().endswith(' '): - cursor.movePosition(QTextCursor.PreviousCharacter, - QTextCursor.KeepAnchor, 1) + cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, 1) break # print('cursor.selected',cursor.selectedText()) cursor.removeSelectedText() diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py index 0c4e48cc..507e0908 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py @@ -4,12 +4,11 @@ # @Email: 1295752786@qq.com # @File: pythonedit.py import logging -import re import time from typing import List, TYPE_CHECKING from PySide2.QtCore import QPoint, QModelIndex -from PySide2.QtGui import QTextCursor, QMouseEvent, QKeyEvent +from PySide2.QtGui import QTextCursor, QMouseEvent from PySide2.QtWidgets import QLabel, QApplication from packages.code_editor.codeeditor.qtpyeditor.Utilities import AutoCompThread @@ -17,7 +16,6 @@ from packages.code_editor.codeeditor.qtpyeditor.codeedit import PMBaseCodeEdit from packages.code_editor.codeeditor.qtpyeditor.highlighters import PythonHighlighter if TYPE_CHECKING: - from jedi.api import Completion from jedi.api.classes import Completion as CompletionResult logger = logging.getLogger(__name__) @@ -50,7 +48,7 @@ class PMPythonCodeEdit(PMBaseCodeEdit): def on_autocomp_signal_received(self, text_cursor_content: tuple, completions: List['CompletionResult']): """ 当收到自动补全提示信号时,执行的函数。 - :param text_cursor_content:(row,col,hint_when_completion_triggered) + :param text_cursor_content: (row,col,hint_when_completion_triggered) :param completions: :return: """ @@ -66,9 +64,6 @@ class PMPythonCodeEdit(PMBaseCodeEdit): else: self.hide_autocomp() - def hide_autocomp(self): - self.popup_hint_widget.hide_autocomp() - def on_text_changed(self): super(PMPythonCodeEdit, self).on_text_changed() cursor_pos = self.cursorRect() @@ -78,7 +73,7 @@ class PMPythonCodeEdit(PMBaseCodeEdit): self.popup_hint_widget.sizeHint().height()) self._request_autocomp() - def _insert_autocomp(self, e: QModelIndex = None): + def _insert_autocomp(self, model_index: QModelIndex = None): row = self.popup_hint_widget.currentRow() if 0 <= row < self.popup_hint_widget.count(): complete, word_type = self.popup_hint_widget.get_complete(row) @@ -98,90 +93,25 @@ class PMPythonCodeEdit(PMBaseCodeEdit): self.insertPlainText(' ') self.popup_hint_widget.hide() - def _get_nearby_text(self): - block_text = self.textCursor().block().text() - col = self.textCursor().columnNumber() - return block_text[:col] - - def _get_hint(self): - block_text = self.textCursor().block().text() - if block_text.lstrip().startswith('#'): # 在注释中 - return '' - col = self.textCursor().columnNumber() - nearby_text = block_text[:col] - hint = re.split('[.:;,?!\s \+ \- = \* \\ \/ \( \)\[\]\{\} ]', nearby_text)[-1] - return hint - - def _request_autocomp(self): - position = self.cursor_position - nearby_text = self._get_nearby_text() - hint = self._get_hint() - - if hint == '' and not nearby_text.endswith(('.', '\\\\', '/')): - self.popup_hint_widget.hide_autocomp() - return - self.autocomp_thread.text_cursor_pos = (position[0] + 1, position[1]) - self.autocomp_thread.text = self.toPlainText() - def autocomp_show(self, completions: List['CompletionResult']): - l = [] + result = [] if len(completions) != 0: self.popup_hint_widget.set_completions(completions) else: self.popup_hint_widget.hide() - self.popup_hint_widget.autocomp_list = l - - def mousePressEvent(self, a0: QMouseEvent) -> None: - # PluginInterface.show_tool_bar('code_editor_toolbar') - if self.popup_hint_widget.isVisible(): - self.popup_hint_widget.hide_autocomp() - super().mousePressEvent(a0) - - def keyPressEvent(self, event: QKeyEvent) -> None: - super().keyPressEvent(event) - - def on_back_tab(self): - cursor = self.textCursor() - if cursor.hasSelection(): - self.editUnindent() - else: - cursor = self.textCursor() - cursor.clearSelection() - cursor.movePosition(QTextCursor.StartOfBlock) - for i in range(4): - cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, 1) - if not cursor.selectedText().endswith(' '): - cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, 1) - break - cursor.removeSelectedText() - - def on_tab(self): - cursor = self.textCursor() - if cursor.hasSelection(): - self.editIndent() - return - else: - nearby_text = self._get_nearby_text() - hint = self._get_hint() - - if hint == '' and not nearby_text.endswith(('.', '\\\\', '/')): - cursor = self.textCursor() - cursor.insertText(" ") - else: - self._request_autocomp() + self.popup_hint_widget.autocomp_list = result def mouseMoveEvent(self, e: QMouseEvent): """ 鼠标移动事件 移动到marker上的时候,便弹出提示框。 - 编辑器的提示位置 - :param e: - :return: + 编辑器的提示位置。 """ super(PMPythonCodeEdit, self).mouseMoveEvent(e) cursor: QTextCursor = self.cursorForPosition(e.pos()) - if not self.should_check_code(): + # 如果代码量过大,则跳过 + if not len(self.toPlainText()) < 10000 * 120: return line, col = cursor.blockNumber(), cursor.positionInBlock() flag = False @@ -205,13 +135,6 @@ class PMPythonCodeEdit(PMBaseCodeEdit): self.hint_widget.setVisible(flag) e.ignore() - def should_check_code(self) -> bool: - """ - 返回是否会对代码做insight. - :return: - """ - return len(self.toPlainText()) < 10000 * 120 - if __name__ == '__main__': app = QApplication([]) diff --git a/pyminer_comm/base/network.py b/pyminer_comm/base/network.py index e92d1cf0..a5e5ddf6 100644 --- a/pyminer_comm/base/network.py +++ b/pyminer_comm/base/network.py @@ -3,11 +3,12 @@ # @Author: Zhanyi Hou # @Email: 1295752786@qq.com # @File: base.py -import socket import logging +import socket logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) +# 由于这个文件里的内容会在ipython中进行调用,因此使用WARNING级别以降低在ipython里面的输出 +logger.setLevel(logging.WARNING) def get(method: str, msg: str, port=52346, _timeout: int = None, **kwargs) -> bytes: -- Gitee From 4c4338f907afd1d1e87858f08cbc8e9deb9fb780 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 17:42:10 +0800 Subject: [PATCH 007/108] =?UTF-8?q?=E8=B0=83=E5=A4=A7code=5Feditor?= =?UTF-8?q?=E7=9A=84hint=E6=A1=86=E5=A4=A7=E5=B0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py index 507e0908..a789e514 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py @@ -43,7 +43,7 @@ class PMPythonCodeEdit(PMBaseCodeEdit): self.hint_widget = QLabel('', parent=self) # 提示框标签。 self.hint_widget.setVisible(False) - self.hint_widget.setStyleSheet("background-color:#d8d8d8") + self.hint_widget.setStyleSheet("background-color:#d8d8d8;padding:4px") def on_autocomp_signal_received(self, text_cursor_content: tuple, completions: List['CompletionResult']): """ -- Gitee From 6405f469cac67154d5e3182cbea5d060d3e79980 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 19:11:27 +0800 Subject: [PATCH 008/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codeeditor/qtpyeditor/codeedit/pythonedit.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py index a789e514..aaaa5ac7 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py @@ -82,8 +82,6 @@ class PMPythonCodeEdit(PMBaseCodeEdit): return comp = word[len(self._get_hint()):] self.insertPlainText(comp) - textcursor: QTextCursor = self.textCursor() - word = self.get_word(textcursor.blockNumber(), textcursor.columnNumber() - 1) if word_type == 'function': self.insertPlainText('()') tc = self.textCursor() @@ -101,14 +99,14 @@ class PMPythonCodeEdit(PMBaseCodeEdit): self.popup_hint_widget.hide() self.popup_hint_widget.autocomp_list = result - def mouseMoveEvent(self, e: QMouseEvent): + def mouseMoveEvent(self, event: QMouseEvent): """ 鼠标移动事件 移动到marker上的时候,便弹出提示框。 编辑器的提示位置。 """ - super(PMPythonCodeEdit, self).mouseMoveEvent(e) - cursor: QTextCursor = self.cursorForPosition(e.pos()) + super(PMPythonCodeEdit, self).mouseMoveEvent(event) + cursor: QTextCursor = self.cursorForPosition(event.pos()) # 如果代码量过大,则跳过 if not len(self.toPlainText()) < 10000 * 120: @@ -128,12 +126,12 @@ class PMPythonCodeEdit(PMBaseCodeEdit): flag = True text += marker_property[3] + '\n' break - self.hint_widget.setGeometry(e.x(), e.y() + 20, + self.hint_widget.setGeometry(event.x(), event.y() + 20, self.hint_widget.sizeHint().width(), self.hint_widget.sizeHint().height()) self.hint_widget.setText(text.strip()) self.hint_widget.setVisible(flag) - e.ignore() + event.ignore() if __name__ == '__main__': -- Gitee From 7bcd512c04af8f5a2be2b0d9730f789071724d7b Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 19:26:15 +0800 Subject: [PATCH 009/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qtpyeditor/codeedit/basecodeedit.py | 80 +++++++++---------- 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py index 3280faf0..47b8cba6 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py @@ -44,17 +44,19 @@ def create_icons(): return icons -class AutoCompList(QTableWidget): - # {'module':} # , class, instance, function, param, path, keyword, property and statement.'} +class AutoCompleteDropdownWidget(QTableWidget): ROLE_NAME = 15 ROLE_TYPE = 16 ROLE_COMPLETE = 17 ROLE_COMPLETION = 18 + parent: 'Callable[[], PMBaseCodeEdit]' + + # 为原生PySide2的类型添加类型提示 + verticalHeader: 'Callable[[], QHeaderView]' + def __init__(self, parent: 'PMBaseCodeEdit' = None): super().__init__(parent) - self._parent: 'PMBaseCodeEdit' = parent - self.last_show_time = 0 self.icons = create_icons() self.verticalHeader().setDefaultSectionSize(20) self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) @@ -64,60 +66,50 @@ class AutoCompList(QTableWidget): # self.horizontalHeader().setMinimumWidth(300) # self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) - def verticalHeader(self) -> QHeaderView: - return super(AutoCompList, self).verticalHeader() - - def show(self) -> None: - self.last_show_time = time.time() - super().show() - def hide_autocomp(self): - """ - 隐藏自动补全菜单并且主界面设置焦点。 - :return: - """ + """隐藏自动补全菜单并且主界面设置焦点。""" self.hide() - self._parent.setFocus() + self.parent().setFocus() def count(self): return self.rowCount() - def keyPressEvent(self, e: QKeyEvent) -> None: + def keyPressEvent(self, event: QKeyEvent) -> None: + parent = self.parent() if self.isVisible(): - if e.key() == Qt.Key_Return or e.key() == Qt.Key_Tab: - self._parent._insert_autocomp() - self._parent.setFocus() - e.accept() - print('return!') + if event.key() == Qt.Key_Return or event.key() == Qt.Key_Tab: + parent._insert_autocomp() + parent.setFocus() + event.accept() return - elif e.key() == Qt.Key_Escape: + elif event.key() == Qt.Key_Escape: self.hide() - self._parent.setFocus() + parent.setFocus() return - elif e.key() == Qt.Key_Up or e.key() == Qt.Key_Down: - super().keyPressEvent(e) - e.accept() + elif event.key() == Qt.Key_Up or event.key() == Qt.Key_Down: + super().keyPressEvent(event) + event.accept() return - elif e.key() == Qt.Key_Left or e.key() == Qt.Key_Right: + elif event.key() == Qt.Key_Left or event.key() == Qt.Key_Right: self.hide_autocomp() - elif e.key() == Qt.Key_Control or e.key() == Qt.Key_Alt: # 按下Ctrl键时,不关闭界面,因为可能存在快捷键。 + elif event.key() == Qt.Key_Control or event.key() == Qt.Key_Alt: # 按下Ctrl键时,不关闭界面,因为可能存在快捷键。 pass else: - if (Qt.Key_0 <= e.key() <= Qt.Key_9) and ( - e.modifiers() == Qt.ControlModifier or e.modifiers() == Qt.AltModifier): - index = e.key() - Qt.Key_0 + if (Qt.Key_0 <= event.key() <= Qt.Key_9) and ( + event.modifiers() == Qt.ControlModifier or event.modifiers() == Qt.AltModifier): + index = event.key() - Qt.Key_0 if 0 <= index < self.count(): self.setCurrentItem(self.item(index, 0)) - self._parent._insert_autocomp() - self._parent.setFocus() + parent._insert_autocomp() + parent.setFocus() self.hide() - e.accept() + event.accept() return self.hide_autocomp() - e.ignore() + event.ignore() return - super().keyPressEvent(e) - e.ignore() + super().keyPressEvent(event) + event.ignore() def set_completions(self, completions: List['CompletionResult']): """ @@ -132,9 +124,9 @@ class AutoCompList(QTableWidget): labels = [] for i, completion in enumerate(completions): item = QTableWidgetItem(completion.name) - item.setData(AutoCompList.ROLE_NAME, completion.name) + item.setData(AutoCompleteDropdownWidget.ROLE_NAME, completion.name) - item.setData(AutoCompList.ROLE_COMPLETION, completion) + item.setData(AutoCompleteDropdownWidget.ROLE_COMPLETION, completion) item.setText(completion.name) if i < 30: # 当条目数太多的时候,不能添加图标,否则速度会非常慢 icon = self.icons.get(completion.type) @@ -154,8 +146,8 @@ class AutoCompList(QTableWidget): logger.info('completion time:{0},completion list length:{1}'.format(t1 - t0, len(completions))) def get_complete(self, row: int) -> Tuple[str, str]: - return self.item(row, 0).data(AutoCompList.ROLE_COMPLETION).complete, self.item(row, 0).data( - AutoCompList.ROLE_COMPLETION).type + return self.item(row, 0).data(AutoCompleteDropdownWidget.ROLE_COMPLETION).complete, self.item(row, 0).data( + AutoCompleteDropdownWidget.ROLE_COMPLETION).type def get_text(self, row: int) -> str: return self.item(row, 0).text() @@ -197,7 +189,7 @@ class PMBaseCodeEdit(QCodeEditor): self.textChanged.connect(self.on_text_changed) - self.popup_hint_widget = AutoCompList(self) + self.popup_hint_widget = AutoCompleteDropdownWidget(self) self.popup_hint_widget.doubleClicked.connect(self._insert_autocomp) self.popup_hint_widget.hide() self.setContextMenuPolicy(Qt.CustomContextMenu) @@ -697,7 +689,7 @@ class PMBaseCodeEdit(QCodeEditor): if __name__ == '__main__': app = QApplication([]) - e = AutoCompList() + e = AutoCompleteDropdownWidget() e.show() -- Gitee From 359a29889a013ea2afd9deb28031961611782b2b Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 20:06:10 +0800 Subject: [PATCH 010/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/codeeditor/abstracteditor.py | 2 +- .../code_editor/codeeditor/pythoneditor.py | 6 +- .../qtpyeditor/codeedit/basecodeedit.py | 94 ++++++-------- .../qtpyeditor/codeeditor/abstracteditor.py | 9 +- .../qtpyeditor/codeeditor/baseeditor.py | 18 +-- .../codeeditor/qtpyeditor/find_gotoline.py | 115 ------------------ packages/code_editor/codeeditor/tabwidget.py | 4 +- 7 files changed, 62 insertions(+), 186 deletions(-) delete mode 100644 packages/code_editor/codeeditor/qtpyeditor/find_gotoline.py diff --git a/packages/code_editor/codeeditor/abstracteditor.py b/packages/code_editor/codeeditor/abstracteditor.py index 5b70f679..920e8170 100644 --- a/packages/code_editor/codeeditor/abstracteditor.py +++ b/packages/code_editor/codeeditor/abstracteditor.py @@ -197,7 +197,7 @@ class PMAbstractEditor(QWidget): :rtype: bool :return: 返回内容是否被修改 """ - return self.textEdit.isModified() + return self.textEdit.is_modified() def filename(self) -> str: """ diff --git a/packages/code_editor/codeeditor/pythoneditor.py b/packages/code_editor/codeeditor/pythoneditor.py index 14efad54..7944e984 100644 --- a/packages/code_editor/codeeditor/pythoneditor.py +++ b/packages/code_editor/codeeditor/pythoneditor.py @@ -258,7 +258,7 @@ class PMPythonEditor(PMGPythonEditor): self.help_runner = PMGOneShotThreadRunner(callback=show_help) def get_hint(self): - pos = self.text_edit.getCursorPosition() + pos = self.text_edit.get_cursor_position() text = self.text(pos[0]) try: line = text[:pos[1] + 1] @@ -467,8 +467,8 @@ class PMPythonEditor(PMGPythonEditor): break line_text_list = self.text().split('\n') line_text_list[line] = line_text - first_visible_line = self.text_edit.firstVisibleLine() - cursor_pos = self.text_edit.getCursorPosition() + first_visible_line = self.text_edit.first_visible_line_number + cursor_pos = self.text_edit.get_cursor_position() text = '\n'.join(line_text_list) self.set_text(text) self.set_marker_for_run() diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py index 47b8cba6..220c0591 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py @@ -13,7 +13,7 @@ from queue import Queue from typing import List, Tuple, Dict, TYPE_CHECKING, Callable from PySide2.QtCore import Qt, QModelIndex, Signal, QTimer, QUrl, SignalInstance -from PySide2.QtGui import QDropEvent, QPixmap, QDragMoveEvent, QDragLeaveEvent, QDragEnterEvent +from PySide2.QtGui import QDropEvent, QPixmap, QDragEnterEvent from PySide2.QtGui import QTextCursor, QKeyEvent, QMouseEvent, QIcon, QFocusEvent from PySide2.QtWidgets import QApplication, QWidget, QPlainTextEdit, QTableWidget, QTableWidgetItem, QHeaderView @@ -37,8 +37,7 @@ def create_icons(): icon_abso_path = os.path.join(icon_folder, icon_file_name) icon1 = QIcon() # create_icon(icon_abso_path) icon1.addPixmap(QPixmap(icon_abso_path), QIcon.Normal, QIcon.Off) - # print(icon1) - logging.debug('loading {0}'.format(icon_file_name)) + logging.debug(f'loading {icon_file_name}') icons[icon_file_name[:-4]] = icon1 return icons @@ -50,9 +49,8 @@ class AutoCompleteDropdownWidget(QTableWidget): ROLE_COMPLETE = 17 ROLE_COMPLETION = 18 - parent: 'Callable[[], PMBaseCodeEdit]' - # 为原生PySide2的类型添加类型提示 + parent: 'Callable[[], PMBaseCodeEdit]' verticalHeader: 'Callable[[], QHeaderView]' def __init__(self, parent: 'PMBaseCodeEdit' = None): @@ -112,11 +110,7 @@ class AutoCompleteDropdownWidget(QTableWidget): event.ignore() def set_completions(self, completions: List['CompletionResult']): - """ - module, class, instance, function, param, path, keyword, property and statement. - :param completions: - :return: - """ + """module, class, instance, function, param, path, keyword, property and statement.""" t0 = time.time() self.setRowCount(0) self.setRowCount(len(completions)) @@ -143,7 +137,7 @@ class AutoCompleteDropdownWidget(QTableWidget): self.setFocus() self.setCurrentItem(self.item(0, 0)) t1 = time.time() - logger.info('completion time:{0},completion list length:{1}'.format(t1 - t0, len(completions))) + logger.info(f'completion time:{t1 - t0},completion list length:{len(completions)}') def get_complete(self, row: int) -> Tuple[str, str]: return self.item(row, 0).data(AutoCompleteDropdownWidget.ROLE_COMPLETION).complete, self.item(row, 0).data( @@ -248,7 +242,7 @@ class PMBaseCodeEdit(QCodeEditor): pass self._last_text = self.toPlainText() - def _insert_autocomp(self, e: QModelIndex = None): + def _insert_autocomp(self, event: QModelIndex = None): raise NotImplementedError def _get_nearby_text(self): @@ -283,11 +277,11 @@ class PMBaseCodeEdit(QCodeEditor): def cursor_position(self) -> Tuple[int, int]: return self.textCursor().blockNumber(), self.textCursor().columnNumber() - def mousePressEvent(self, a0: QMouseEvent) -> None: + def mousePressEvent(self, event: QMouseEvent) -> None: if self.popup_hint_widget.isVisible(): self.popup_hint_widget.hide_autocomp() self.signal_focused_in.emit(None) - super().mousePressEvent(a0) + super().mousePressEvent(event) def keyPressEvent(self, event: QKeyEvent) -> None: k = event.key() @@ -343,7 +337,7 @@ class PMBaseCodeEdit(QCodeEditor): return super().keyPressEvent(event) - def on_backspace(self, key_backspace_event: QKeyEvent): + def on_backspace(self, _: QKeyEvent): cursor: QTextCursor = self.textCursor() cursor.beginEditBlock() previous_text = cursor.block().text()[:cursor.positionInBlock()] @@ -436,7 +430,7 @@ class PMBaseCodeEdit(QCodeEditor): def on_back_tab(self): cursor = self.textCursor() if cursor.hasSelection(): - self.editUnindent() + self.edit_unindent() else: cursor = self.textCursor() cursor.clearSelection() @@ -452,7 +446,7 @@ class PMBaseCodeEdit(QCodeEditor): def on_tab(self): with self.editing_block_cursor() as cursor: if cursor.hasSelection(): - self.editIndent() + self.edit_indent() else: nearby_text = self._get_nearby_text() hint = self._get_hint() @@ -463,12 +457,11 @@ class PMBaseCodeEdit(QCodeEditor): else: self._request_autocomp() - def editIndent(self): + def edit_indent(self): cursor = self.textCursor() cursor.beginEditBlock() if cursor.hasSelection(): start = pos = cursor.anchor() - start_line = self.document().findBlock(start) end = cursor.position() if start > end: @@ -494,12 +487,11 @@ class PMBaseCodeEdit(QCodeEditor): if lastPos == pos: break cursor.setPosition(start) - cursor.movePosition(QTextCursor.NextCharacter, - QTextCursor.KeepAnchor, end - start) + cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, end - start) cursor.endEditBlock() return True - def editUnindent(self): + def edit_unindent(self): cursor = self.textCursor() cursor.beginEditBlock() if cursor.hasSelection(): @@ -516,8 +508,7 @@ class PMBaseCodeEdit(QCodeEditor): cursor.movePosition(QTextCursor.StartOfLine) end = cursor.position() while pos >= start: - cursor.movePosition(QTextCursor.NextCharacter, - QTextCursor.KeepAnchor, 4) + cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, 4) if cursor.selectedText() == " ": cursor.removeSelectedText() cursor.movePosition(QTextCursor.Up) @@ -527,43 +518,47 @@ class PMBaseCodeEdit(QCodeEditor): if pos == lastpos: break cursor.setPosition(start) - cursor.movePosition(QTextCursor.NextCharacter, - QTextCursor.KeepAnchor, end - start) + cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, end - start) cursor.endEditBlock() def save(self): self.signal_save.emit() - def isModified(self): + def is_modified(self): + return self.modified + + @property + def is_modified_prop(self): return self.modified - def firstVisibleLine(self) -> int: + @property + def first_visible_line_number(self) -> int: return self.firstVisibleBlock().blockNumber() - def currentLine(self) -> int: + @property + def current_line_number(self) -> int: return self.textCursor().blockNumber() - def goToLine(self, line: int): + def go_to_line(self, line: int): tc = self.textCursor() pos = self.document().findBlockByNumber(line - 1).position() tc.setPosition(pos, QTextCursor.MoveAnchor) # self.setTextCursor(tc) - def getSelectedText(self) -> str: + def get_selected_text(self) -> str: if self.textCursor().hasSelection(): return self.textCursor().selectedText() else: return '' - def getSelectedRows(self) -> Tuple[int, int]: + def get_selected_row_numbers(self) -> Tuple[int, int]: """返回选中的行号范围""" start = self.textCursor().selectionStart() end = self.textCursor().selectionEnd() start_block_id = self.document().findBlock(start).blockNumber() end_block_id = self.document().findBlock(end).blockNumber() - - return (start_block_id, end_block_id) + return start_block_id, end_block_id def set_eol_status(self): """根据文件内容中的换行符设置底部状态""" @@ -586,23 +581,22 @@ class PMBaseCodeEdit(QCodeEditor): # self.label_status_eol.setText('Unix(LF)') # self.textEdit.setEolMode(QsciScintilla.EolUnix) # \n换行 - def load_color_scheme(self, scheme: Dict[str, str]): + @staticmethod + def load_color_scheme(scheme: Dict[str, str]): PythonHighlighter.font_cfg.load_color_scheme(scheme) - def getCursorPosition(self) -> int: + def get_cursor_position(self) -> int: # QTextCursor.position() return self.textCursor().position() - def setSelection(self): + def set_selection(self): raise NotImplementedError - text_cursor: QTextCursor = self.textCursor() - text_cursor.clearSelection() - # text_cursor.setPosition() - def hasSelectedText(self): + @property + def is_text_selected(self): return self.textCursor().hasSelection() - def replace(self, replacement: str): + def replace_selection(self, replacement: str): cursor: QTextCursor = self.textCursor() cursor.removeSelectedText() cursor.insertText(replacement) @@ -612,7 +606,7 @@ class PMBaseCodeEdit(QCodeEditor): def get_word(self, row=-1, col=0) -> str: """获取某个行列位置下的文本.若row=-1则获取光标之下的文本""" if row == -1: - line_no = self.currentLine() + line_no = self.current_line_number text_cursor: QTextCursor = self.textCursor() col = text_cursor.positionInBlock() else: @@ -669,22 +663,14 @@ class PMBaseCodeEdit(QCodeEditor): if event.mimeData().hasText(): event.acceptProposedAction() - def dragMoveEvent(self, event: QDragMoveEvent): # 4 - pass - - def dragLeaveEvent(self, event: QDragLeaveEvent): # 5 - pass - def dropEvent(self, drop_event: QDropEvent): # 6 - url: QUrl - urls = drop_event.mimeData().urls() + urls: List[QUrl] = drop_event.mimeData().urls() for url in urls: try: file = url.toLocalFile() self.signal_file_dropped.emit(file) - except: - import traceback - traceback.print_exc() + except Exception as exception: + logger.exception(exception) if __name__ == '__main__': diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py index 42abf9eb..04a123de 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py @@ -35,14 +35,19 @@ __version__ = '0.1' import logging import os -from typing import Dict, Any +from typing import Dict, Any, TYPE_CHECKING from PySide2.QtWidgets import QWidget, QMessageBox +if TYPE_CHECKING: + from packages.code_editor.codeeditor.qtpyeditor.codeedit import PMBaseCodeEdit + logger = logging.getLogger(__name__) class PMAbstractEditor(QWidget): + textEdit: 'PMBaseCodeEdit' + def __init__(self, parent): super().__init__(parent) self.last_save_time = 0 @@ -199,7 +204,7 @@ class PMAbstractEditor(QWidget): :rtype: bool :return: 返回内容是否被修改 """ - return self.textEdit.isModified() + return self.textEdit.is_modified() def filename(self) -> str: """ diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py index 15581d40..f1a73ef4 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py @@ -150,8 +150,8 @@ class FindDialog(QDialog): def replace_current(self): text: str = self.settings_panel.widgets_dic['text_to_replace'].get_value() - if self.text_edit.hasSelectedText(): - self.text_edit.replace(text) + if self.text_edit.is_text_selected: + self.text_edit.replace_selection(text) self.search_up() def replace_all(self): @@ -160,15 +160,15 @@ class FindDialog(QDialog): while (1): b = self.text_editor.search_word(forward=True, **settings) if b: - self.text_edit.replace(text_to_replace) + self.text_edit.replace_selection(text_to_replace) else: break def show(self) -> None: super().show() - if self.text_edit.getSelectedText() != '': - self.settings_panel.set_value({'text_to_find': self.text_edit.getSelectedText()}) + if self.text_edit.get_selected_text() != '': + self.settings_panel.set_value({'text_to_find': self.text_edit.get_selected_text()}) def show_replace_actions(self, replace_on: bool = False): self.settings_panel.get_ctrl('text_to_replace').setVisible(replace_on) @@ -285,7 +285,7 @@ class PMGBaseEditor(PMAbstractEditor): find_flags = QTextDocument.FindFlags # print(find_flags) ret = self.text_edit.find(text_to_find, options=find_flags) - cursor_pos = self.text_edit.getCursorPosition() + cursor_pos = self.text_edit.get_cursor_position() print(ret, wrap) if wrap and (not ret): cursor = self.text_edit.textCursor() @@ -440,7 +440,7 @@ class PMGBaseEditor(PMAbstractEditor): :return: """ QCoreApplication.translate = QCoreApplication.translate - path = self._path.replace(os.sep, '/') + path = self._path.replace_selection(os.sep, '/') default_dir = self.default_save_path() if path.startswith(QDir.tempPath().replace(os.sep, '/')): assert os.path.exists(default_dir) or default_dir == '' @@ -521,7 +521,7 @@ class PMGBaseEditor(PMAbstractEditor): str, 选中的或全部的代码 """ if selected: - return self.text_edit.getSelectedText() + return self.text_edit.get_selected_text() else: return self.text_edit.toPlainText() @@ -694,7 +694,7 @@ class PMGBaseEditor(PMAbstractEditor): del menu def slot_find_in_path(self): - sel = self.text_edit.getSelectedText() + sel = self.text_edit.get_selected_text() self.signal_request_find_in_path.emit(sel) def slot_find(self): diff --git a/packages/code_editor/codeeditor/qtpyeditor/find_gotoline.py b/packages/code_editor/codeeditor/qtpyeditor/find_gotoline.py deleted file mode 100644 index 617dd324..00000000 --- a/packages/code_editor/codeeditor/qtpyeditor/find_gotoline.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/1/20 8:23 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: find_gotoline.py -from PySide2.QtWidgets import QDialog, QVBoxLayout, QPushButton, QHBoxLayout -from pmgwidgets import PMGPanel - - -class FindDialog(QDialog): - def __init__(self, parent=None, text_edit: 'PMCodeEditor' = None): - super(FindDialog, self).__init__(parent) - self.text_editor = text_edit - self.qsci_text_edit: 'QsciScintilla' = text_edit.textEdit - views = [('line_ctrl', 'text_to_find', self.tr('Text to Find'), ''), - ('line_ctrl', 'text_to_replace', self.tr('Text to Replace'), ''), - ('check_ctrl', 'wrap', self.tr('Wrap'), True), - ('check_ctrl', 'regex', self.tr('Regex'), False), - ('check_ctrl', 'case_sensitive', self.tr('Case Sensitive'), True), - ('check_ctrl', 'whole_word', self.tr('Whole Word'), True), - ] - self.settings_panel = PMGPanel(parent=self, views=views) - self.setLayout(QVBoxLayout()) - self.layout().addWidget(self.settings_panel) - self.button_up = QPushButton(self.tr('Up')) - self.button_down = QPushButton(self.tr('Down')) - self.button_replace = QPushButton(self.tr('Replace')) - self.button_replace_all = QPushButton(self.tr('Replace All')) - - self.button_up.clicked.connect(self.search_up) - self.button_down.clicked.connect(self.search_down) - self.button_replace.clicked.connect(self.replace_current) - self.button_replace_all.clicked.connect(self.replace_all) - - self.button_bar = QHBoxLayout() - self.button_bar.addWidget(self.button_up) - self.button_bar.addWidget(self.button_down) - self.button_bar.addWidget(self.button_replace) - self.button_bar.addWidget(self.button_replace_all) - self.button_bar.setContentsMargins(0, 0, 0, 0) - self.layout().addLayout(self.button_bar) - - def search_up(self): - settings = self.settings_panel.get_value() - self.text_editor.search_word(forward=True, **settings) - pass - - def search_down(self): - """ - 反方向查找。注意,简单的设置qsci的forward=False是不够的,还需要对位置进行处理。 - 这似乎是QSciScintilla的bug. - """ - settings = self.settings_panel.get_value() - line, index = self.text_editor.textEdit.getSelection()[:2] - self.text_editor.search_word(forward=False, **settings, line=line, index=index) - - pass - - def replace_current(self): - text: str = self.settings_panel.widgets_dic['text_to_replace'].get_value() - if self.qsci_text_edit.hasSelectedText(): - self.qsci_text_edit.replace(text) - - def replace_all(self): - settings = self.settings_panel.get_value() - text_to_replace = self.settings_panel.widgets_dic['text_to_replace'].get_value() - while (1): - b = self.text_editor.search_word(forward=True, **settings) - if b: - self.qsci_text_edit.replace(text_to_replace) - else: - break - - def show(self) -> None: - super().show() - if self.qsci_text_edit.hasSelectedText(): - self.settings_panel.set_value({'text_to_find': self.qsci_text_edit.selectedText()}) - - def closeEvent(self, a0: 'QCloseEvent') -> None: - sel = self.qsci_text_edit.getCursorPosition() - self.qsci_text_edit.setSelection(sel[0], sel[1], sel[0], sel[1]) - - def close(self) -> bool: - return False - -# class GotoLineDialog(QDialog, Ui_DialogGoto): -# """跳转指定行""" -# -# def __init__(self, editor: 'PMCodeEditor', *args, **kwargs): -# super(GotoLineDialog, self).__init__(*args, **kwargs) -# self.setupUi(self) -# self.editor = editor -# self.buttonBox.accepted.connect(self.slot_accepted) -# line, column = editor.getCursorPosition() -# self.lineEdit.setText('%s:%s' % (line + 1, column + 1)) -# self.lineEdit.setFocus() -# self.lineEdit.selectAll() -# -# def slot_accepted(self): -# """ -# 跳转到对应行列 -# :return: -# """ -# text = re.findall(r'^\d+$|^\d+:\d+$', self.lineEdit.text().strip()) -# if not text: -# return -# text = text[0] -# if text.find(':') == -1: -# text += ':0' -# try: -# line, column = text.split(':') -# self.editor.setCursorPosition(max(0, int(line) - 1), max(0, int(column) - 1)) -# self.accept() -# except Exception as e: -# logger.warning(str(e)) diff --git a/packages/code_editor/codeeditor/tabwidget.py b/packages/code_editor/codeeditor/tabwidget.py index d909d4a4..23ccb460 100644 --- a/packages/code_editor/codeeditor/tabwidget.py +++ b/packages/code_editor/codeeditor/tabwidget.py @@ -476,7 +476,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): last_pos_result: Tuple[str, int, int] = self.cursor_pos_manager.undo() self._last_cursorpos_requested_time = time.time() if last_pos_result is not None: - if last_pos_result[1] == self.currentWidget().textEdit.getCursorPosition()[0]: + if last_pos_result[1] == self.currentWidget().textEdit.get_cursor_position()[0]: last_pos_result = self.cursor_pos_manager.undo() if last_pos_result is None: return @@ -491,7 +491,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): next_pos_result: Tuple[str, int, int] = self.cursor_pos_manager.redo() self._last_cursorpos_requested_time = time.time() if next_pos_result is not None: - if next_pos_result[1] == self.currentWidget().textEdit.getCursorPosition()[0]: + if next_pos_result[1] == self.currentWidget().textEdit.get_cursor_position()[0]: next_pos_result = self.cursor_pos_manager.redo() if next_pos_result is None: return -- Gitee From 145c5c67c33c5e9d071bd403e3ace757658ffb07 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 20:08:38 +0800 Subject: [PATCH 011/108] =?UTF-8?q?=E5=88=A0=E9=99=A4QScintilla=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=A4=B9=EF=BC=8C=E7=9B=AE=E5=89=8D=E4=BC=BC=E4=B9=8E?= =?UTF-8?q?=E5=B7=B2=E5=AF=B9=E5=85=B6=E8=BF=9B=E8=A1=8C=E6=9B=BF=E4=BB=A3?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tools/DocumentationTools/APIGenerator.py | 184 -- .../tools/DocumentationTools/__init__.py | 13 - .../codeeditor/tools/QScintilla/Editor.py | 28 - .../codeeditor/tools/QScintilla/__init__.py | 17 - .../tools/Utilities/ModuleParser.py | 1699 ----------------- .../codeeditor/tools/Utilities/__init__.py | 164 -- .../code_editor/codeeditor/tools/__init__.py | 3 - .../code_editor/codeeditor/tools/eric6_api.py | 324 ---- 8 files changed, 2432 deletions(-) delete mode 100644 packages/code_editor/codeeditor/tools/DocumentationTools/APIGenerator.py delete mode 100644 packages/code_editor/codeeditor/tools/DocumentationTools/__init__.py delete mode 100644 packages/code_editor/codeeditor/tools/QScintilla/Editor.py delete mode 100644 packages/code_editor/codeeditor/tools/QScintilla/__init__.py delete mode 100644 packages/code_editor/codeeditor/tools/Utilities/ModuleParser.py delete mode 100644 packages/code_editor/codeeditor/tools/Utilities/__init__.py delete mode 100644 packages/code_editor/codeeditor/tools/__init__.py delete mode 100644 packages/code_editor/codeeditor/tools/eric6_api.py diff --git a/packages/code_editor/codeeditor/tools/DocumentationTools/APIGenerator.py b/packages/code_editor/codeeditor/tools/DocumentationTools/APIGenerator.py deleted file mode 100644 index d5df81be..00000000 --- a/packages/code_editor/codeeditor/tools/DocumentationTools/APIGenerator.py +++ /dev/null @@ -1,184 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2004 - 2020 Detlev Offenbach -# - -""" -Module implementing the builtin API generator. -""" - - -class APIGenerator(object): - """ - Class implementing the builtin documentation generator. - """ - - def __init__(self, module): - """ - Constructor - - @param module The information of the parsed Python file. - """ - self.module = module - - def genAPI(self, newStyle, basePackage, includePrivate): - """ - Public method to generate the API information. - - @param newStyle flag indicating the api generation for QScintilla 1.7 - and newer (boolean) (ignored) - @param basePackage name of the base package (string) - @param includePrivate flag indicating to include - private methods/functions (boolean) - @return API information (list of strings) - """ - self.includePrivate = includePrivate - modulePath = self.module.name.split('.') - if modulePath[-1] == '__init__': - del modulePath[-1] - if basePackage: - modulePath[0] = basePackage - self.moduleName = "{0}.".format('.'.join(modulePath)) - self.api = [] - self.__addGlobalsAPI() - self.__addClassesAPI() - self.__addFunctionsAPI() - return self.api - - def genBases(self, includePrivate): - """ - Public method to generate the base classes information. - - @param includePrivate flag indicating to include private classes - (boolean) - @return base classes information (dictionary of list of strings) - """ - bases = {} - self.includePrivate = includePrivate - classNames = sorted(list(self.module.classes.keys())) - for className in classNames: - if not self.__isPrivate(self.module.classes[className]): - if className not in bases: - bases[className] = [ - b for b in self.module.classes[className].super - if b != "object"] - return bases - - def __isPrivate(self, obj): - """ - Private method to check, if an object is considered private. - - @param obj reference to the object to be checked - @return flag indicating, that object is considered private (boolean) - """ - private = obj.isPrivate() and not self.includePrivate - return private - - def __addGlobalsAPI(self): - """ - Private method to generate the api section for global variables. - """ - from QScintilla.Editor import Editor - - moduleNameStr = "{0}".format(self.moduleName) - - for globalName in sorted(self.module.globals.keys()): - if not self.__isPrivate(self.module.globals[globalName]): - if self.module.globals[globalName].isPublic(): - iconId = Editor.AttributeID - elif self.module.globals[globalName].isProtected(): - iconId = Editor.AttributeProtectedID - else: - iconId = Editor.AttributePrivateID - self.api.append("{0}{1}?{2:d}".format( - moduleNameStr, globalName, iconId)) - - def __addClassesAPI(self): - """ - Private method to generate the api section for classes. - """ - classNames = sorted(list(self.module.classes.keys())) - for className in classNames: - if not self.__isPrivate(self.module.classes[className]): - self.__addClassVariablesAPI(className) - self.__addMethodsAPI(className) - - def __addMethodsAPI(self, className): - """ - Private method to generate the api section for class methods. - - @param className name of the class containing the method (string) - """ - from QScintilla.Editor import Editor - - _class = self.module.classes[className] - methods = sorted(list(_class.methods.keys())) - if '__init__' in methods: - methods.remove('__init__') - if _class.isPublic(): - iconId = Editor.ClassID - elif _class.isProtected(): - iconId = Editor.ClassProtectedID - else: - iconId = Editor.ClassPrivateID - self.api.append( - '{0}{1}?{2:d}({3})'.format( - self.moduleName, _class.name, iconId, - ', '.join(_class.methods['__init__'].parameters[1:]))) - - classNameStr = "{0}{1}.".format(self.moduleName, className) - for method in methods: - if not self.__isPrivate(_class.methods[method]): - if _class.methods[method].isPublic(): - iconId = Editor.MethodID - elif _class.methods[method].isProtected(): - iconId = Editor.MethodProtectedID - else: - iconId = Editor.MethodPrivateID - self.api.append( - '{0}{1}?{2:d}({3})'.format( - classNameStr, method, iconId, - ', '.join(_class.methods[method].parameters[1:]))) - - def __addClassVariablesAPI(self, className): - """ - Private method to generate class api section for class variables. - - @param className name of the class containing the class variables - (string) - """ - from QScintilla.Editor import Editor - - _class = self.module.classes[className] - classNameStr = "{0}{1}.".format(self.moduleName, className) - for variable in sorted(_class.globals.keys()): - if not self.__isPrivate(_class.globals[variable]): - if _class.globals[variable].isPublic(): - iconId = Editor.AttributeID - elif _class.globals[variable].isProtected(): - iconId = Editor.AttributeProtectedID - else: - iconId = Editor.AttributePrivateID - self.api.append('{0}{1}?{2:d}'.format( - classNameStr, variable, iconId)) - - def __addFunctionsAPI(self): - """ - Private method to generate the api section for functions. - """ - from QScintilla.Editor import Editor - - funcNames = sorted(list(self.module.functions.keys())) - for funcName in funcNames: - if not self.__isPrivate(self.module.functions[funcName]): - if self.module.functions[funcName].isPublic(): - iconId = Editor.MethodID - elif self.module.functions[funcName].isProtected(): - iconId = Editor.MethodProtectedID - else: - iconId = Editor.MethodPrivateID - self.api.append( - '{0}{1}?{2:d}({3})'.format( - self.moduleName, self.module.functions[funcName].name, - iconId, - ', '.join(self.module.functions[funcName].parameters))) diff --git a/packages/code_editor/codeeditor/tools/DocumentationTools/__init__.py b/packages/code_editor/codeeditor/tools/DocumentationTools/__init__.py deleted file mode 100644 index 96549b06..00000000 --- a/packages/code_editor/codeeditor/tools/DocumentationTools/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2003 - 2020 Detlev Offenbach -# - -""" -Package implementing the source code documentation tools. -""" - -supportedExtensionsDictForApis = { - "Python3": [".py", ".pyw", ".py3", ".pyw3"], - "Ruby": [".rb"] -} diff --git a/packages/code_editor/codeeditor/tools/QScintilla/Editor.py b/packages/code_editor/codeeditor/tools/QScintilla/Editor.py deleted file mode 100644 index a2a8f6e7..00000000 --- a/packages/code_editor/codeeditor/tools/QScintilla/Editor.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2002 - 2020 Detlev Offenbach -# - -""" -Module implementing the editor component of the eric6 IDE. -""" - - -class Editor: - # Autocompletion icon definitions - ClassID = 1 - ClassProtectedID = 2 - ClassPrivateID = 3 - MethodID = 4 - MethodProtectedID = 5 - MethodPrivateID = 6 - AttributeID = 7 - AttributeProtectedID = 8 - AttributePrivateID = 9 - EnumID = 10 - KeywordsID = 11 - ModuleID = 12 - - FromDocumentID = 99 - - TemplateImageID = 100 diff --git a/packages/code_editor/codeeditor/tools/QScintilla/__init__.py b/packages/code_editor/codeeditor/tools/QScintilla/__init__.py deleted file mode 100644 index 4d195b47..00000000 --- a/packages/code_editor/codeeditor/tools/QScintilla/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2002 - 2020 Detlev Offenbach -# - -""" -Package implementing the editor and shell components of the eric6 IDE. - -The editor component of the eric6 IDE is based on the Qt port -of the Scintilla editor widget. It supports syntax highlighting, code -folding, has an interface to the integrated debugger and can be -configured to the most possible degree. - -The shell component is derived from the editor component and is the visible -component of the interactive language shell. It interacts with the debug -client through the debug server. -""" diff --git a/packages/code_editor/codeeditor/tools/Utilities/ModuleParser.py b/packages/code_editor/codeeditor/tools/Utilities/ModuleParser.py deleted file mode 100644 index 074db5b0..00000000 --- a/packages/code_editor/codeeditor/tools/Utilities/ModuleParser.py +++ /dev/null @@ -1,1699 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2003 - 2020 Detlev Offenbach -# - -""" -Parse a Python module file. - -BUGS (from pyclbr.py) -
    -
  • Code that doesn't pass tabnanny or python -t will confuse it, unless - you set the module TABWIDTH variable (default 8) to the correct tab width - for the file.
  • -
-""" - -import os -import re -import sys -from functools import reduce - -import Utilities -import importlib.machinery - -__all__ = ["Module", "Class", "Function", "Attribute", "RbModule", - "readModule", "getTypeFromTypeName"] - -TABWIDTH = 4 - -SEARCH_ERROR = 0 -PY_SOURCE = 1 -PTL_SOURCE = 128 -RB_SOURCE = 129 - -SUPPORTED_TYPES = [PY_SOURCE, PTL_SOURCE, RB_SOURCE] -TYPE_MAPPING = { - "Python": PY_SOURCE, - "Python3": PY_SOURCE, - "MicroPython": PY_SOURCE, - "Ruby": RB_SOURCE, -} - - -def getTypeFromTypeName(name): - """ - Module function to determine the module type given the module type name. - - @param name module type name (string) - @return module type or -1 for failure (integer) - """ - if name in TYPE_MAPPING: - return TYPE_MAPPING[name] - else: - return -1 - - -_py_getnext = re.compile( - r""" - (?P - \# .*? $ # ignore everything in comments - ) - -| (?P - \""" (?P - [^"\\]* (?: - (?: \\. | "(?!"") ) - [^"\\]* - )* - ) - \""" - - | """ (?P - [^'\\]* (?: - (?: \\. | '(?!'') ) - [^'\\]* - )* - ) - """ - - | " [^"\\\n]* (?: \\. [^"\\\n]*)* " - - | ' [^'\\\n]* (?: \\. [^'\\\n]*)* ' - - | \#\#\# (?P - [^#\\]* (?: - (?: \\. | \#(?!\#\#) ) - [^#\\]* - )* - ) - \#\#\# - ) - -| (?P - (?<= :) \s* - [ru]? \""" (?P - [^"\\]* (?: - (?: \\. | "(?!"") ) - [^"\\]* - )* - ) - \""" - - | (?<= :) \s* - [ru]? """ (?P - [^'\\]* (?: - (?: \\. | '(?!'') ) - [^'\\]* - )* - ) - """ - - | (?<= :) \s* - \#\#\# (?P - [^#\\]* (?: - (?: \\. | \#(?!\#\#) ) - [^#\\]* - )* - ) - \#\#\# - ) - -| (?P - ^ - (?P [ \t]* ) - (?P @classmethod | @staticmethod ) - ) - -| (?P - (^ [ \t]* @ (?: PyQt[45] \. )? (?: QtCore \. )? - (?: pyqtSignature | pyqtSlot ) - [ \t]* \( - (?P [^)]* ) - \) \s* - )? - ^ - (?P [ \t]* ) - (?: async [ \t]+ )? def [ \t]+ - (?P \w+ ) - (?: [ \t]* \[ (?: plain | html ) \] )? - [ \t]* \( - (?P (?: [^)] | \)[ \t]*,? )*? ) - \) [ \t]* - (?P (?: -> [ \t]* [^:]+ )? ) - [ \t]* : - ) - -| (?P - ^ - (?P [ \t]* ) - class [ \t]+ - (?P \w+ ) - [ \t]* - (?P \( [^)]* \) )? - [ \t]* : - ) - -| (?P - ^ - (?P [ \t]* ) - self [ \t]* \. [ \t]* - (?P \w+ ) - [ \t]* = - ) - -| (?P - ^ - (?P [ \t]* ) - (?P \w+ ) - [ \t]* = [ \t]* (?P (?:pyqtSignal)? ) - ) - -| (?P
- ^ - if \s+ __name__ \s* == \s* [^:]+ : $ - ) - -| (?P - ^ [ \t]* (?: import | from [ \t]+ \. [ \t]+ import ) [ \t]+ - (?P (?: [^#;\\\n]* (?: \\\n )* )* ) - ) - -| (?P - ^ [ \t]* from [ \t]+ - (?P - \.* \w+ - (?: - [ \t]* \. [ \t]* \w+ - )* - ) - [ \t]+ - import [ \t]+ - (?P - (?: \( \s* .*? \s* \) ) - | - (?: [^#;\\\n]* (?: \\\n )* )* ) - ) - -| (?P - ^ - (?P [ \t]* ) - (?: (?: if | elif ) [ \t]+ [^:]* | else [ \t]* ) : - (?= \s* (?: async [ \t]+ )? def) - )""", - re.VERBOSE | re.DOTALL | re.MULTILINE).search - -_rb_getnext = re.compile( - r""" - (?P - =begin [ \t]+ edoc (?P .*? ) =end - ) - -| (?P - =begin .*? =end - - | <<-? (?P [a-zA-Z0-9_]+? ) [ \t]* .*? (?P=HereMarker1) - - | <<-? ['"] (?P .*? ) ['"] [ \t]* .*? (?P=HereMarker2) - - | " [^"\\\n]* (?: \\. [^"\\\n]*)* " - - | ' [^'\\\n]* (?: \\. [^'\\\n]*)* ' - ) - -| (?P - ^ - [ \t]* \#+ .*? $ - ) - -| (?P - ^ - (?P [ \t]* ) - def [ \t]+ - (?: - (?P [a-zA-Z0-9_]+ (?: \. | :: ) - [a-zA-Z_] [a-zA-Z0-9_?!=]* ) - | - (?P [a-zA-Z_] [a-zA-Z0-9_?!=]* ) - | - (?P [^( \t]{1,3} ) - ) - [ \t]* - (?: - \( (?P (?: [^)] | \)[ \t]*,? )*? ) \) - )? - [ \t]* - ) - -| (?P - ^ - (?P [ \t]* ) - class - (?: - [ \t]+ - (?P [A-Z] [a-zA-Z0-9_]* ) - [ \t]* - (?P < [ \t]* [A-Z] [a-zA-Z0-9_]* )? - | - [ \t]* << [ \t]* - (?P [a-zA-Z_] [a-zA-Z0-9_]* ) - ) - [ \t]* - ) - -| (?P - \( - [ \t]* - class - .*? - end - [ \t]* - \) - ) - -| (?P - ^ - (?P [ \t]* ) - module [ \t]+ - (?P [A-Z] [a-zA-Z0-9_]* ) - [ \t]* - ) - -| (?P - ^ - (?P [ \t]* ) - (?: - (?P private | public | protected ) [^_] - | - (?P - private_class_method | public_class_method ) - ) - \(? - [ \t]* - (?P (?: : [a-zA-Z0-9_]+ , \s* )* - (?: : [a-zA-Z0-9_]+ )+ )? - [ \t]* - \)? - ) - -| (?P - ^ - (?P [ \t]* ) - (?P (?: @ | @@ | [A-Z]) [a-zA-Z0-9_]* ) - [ \t]* = - ) - -| (?P - ^ - (?P [ \t]* ) - attr - (?P (?: _accessor | _reader | _writer ) )? - \(? - [ \t]* - (?P (?: : [a-zA-Z0-9_]+ , \s* )* - (?: : [a-zA-Z0-9_]+ | true | false )+ ) - [ \t]* - \)? - ) - -| (?P - ^ - [ \t]* - (?: if | unless | case | while | until | for | begin ) \b [^_] - | - [ \t]* do [ \t]* (?: \| .*? \| )? [ \t]* $ - ) - -| (?P - \b (?: if ) \b [^_] .*? $ - | - \b (?: if ) \b [^_] .*? end [ \t]* $ - ) - -| (?P - [ \t]* - (?: - end [ \t]* $ - | - end \b [^_] - ) - )""", - re.VERBOSE | re.DOTALL | re.MULTILINE).search - -_hashsub = re.compile(r"""^([ \t]*)#[ \t]?""", re.MULTILINE).sub - -_commentsub = re.compile(r"""#[^\n]*\n|#[^\n]*$""").sub - -_modules = {} # cache of modules we've seen - - -class VisibilityBase(object): - """ - Class implementing the visibility aspect of all objects. - """ - - def isPrivate(self): - """ - Public method to check, if the visibility is Private. - - @return flag indicating Private visibility (boolean) - """ - return self.visibility == 0 - - def isProtected(self): - """ - Public method to check, if the visibility is Protected. - - @return flag indicating Protected visibility (boolean) - """ - return self.visibility == 1 - - def isPublic(self): - """ - Public method to check, if the visibility is Public. - - @return flag indicating Public visibility (boolean) - """ - return self.visibility == 2 - - def setPrivate(self): - """ - Public method to set the visibility to Private. - """ - self.visibility = 0 - - def setProtected(self): - """ - Public method to set the visibility to Protected. - """ - self.visibility = 1 - - def setPublic(self): - """ - Public method to set the visibility to Public. - """ - self.visibility = 2 - - -class Module(object): - """ - Class to represent a Python module. - """ - - def __init__(self, name, file=None, moduleType=None): - """ - Constructor - - @param name name of this module (string) - @param file filename of file containing this module (string) - @param moduleType type of this module - """ - self.name = name - self.file = file - self.modules = {} - self.modules_counts = {} - self.classes = {} - self.classes_counts = {} - self.functions = {} - self.functions_counts = {} - self.description = "" - self.globals = {} - self.imports = [] - self.from_imports = {} - self.package = '.'.join(name.split('.')[:-1]) - self.type = moduleType - if moduleType in [PY_SOURCE, PTL_SOURCE]: - self._getnext = _py_getnext - elif moduleType == RB_SOURCE: - self._getnext = _rb_getnext - else: - self._getnext = None - - def addClass(self, name, _class): - """ - Public method to add information about a class. - - @param name name of class to be added (string) - @param _class Class object to be added - """ - if name in self.classes: - self.classes_counts[name] += 1 - name = "{0}_{1:d}".format(name, self.classes_counts[name]) - else: - self.classes_counts[name] = 0 - self.classes[name] = _class - - def addModule(self, name, module): - """ - Public method to add information about a Ruby module. - - @param name name of module to be added (string) - @param module Module object to be added - """ - if name in self.modules: - self.modules_counts[name] += 1 - name = "{0}_{1:d}".format(name, self.modules_counts[name]) - else: - self.modules_counts[name] = 0 - self.modules[name] = module - - def addFunction(self, name, function): - """ - Public method to add information about a function. - - @param name name of function to be added (string) - @param function Function object to be added - """ - if name in self.functions: - self.functions_counts[name] += 1 - name = "{0}_{1:d}".format(name, self.functions_counts[name]) - else: - self.functions_counts[name] = 0 - self.functions[name] = function - - def addGlobal(self, name, attr): - """ - Public method to add information about global variables. - - @param name name of the global to add (string) - @param attr Attribute object to be added - """ - if name not in self.globals: - self.globals[name] = attr - else: - self.globals[name].addAssignment(attr.lineno) - - def addDescription(self, description): - """ - Public method to store the modules docstring. - - @param description the docstring to be stored (string) - """ - self.description = description - - def scan(self, src): - """ - Public method to scan the source text and retrieve the relevant - information. - - @param src the source text to be scanned (string) - """ - if self.type in [PY_SOURCE, PTL_SOURCE]: - self.__py_scan(src) - elif self.type == RB_SOURCE: - self.__rb_scan(src) - - def __py_setVisibility(self, objectRef): - """ - Private method to set the visibility of an object. - - @param objectRef reference to the object (Attribute, Class or Function) - """ - if objectRef.name.startswith('__'): - objectRef.setPrivate() - elif objectRef.name.startswith('_'): - objectRef.setProtected() - else: - objectRef.setPublic() - - def __py_scan(self, src): - """ - Private method to scan the source text of a Python module and retrieve - the relevant information. - - @param src the source text to be scanned (string) - """ - lineno, last_lineno_pos = 1, 0 - lastGlobalEntry = None - classstack = [] # stack of (class, indent) pairs - conditionalsstack = [] # stack of indents of conditional defines - deltastack = [] - deltaindent = 0 - deltaindentcalculated = 0 - i = 0 - modulelevel = True - cur_obj = self - modifierType = Function.General - modifierIndent = -1 - while True: - m = self._getnext(src, i) - if not m: - break - start, i = m.span() - - if m.start("MethodModifier") >= 0: - modifierIndent = _indent(m.group("MethodModifierIndent")) - modifierType = m.group("MethodModifierType") - - elif m.start("Method") >= 0: - # found a method definition or function - thisindent = _indent(m.group("MethodIndent")) - meth_name = m.group("MethodName") - meth_sig = m.group("MethodSignature") - meth_sig = meth_sig.replace('\\\n', '') - meth_ret = m.group("MethodReturnAnnotation") - meth_ret = meth_ret.replace('\\\n', '') - if m.group("MethodPyQtSignature") is not None: - meth_pyqtSig = ( - m.group("MethodPyQtSignature") - .replace('\\\n', '') - .split('result')[0] - .split('name')[0] - .strip("\"', \t") - ) - else: - meth_pyqtSig = None - lineno = lineno + src.count('\n', last_lineno_pos, start) - last_lineno_pos = start - if modifierType and modifierIndent == thisindent: - if modifierType == "@staticmethod": - modifier = Function.Static - elif modifierType == "@classmethod": - modifier = Function.Class - else: - modifier = Function.General - else: - modifier = Function.General - # modify indentation level for conditional defines - if conditionalsstack: - if thisindent > conditionalsstack[-1]: - if not deltaindentcalculated: - deltastack.append( - thisindent - conditionalsstack[-1]) - deltaindent = reduce( - lambda x, y: x + y, deltastack) - deltaindentcalculated = 1 - thisindent -= deltaindent - else: - while ( - conditionalsstack and - conditionalsstack[-1] >= thisindent - ): - del conditionalsstack[-1] - if deltastack: - del deltastack[-1] - deltaindentcalculated = 0 - # close all classes indented at least as much - while classstack and classstack[-1][1] >= thisindent: - if ( - classstack[-1][0] is not None and - isinstance(classstack[-1][0], (Class, Function)) - ): - # record the end line of this class or function - classstack[-1][0].setEndLine(lineno - 1) - del classstack[-1] - if classstack: - csi = -1 - while csi >= -len(classstack): - # nested defs are added to the class - cur_class = classstack[csi][0] - csi -= 1 - if cur_class is None: - continue - - if isinstance(cur_class, Class): - # it's a class method - f = Function( - None, meth_name, None, lineno, - meth_sig, meth_pyqtSig, modifierType=modifier, - annotation=meth_ret) - self.__py_setVisibility(f) - cur_class.addMethod(meth_name, f) - break - else: - # it's a nested function of a module function - f = Function( - self.name, meth_name, self.file, lineno, - meth_sig, meth_pyqtSig, modifierType=modifier, - annotation=meth_ret) - self.__py_setVisibility(f) - self.addFunction(meth_name, f) - else: - # it's a module function - f = Function(self.name, meth_name, self.file, lineno, - meth_sig, meth_pyqtSig, modifierType=modifier, - annotation=meth_ret) - self.__py_setVisibility(f) - self.addFunction(meth_name, f) - if not classstack: - if lastGlobalEntry: - lastGlobalEntry.setEndLine(lineno - 1) - lastGlobalEntry = f - if cur_obj and isinstance(cur_obj, Function): - cur_obj.setEndLine(lineno - 1) - cur_obj = f - classstack.append((None, thisindent)) # Marker for nested fns - - # reset the modifier settings - modifierType = Function.General - modifierIndent = -1 - - elif m.start("Docstring") >= 0: - contents = m.group("DocstringContents3") - if contents is not None: - contents = _hashsub(r"\1", contents) - else: - if self.file.lower().endswith('.ptl'): - contents = "" - else: - contents = ( - m.group("DocstringContents1") or - m.group("DocstringContents2") - ) - if cur_obj: - cur_obj.addDescription(contents) - - elif m.start("String") >= 0: - if ( - modulelevel and ( - src[start - len('\r\n'):start] == '\r\n' or - src[start - len('\n'):start] == '\n' or - src[start - len('\r'):start] == '\r' - ) - ): - contents = m.group("StringContents3") - if contents is not None: - contents = _hashsub(r"\1", contents) - else: - if self.file.lower().endswith('.ptl'): - contents = "" - else: - contents = ( - m.group("StringContents1") or - m.group("StringContents2") - ) - if cur_obj: - cur_obj.addDescription(contents) - - elif m.start("Class") >= 0: - # we found a class definition - thisindent = _indent(m.group("ClassIndent")) - lineno = lineno + src.count('\n', last_lineno_pos, start) - last_lineno_pos = start - # close all classes indented at least as much - while classstack and classstack[-1][1] >= thisindent: - if ( - classstack[-1][0] is not None and - isinstance(classstack[-1][0], (Class, Function)) - ): - # record the end line of this class or function - classstack[-1][0].setEndLine(lineno - 1) - del classstack[-1] - class_name = m.group("ClassName") - inherit = m.group("ClassSupers") - if inherit: - # the class inherits from other classes - inherit = inherit[1:-1].strip() - inherit = _commentsub('', inherit) - names = [] - for n in inherit.split(','): - n = n.strip() - if n: - if n in self.classes: - # we know this super class - n = self.classes[n].name - else: - c = n.split('.') - if len(c) > 1: - # super class is of the - # form module.class: - # look in module for class - m = c[-2] - c = c[-1] - if m in _modules: - m = _modules[m] - n = m.name - names.append(n) - inherit = names - # remember this class - cur_class = Class(self.name, class_name, inherit, - self.file, lineno) - self.__py_setVisibility(cur_class) - cur_obj = cur_class - # add nested classes to the module - self.addClass(class_name, cur_class) - if not classstack: - if lastGlobalEntry: - lastGlobalEntry.setEndLine(lineno - 1) - lastGlobalEntry = cur_class - classstack.append((cur_class, thisindent)) - - elif m.start("Attribute") >= 0: - lineno = lineno + src.count('\n', last_lineno_pos, start) - last_lineno_pos = start - index = -1 - while index >= -len(classstack): - if classstack[index][0] is not None: - attrName = m.group("AttributeName") - attr = Attribute( - self.name, attrName, self.file, lineno) - self.__py_setVisibility(attr) - classstack[index][0].addAttribute(attrName, attr) - break - else: - index -= 1 - - elif m.start("Main") >= 0: - # 'main' part of the script, reset class stack - lineno = lineno + src.count('\n', last_lineno_pos, start) - last_lineno_pos = start - classstack = [] - - elif m.start("Variable") >= 0: - thisindent = _indent(m.group("VariableIndent")) - variable_name = m.group("VariableName") - isSignal = m.group("VariableSignal") != "" - lineno = lineno + src.count('\n', last_lineno_pos, start) - last_lineno_pos = start - if thisindent == 0: - # global variable - attr = Attribute( - self.name, variable_name, self.file, lineno, - isSignal=isSignal) - self.__py_setVisibility(attr) - self.addGlobal(variable_name, attr) - if lastGlobalEntry: - lastGlobalEntry.setEndLine(lineno - 1) - lastGlobalEntry = None - else: - index = -1 - while index >= -len(classstack): - if classstack[index][1] >= thisindent: - index -= 1 - else: - if ( - classstack[index][0] is not None and - isinstance(classstack[index][0], Class) - ): - attr = Attribute( - self.name, variable_name, self.file, - lineno, isSignal=isSignal) - self.__py_setVisibility(attr) - classstack[index][0].addGlobal( - variable_name, attr) - break - - elif m.start("Import") >= 0: - ## import module - names = [n.strip() for n in - "".join(m.group("ImportList").splitlines()) - .replace("\\", "").split(',')] - self.imports.extend( - [name for name in names - if name not in self.imports]) - - elif m.start("ImportFrom") >= 0: - ## from module import stuff - mod = m.group("ImportFromPath") - namesLines = (m.group("ImportFromList") - .replace("(", "").replace(")", "") - .replace("\\", "") - .strip().splitlines()) - namesLines = [line.split("#")[0].strip() - for line in namesLines] - names = [n.strip() for n in - "".join(namesLines) - .split(',')] - if mod not in self.from_imports: - self.from_imports[mod] = [] - self.from_imports[mod].extend( - [name for name in names - if name not in self.from_imports[mod]]) - - elif m.start("ConditionalDefine") >= 0: - # a conditional function/method definition - thisindent = _indent(m.group("ConditionalDefineIndent")) - while ( - conditionalsstack and - conditionalsstack[-1] >= thisindent - ): - del conditionalsstack[-1] - if deltastack: - del deltastack[-1] - conditionalsstack.append(thisindent) - deltaindentcalculated = 0 - - elif m.start("Comment") >= 0: - if modulelevel: - continue - - modulelevel = False - - def __rb_scan(self, src): - """ - Private method to scan the source text of a Python module and retrieve - the relevant information. - - @param src the source text to be scanned (string) - """ - lineno, last_lineno_pos = 1, 0 - classstack = [] # stack of (class, indent) pairs - acstack = [] # stack of (access control, indent) pairs - indent = 0 - i = 0 - cur_obj = self - lastGlobalEntry = None - while True: - m = self._getnext(src, i) - if not m: - break - start, i = m.span() - - if m.start("Method") >= 0: - # found a method definition or function - thisindent = indent - indent += 1 - meth_name = ( - m.group("MethodName") or - m.group("MethodName2") or - m.group("MethodName3") - ) - meth_sig = m.group("MethodSignature") - meth_sig = meth_sig and meth_sig.replace('\\\n', '') or '' - lineno = lineno + src.count('\n', last_lineno_pos, start) - last_lineno_pos = start - if meth_name.startswith('self.'): - meth_name = meth_name[5:] - elif meth_name.startswith('self::'): - meth_name = meth_name[6:] - # close all classes/modules indented at least as much - while classstack and classstack[-1][1] >= thisindent: - if ( - classstack[-1][0] is not None and - isinstance(classstack[-1][0], - (Class, Function, RbModule)) - ): - # record the end line of this class, function or module - classstack[-1][0].setEndLine(lineno - 1) - del classstack[-1] - while acstack and acstack[-1][1] >= thisindent: - del acstack[-1] - if classstack: - csi = -1 - while csi >= -len(classstack): - # nested defs are added to the class - cur_class = classstack[csi][0] - csi -= 1 - if cur_class is None: - continue - - if ( - isinstance(cur_class, Class) or - isinstance(cur_class, RbModule) - ): - # it's a class/module method - f = Function(None, meth_name, - None, lineno, meth_sig) - cur_class.addMethod(meth_name, f) - break - else: - # it's a nested function of a module function - f = Function( - self.name, meth_name, self.file, lineno, meth_sig) - self.addFunction(meth_name, f) - # set access control - if acstack: - accesscontrol = acstack[-1][0] - if accesscontrol == "private": - f.setPrivate() - elif accesscontrol == "protected": - f.setProtected() - elif accesscontrol == "public": - f.setPublic() - else: - # it's a function - f = Function( - self.name, meth_name, self.file, lineno, meth_sig) - self.addFunction(meth_name, f) - if not classstack: - if lastGlobalEntry: - lastGlobalEntry.setEndLine(lineno - 1) - lastGlobalEntry = f - if cur_obj and isinstance(cur_obj, Function): - cur_obj.setEndLine(lineno - 1) - cur_obj = f - classstack.append((None, thisindent)) # Marker for nested fns - - elif m.start("Docstring") >= 0: - contents = m.group("DocstringContents") - if contents is not None: - contents = _hashsub(r"\1", contents) - if cur_obj: - cur_obj.addDescription(contents) - - elif m.start("String") >= 0: - pass - - elif m.start("Comment") >= 0: - pass - - elif m.start("ClassIgnored") >= 0: - pass - - elif m.start("Class") >= 0: - # we found a class definition - thisindent = indent - indent += 1 - lineno = lineno + src.count('\n', last_lineno_pos, start) - last_lineno_pos = start - # close all classes/modules indented at least as much - while classstack and classstack[-1][1] >= thisindent: - if ( - classstack[-1][0] is not None and - isinstance(classstack[-1][0], - (Class, Function, RbModule)) - ): - # record the end line of this class, function or module - classstack[-1][0].setEndLine(lineno - 1) - del classstack[-1] - class_name = m.group("ClassName") or m.group("ClassName2") - inherit = m.group("ClassSupers") - if inherit: - # the class inherits from other classes - inherit = inherit[1:].strip() - inherit = [_commentsub('', inherit)] - # remember this class - cur_class = Class(self.name, class_name, inherit, - self.file, lineno) - # add nested classes to the file - if classstack and isinstance(classstack[-1][0], RbModule): - parent_obj = classstack[-1][0] - else: - parent_obj = self - if class_name in parent_obj.classes: - cur_class = parent_obj.classes[class_name] - elif ( - classstack and - isinstance(classstack[-1][0], Class) and - class_name == "self" - ): - cur_class = classstack[-1][0] - else: - parent_obj.addClass(class_name, cur_class) - if not classstack: - if lastGlobalEntry: - lastGlobalEntry.setEndLine(lineno - 1) - lastGlobalEntry = cur_class - cur_obj = cur_class - classstack.append((cur_class, thisindent)) - while acstack and acstack[-1][1] >= thisindent: - del acstack[-1] - acstack.append(["public", thisindent]) - # default access control is 'public' - - elif m.start("Module") >= 0: - # we found a module definition - thisindent = indent - indent += 1 - lineno = lineno + src.count('\n', last_lineno_pos, start) - last_lineno_pos = start - # close all classes/modules indented at least as much - while classstack and classstack[-1][1] >= thisindent: - if ( - classstack[-1][0] is not None and - isinstance(classstack[-1][0], - (Class, Function, RbModule)) - ): - # record the end line of this class, function or module - classstack[-1][0].setEndLine(lineno - 1) - del classstack[-1] - module_name = m.group("ModuleName") - # remember this class - cur_class = RbModule(self.name, module_name, - self.file, lineno) - # add nested Ruby modules to the file - if module_name in self.modules: - cur_class = self.modules[module_name] - else: - self.addModule(module_name, cur_class) - if not classstack: - if lastGlobalEntry: - lastGlobalEntry.setEndLine(lineno - 1) - lastGlobalEntry = cur_class - cur_obj = cur_class - classstack.append((cur_class, thisindent)) - while acstack and acstack[-1][1] >= thisindent: - del acstack[-1] - acstack.append(["public", thisindent]) - # default access control is 'public' - - elif m.start("AccessControl") >= 0: - aclist = m.group("AccessControlList") - if aclist is None: - index = -1 - while index >= -len(acstack): - if acstack[index][1] < indent: - actype = ( - m.group("AccessControlType") or - m.group("AccessControlType2").split('_')[0] - ) - acstack[index][0] = actype.lower() - break - else: - index -= 1 - else: - index = -1 - while index >= -len(classstack): - if ( - classstack[index][0] is not None and - not isinstance(classstack[index][0], Function) and - not classstack[index][1] >= indent - ): - parent = classstack[index][0] - actype = ( - m.group("AccessControlType") or - m.group("AccessControlType2").split('_')[0] - ) - actype = actype.lower() - for name in aclist.split(","): - # get rid of leading ':' - name = name.strip()[1:] - acmeth = parent.getMethod(name) - if acmeth is None: - continue - if actype == "private": - acmeth.setPrivate() - elif actype == "protected": - acmeth.setProtected() - elif actype == "public": - acmeth.setPublic() - break - else: - index -= 1 - - elif m.start("Attribute") >= 0: - lineno = lineno + src.count('\n', last_lineno_pos, start) - last_lineno_pos = start - index = -1 - while index >= -len(classstack): - if ( - classstack[index][0] is not None and - not isinstance(classstack[index][0], Function) and - not classstack[index][1] >= indent - ): - attrName = m.group("AttributeName") - attr = Attribute( - self.name, attrName, self.file, lineno) - if attrName.startswith("@@") or attrName[0].isupper(): - classstack[index][0].addGlobal(attrName, attr) - else: - classstack[index][0].addAttribute(attrName, attr) - break - else: - index -= 1 - else: - attrName = m.group("AttributeName") - if attrName[0] != "@": - attr = Attribute( - self.name, attrName, self.file, lineno) - self.addGlobal(attrName, attr) - if lastGlobalEntry: - lastGlobalEntry.setEndLine(lineno - 1) - lastGlobalEntry = None - - elif m.start("Attr") >= 0: - lineno = lineno + src.count('\n', last_lineno_pos, start) - last_lineno_pos = start - index = -1 - while index >= -len(classstack): - if ( - classstack[index][0] is not None and - not isinstance(classstack[index][0], Function) and - not classstack[index][1] >= indent - ): - parent = classstack[index][0] - if m.group("AttrType") is None: - nv = m.group("AttrList").split(",") - if not nv: - break - # get rid of leading ':' - name = nv[0].strip()[1:] - attr = ( - parent.getAttribute("@" + name) or - parent.getAttribute("@@" + name) or - Attribute( - self.name, "@" + name, self.file, lineno) - ) - if len(nv) == 1 or nv[1].strip() == "false": - attr.setProtected() - elif nv[1].strip() == "true": - attr.setPublic() - parent.addAttribute(attr.name, attr) - else: - access = m.group("AttrType") - for name in m.group("AttrList").split(","): - # get rid of leading ':' - name = name.strip()[1:] - attr = ( - parent.getAttribute("@" + name) or - parent.getAttribute("@@" + name) or - Attribute( - self.name, "@" + name, self.file, - lineno) - ) - if access == "_accessor": - attr.setPublic() - elif ( - access == "_reader" or - access == "_writer" - ): - if attr.isPrivate(): - attr.setProtected() - elif attr.isProtected(): - attr.setPublic() - parent.addAttribute(attr.name, attr) - break - else: - index -= 1 - - elif m.start("Begin") >= 0: - # a begin of a block we are not interested in - indent += 1 - - elif m.start("End") >= 0: - # an end of a block - indent -= 1 - if indent < 0: - # no negative indent allowed - if classstack: - # it's a class/module method - indent = classstack[-1][1] - else: - indent = 0 - - elif m.start("BeginEnd") >= 0: - pass - - def createHierarchy(self): - """ - Public method to build the inheritance hierarchy for all classes of - this module. - - @return A dictionary with inheritance hierarchies. - """ - hierarchy = {} - for cls in list(list(self.classes.keys())): - self.assembleHierarchy(cls, self.classes, [cls], hierarchy) - for mod in list(list(self.modules.keys())): - self.assembleHierarchy(mod, self.modules, [mod], hierarchy) - return hierarchy - - def assembleHierarchy(self, name, classes, path, result): - """ - Public method to assemble the inheritance hierarchy. - - This method will traverse the class hierarchy, from a given class - and build up a nested dictionary of super-classes. The result is - intended to be inverted, i.e. the highest level are the super classes. - - This code is borrowed from Boa Constructor. - - @param name name of class to assemble hierarchy (string) - @param classes A dictionary of classes to look in. - @param path - @param result The resultant hierarchy - """ - rv = {} - if name in classes: - for cls in classes[name].super: - if cls not in classes: - rv[cls] = {} - exhausted = path + [cls] - exhausted.reverse() - self.addPathToHierarchy( - exhausted, result, self.addPathToHierarchy) - else: - rv[cls] = self.assembleHierarchy( - cls, classes, path + [cls], result) - - if len(rv) == 0: - exhausted = path - exhausted.reverse() - self.addPathToHierarchy(exhausted, result, self.addPathToHierarchy) - - def addPathToHierarchy(self, path, result, fn): - """ - Public method to put the exhausted path into the result dictionary. - - @param path the exhausted path of classes - @param result the result dictionary - @param fn function to call for classe that are already part of the - result dictionary - """ - if path[0] in list(list(result.keys())): - if len(path) > 1: - fn(path[1:], result[path[0]], fn) - else: - for part in path: - result[part] = {} - result = result[part] - - def getName(self): - """ - Public method to retrieve the modules name. - - @return module name (string) - """ - return self.name - - def getFileName(self): - """ - Public method to retrieve the modules filename. - - @return module filename (string) - """ - return self.file - - def getType(self): - """ - Public method to get the type of the module's source. - - @return type of the modules's source (string) - """ - if self.type in [PY_SOURCE, PTL_SOURCE]: - moduleType = "Python3" - elif self.type == RB_SOURCE: - moduleType = "Ruby" - else: - moduleType = "" - return moduleType - - -class Class(VisibilityBase): - """ - Class to represent a Python class. - """ - - def __init__(self, module, name, superClasses, file, lineno): - """ - Constructor - - @param module name of module containing this class (string) - @param name name of the class (string) - @param superClasses list of classnames this class is inherited from - (list of strings) - @param file name of file containing this class (string) - @param lineno linenumber of the class definition (integer) - """ - self.module = module - self.name = name - if superClasses is None: - superClasses = [] - self.super = superClasses - self.methods = {} - self.attributes = {} - self.globals = {} - self.file = file - self.lineno = lineno - self.endlineno = -1 # marker for "not set" - self.description = "" - self.setPublic() - - def addMethod(self, name, function): - """ - Public method to add information about a method. - - @param name name of method to be added (string) - @param function Function object to be added - """ - self.methods[name] = function - - def getMethod(self, name): - """ - Public method to retrieve a method by name. - - @param name name of the method (string) - @return the named method or None - """ - try: - return self.methods[name] - except KeyError: - return None - - def addAttribute(self, name, attr): - """ - Public method to add information about attributes. - - @param name name of the attribute to add (string) - @param attr Attribute object to be added - """ - if name not in self.attributes: - self.attributes[name] = attr - else: - self.attributes[name].addAssignment(attr.lineno) - - def getAttribute(self, name): - """ - Public method to retrieve an attribute by name. - - @param name name of the attribute (string) - @return the named attribute or None - """ - try: - return self.attributes[name] - except KeyError: - return None - - def addGlobal(self, name, attr): - """ - Public method to add information about global (class) variables. - - @param name name of the global to add (string) - @param attr Attribute object to be added - """ - if name not in self.globals: - self.globals[name] = attr - else: - self.globals[name].addAssignment(attr.lineno) - - def addDescription(self, description): - """ - Public method to store the class docstring. - - @param description the docstring to be stored (string) - """ - self.description = description - - def setEndLine(self, endLineNo): - """ - Public method to record the number of the last line of a class. - - @param endLineNo number of the last line (integer) - """ - self.endlineno = endLineNo - - -class RbModule(Class): - """ - Class to represent a Ruby module. - """ - - def __init__(self, module, name, file, lineno): - """ - Constructor - - @param module name of module containing this class (string) - @param name name of the class (string) - @param file name of file containing this class (string) - @param lineno linenumber of the class definition (integer) - """ - Class.__init__(self, module, name, None, file, lineno) - self.classes = {} - - def addClass(self, name, _class): - """ - Public method to add information about a class. - - @param name name of class to be added (string) - @param _class Class object to be added - """ - self.classes[name] = _class - - -class Function(VisibilityBase): - """ - Class to represent a Python function or method. - """ - General = 0 - Static = 1 - Class = 2 - - def __init__(self, module, name, file, lineno, signature='', - pyqtSignature=None, modifierType=General, annotation=""): - """ - Constructor - - @param module name of module containing this function (string) - @param name name of the function (string) - @param file name of file containing this function (string) - @param lineno linenumber of the function definition (integer) - @param signature the functions call signature (string) - @param pyqtSignature the functions PyQt signature (string) - @param modifierType type of the function - @param annotation return annotation - """ - self.module = module - self.name = name - self.file = file - self.lineno = lineno - self.endlineno = -1 # marker for "not set" - signature = _commentsub('', signature) - self.parameters = [e.strip() for e in signature.split(',')] - self.description = "" - self.pyqtSignature = pyqtSignature - self.modifier = modifierType - self.annotation = annotation - self.setPublic() - - def addDescription(self, description): - """ - Public method to store the functions docstring. - - @param description the docstring to be stored (string) - """ - self.description = description - - def setEndLine(self, endLineNo): - """ - Public method to record the number of the last line of a class. - - @param endLineNo number of the last line (integer) - """ - self.endlineno = endLineNo - - -class Attribute(VisibilityBase): - """ - Class to represent a Python function or method. - """ - - def __init__(self, module, name, file, lineno, isSignal=False): - """ - Constructor - - @param module name of module containing this function (string) - @param name name of the function (string) - @param file name of file containing this function (string) - @param lineno linenumber of the first attribute assignment (integer) - @keyparam isSignal flag indicating a signal definition (boolean) - """ - self.module = module - self.name = name - self.file = file - self.lineno = lineno - self.isSignal = isSignal - self.setPublic() - self.linenos = [lineno] - - def addAssignment(self, lineno): - """ - Public method to add another assignment line number. - - @param lineno linenumber of the additional attribute assignment - (integer) - """ - if lineno not in self.linenos: - self.linenos.append(lineno) - - -def readModule(module, path=None, inpackage=False, basename="", - extensions=None, caching=True, ignoreBuiltinModules=False): - """ - Function to read a module file and parse it. - - The module is searched in path and sys.path, read and parsed. - If the module was parsed before, the information is taken - from a cache in order to speed up processing. - - @param module name of the module to be parsed (string) - @param path search path for the module (list of strings) - @param inpackage flag indicating that module is inside a - package (boolean) - @param basename a path basename that is deleted from the filename of - the module file to be read (string) - @param extensions list of extensions, which should be considered valid - source file extensions (list of strings) - @param caching flag indicating that the parsed module should be - cached (boolean) - @param ignoreBuiltinModules flag indicating to ignore the builtin modules - (boolean) - @return reference to a Module object containing the parsed - module information (Module) - """ - global _modules - - if extensions is None: - _extensions = ['.py', '.pyw', '.ptl', '.rb'] - else: - _extensions = extensions[:] - try: - _extensions.remove('.py') - except ValueError: - pass - - modname = module - - if os.path.exists(module): - path = [os.path.dirname(module)] - if module.lower().endswith(".py"): - module = module[:-3] - if ( - os.path.exists(os.path.join(path[0], "__init__.py")) or - os.path.exists(os.path.join(path[0], "__init__.rb")) or - inpackage - ): - if basename: - module = module.replace(basename, "") - if os.path.isabs(module): - modname = os.path.splitdrive(module)[1][len(os.sep):] - else: - modname = module - modname = modname.replace(os.sep, '.') - inpackage = 1 - else: - modname = os.path.basename(module) - for ext in _extensions: - if modname.lower().endswith(ext): - modname = modname[:-len(ext)] - break - module = os.path.basename(module) - - if caching and modname in _modules: - # we've seen this module before... - return _modules[modname] - - if not ignoreBuiltinModules and module in sys.builtin_module_names: - # this is a built-in module - mod = Module(modname, None, None) - if caching: - _modules[modname] = mod - return mod - - # search the path for the module - path = [] if path is None else path[:] - f = None - if inpackage: - try: - f, file, (suff, mode, moduleType) = find_module( - module, path, _extensions) - except ImportError: - f = None - if f is None: - fullpath = path[:] + sys.path[:] - f, file, (suff, mode, moduleType) = find_module( - module, fullpath, _extensions) - if f: - f.close() - if moduleType not in SUPPORTED_TYPES: - # not supported source, can't do anything with this module - _modules[modname] = Module(modname, None, None) - return _modules[modname] - - mod = Module(modname, file, moduleType) - try: - src = Utilities.readEncodedFile(file)[0] - mod.scan(src) - except (UnicodeError, IOError): - pass - if caching: - _modules[modname] = mod - return mod - - -def _indent(ws): - """ - Protected function to determine the indent width of a whitespace string. - - @param ws The whitespace string to be cheked. (string) - @return Length of the whitespace string after tab expansion. - """ - return len(ws.expandtabs(TABWIDTH)) - - -def find_module(name, path, extensions): - """ - Module function to extend the Python module finding mechanism. - - This function searches for files in the given path. If the filename - doesn't have an extension or an extension of .py, the normal search - implemented in the imp module is used. For all other supported files - only path is searched. - - @param name filename or modulename to search for (string) - @param path search path (list of strings) - @param extensions list of extensions, which should be considered valid - source file extensions (list of strings) - @return tuple of the open file, pathname and description. Description - is a tuple of file suffix, file mode and file type) - @exception ImportError The file or module wasn't found. - """ - for ext in extensions: - if name.lower().endswith(ext): - for p in path: # only search in path - if os.path.exists(os.path.join(p, name)): - pathname = os.path.join(p, name) - if ext == '.ptl': - # Quixote page template - return (open(pathname), pathname, - ('.ptl', 'r', PTL_SOURCE)) - elif ext == '.rb': - # Ruby source file - return (open(pathname), pathname, - ('.rb', 'r', RB_SOURCE)) - else: - return (open(pathname), pathname, - (ext, 'r', PY_SOURCE)) - raise ImportError - - # standard Python module file - if name.lower().endswith('.py'): - name = name[:-3] - - spec = importlib.machinery.PathFinder.find_spec(name, path) - if spec is None: - raise ImportError - if isinstance(spec.loader, importlib.machinery.SourceFileLoader): - ext = os.path.splitext(spec.origin)[-1] - return (open(spec.origin), spec.origin, (ext, 'r', PY_SOURCE)) - - raise ImportError - - -def resetParsedModules(): - """ - Module function to reset the list of modules already parsed. - """ - _modules.clear() - - -def resetParsedModule(module, basename=""): - """ - Module function to clear one module from the list of parsed modules. - - @param module Name of the module to be parsed (string) - @param basename a path basename. This basename is deleted from - the filename of the module file to be cleared. (string) - """ - modname = module - - if os.path.exists(module): - path = [os.path.dirname(module)] - if module.lower().endswith(".py"): - module = module[:-3] - if os.path.exists(os.path.join(path[0], "__init__.py")): - if basename: - module = module.replace(basename, "") - modname = module.replace(os.sep, '.') - else: - modname = os.path.basename(module) - if ( - modname.lower().endswith(".ptl") or - modname.lower().endswith(".pyw") - ): - modname = modname[:-4] - elif modname.lower().endswith(".rb"): - modname = modname[:-3] - module = os.path.basename(module) - - if modname in _modules: - del _modules[modname] diff --git a/packages/code_editor/codeeditor/tools/Utilities/__init__.py b/packages/code_editor/codeeditor/tools/Utilities/__init__.py deleted file mode 100644 index 61130c1e..00000000 --- a/packages/code_editor/codeeditor/tools/Utilities/__init__.py +++ /dev/null @@ -1,164 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2003 - 2020 Detlev Offenbach -# - -""" -Package implementing various functions/classes needed everywhere within eric6. -""" -import os -import re -from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF32 - -try: - EXTSEP = os.extsep -except AttributeError: - EXTSEP = "." -codingBytes_regexps = [ - (5, re.compile(br"""coding[:=]\s*([-\w_.]+)""")), - (1, re.compile(br"""<\?xml.*\bencoding\s*=\s*['"]([-\w_.]+)['"]\?>""")), -] - - -def decode(text): - """ - Function to decode some byte text into a string. - - @param text byte text to decode (bytes) - @return tuple of decoded text and encoding (string, string) - """ - try: - if text.startswith(BOM_UTF8): - # UTF-8 with BOM - return str(text[len(BOM_UTF8):], 'utf-8'), 'utf-8-bom' - elif text.startswith(BOM_UTF16): - # UTF-16 with BOM - return str(text[len(BOM_UTF16):], 'utf-16'), 'utf-16' - elif text.startswith(BOM_UTF32): - # UTF-32 with BOM - return str(text[len(BOM_UTF32):], 'utf-32'), 'utf-32' - coding = get_codingBytes(text) - if coding: - return str(text, coding), coding - except (UnicodeError, LookupError): - pass - - # Assume UTF-8 - try: - return str(text, 'utf-8'), 'utf-8' - except (UnicodeError, LookupError): - pass - - guess = None - # Try the universal character encoding detector - try: - import chardet - guess = chardet.detect(text) - if ( - guess and - guess['confidence'] > 0.95 and - guess['encoding'] is not None - ): - codec = guess['encoding'].lower() - return str(text, codec), '{0}'.format(codec) - except (UnicodeError, LookupError): - pass - except ImportError: - pass - - # Use the guessed one even if confifence level is low - if guess and guess['encoding'] is not None: - try: - codec = guess['encoding'].lower() - return str(text, codec), '{0}'.format(codec) - except (UnicodeError, LookupError): - pass - - # Assume UTF-8 loosing information - return str(text, "utf-8", "ignore"), 'utf-8' - - -def get_codingBytes(text): - """ - Function to get the coding of a bytes text. - - @param text bytes text to inspect (bytes) - @return coding string - """ - lines = text.splitlines() - for coding in codingBytes_regexps: - coding_re = coding[1] - head = lines[:coding[0]] - for line in head: - m = coding_re.search(line) - if m: - return str(m.group(1), "ascii").lower() - return None - - -def readEncodedFile(filename): - """ - Function to read a file and decode its contents into proper text. - - @param filename name of the file to read (string) - @return tuple of decoded text and encoding (string, string) - """ - f = open(filename, "rb") - text = f.read() - f.close() - return decode(text) - - -def joinext(prefix, ext): - """ - Function to join a file extension to a path. - - The leading "." of ext is replaced by a platform specific extension - separator if necessary. - - @param prefix the basepart of the filename (string) - @param ext the extension part (string) - @return the complete filename (string) - """ - if ext[0] != ".": - ext = ".{0}".format(ext) - # require leading separator to match os.path.splitext - return prefix + EXTSEP + ext[1:] - - -def getDirs(path, excludeDirs): - """ - Function returning a list of all directories below path. - - @param path root of the tree to check - @param excludeDirs basename of directories to ignore - @return list of all directories found - """ - try: - names = os.listdir(path) - except EnvironmentError: - return [] - - dirs = [] - for name in names: - if ( - os.path.isdir(os.path.join(path, name)) and - not os.path.islink(os.path.join(path, name)) - ): - exclude = 0 - for e in excludeDirs: - if name.split(os.sep, 1)[0] == e: - exclude = 1 - break - if not exclude: - dirs.append(os.path.join(path, name)) - - for name in dirs[:]: - if not os.path.islink(name): - dirs = dirs + getDirs(name, excludeDirs) - - return dirs - - -def isWindowsPlatform(): - return os.name == 'nt' diff --git a/packages/code_editor/codeeditor/tools/__init__.py b/packages/code_editor/codeeditor/tools/__init__.py deleted file mode 100644 index 3a1213c8..00000000 --- a/packages/code_editor/codeeditor/tools/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - diff --git a/packages/code_editor/codeeditor/tools/eric6_api.py b/packages/code_editor/codeeditor/tools/eric6_api.py deleted file mode 100644 index 246187aa..00000000 --- a/packages/code_editor/codeeditor/tools/eric6_api.py +++ /dev/null @@ -1,324 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# Copyright (c) 2003 - 2020 Detlev Offenbach -# - -""" -Eric6 API Generator. - -This is the main Python script of the API generator. It is -this script that gets called via the API generation interface. -This script can be used via the commandline as well. -""" - -import DocumentationTools -import Utilities -from DocumentationTools.APIGenerator import APIGenerator -import Utilities.ModuleParser -import fnmatch -import glob -import os -import sys - -sys.path.insert(1, os.path.dirname(__file__)) - - -# make ThirdParty package available as a packages repository -sys.path.insert(2, os.path.join(os.path.dirname(__file__), - "ThirdParty", "EditorConfig")) - - -Version = '20.8 (rev. 27cfb65c7324)' - - -def usage(): - """ - Function to print some usage information. - - It prints a reference of all commandline parameters that may - be used and ends the application. - """ - print("eric6_api") - print() - print("Copyright (c) 2004 - 2020 Detlev Offenbach" - " .") - print() - print("Usage:") - print() - print(" eric6_api [options] files...") - print() - print("where files can be either python modules, package") - print("directories or ordinary directories.") - print() - print("Options:") - print() - print(" -b name or --base=name") - print(" Use the given name as the name of the base package.") - print(" -e eol-type or --eol=eol-type") - print(" Use the given eol type to terminate lines.") - print(" Valid values are 'cr', 'lf' and 'crlf'.") - print(" --exclude-file=pattern") - print(" Specify a filename pattern of files to be excluded.") - print(" This option may be repeated multiple times.") - print(" -h or --help") - print(" Show this help and exit.") - print(" -i or --ignore") - print(" Ignore the set of builtin modules") - print(" -l language or --language=language") - print(" Generate an API file for the given programming language.") - print(" Supported programming languages are:") - for lang in sorted( - DocumentationTools.supportedExtensionsDictForApis.keys()): - print(" * {0}".format(lang)) - print(" The default is 'Python3'.") - print(" This option may be repeated multiple times.") - print(" -o filename or --output=filename") - print(" Write the API information to the named file." - " A '%L' placeholder") # __IGNORE_WARNING_M601__ - print(" is replaced by the language of the API file" - " (see --language).") - print(" -p or --private") - print(" Include private methods and functions.") - print(" -R, -r or --recursive") - print(" Perform a recursive search for source files.") - print(" -t ext or --extension=ext") - print(" Add the given extension to the list of file extensions.") - print(" This option may be given multiple times.") - print(" -V or --version") - print(" Show version information and exit.") - print(" -x directory or --exclude=directory") - print(" Specify a directory basename to be excluded.") - print(" This option may be repeated multiple times.") - sys.exit(1) - - -def version(): - """ - Function to show the version information. - """ - print( - """eric6_api {0}\n""" - """\n""" - """Eric6 API generator.\n""" - """\n""" - """Copyright (c) 2004 - 2020 Detlev Offenbach""" - """ \n""" - """This is free software; see the LICENSE.GPL3 for copying""" - """ conditions.\n""" - """There is NO warranty; not even for MERCHANTABILITY or FITNESS""" - """ FOR A\n""" - """PARTICULAR PURPOSE.""".format(Version)) - sys.exit(1) - - -def main(): - """ - Main entries point into the application. - """ - global supportedExtensions - - import getopt - - try: - opts, args = getopt.getopt( - sys.argv[1:], "b:e:hil:o:pRrt:Vx:", - ["base=", "eol=", "exclude=", "exclude-file=", "extension=", - "help", "ignore", "language=", "output=", "private", "recursive", - "version", ]) - except getopt.error: - usage() - - excludeDirs = [".svn", ".hg", ".git", ".ropeproject", ".eric6project", - "dist", "build", "doc", "docs"] - excludePatterns = [] - outputFileName = "" - recursive = False - basePackage = "" - includePrivate = False - progLanguages = [] - extensions = [] - newline = None - ignoreBuiltinModules = False - - for k, v in opts: - if k in ["-o", "--output"]: - outputFileName = v - elif k in ["-R", "-r", "--recursive"]: - recursive = True - elif k in ["-x", "--exclude"]: - excludeDirs.append(v) - elif k == "--exclude-file": - excludePatterns.append(v) - elif k in ["-h", "--help"]: - usage() - elif k in ["-i", "--ignore"]: - ignoreBuiltinModules = True - elif k in ["-V", "--version"]: - version() - elif k in ["-t", "--extension"]: - if not v.startswith("."): - v = ".{0}".format(v) - extensions.append(v) - elif k in ["-b", "--base"]: - basePackage = v - elif k in ["-p", "--private"]: - includePrivate = True - elif k in ["-l", "--language"]: - if v not in progLanguages: - if v not in DocumentationTools.supportedExtensionsDictForApis: - sys.stderr.write( - "Wrong language given: {0}. Aborting\n".format(v)) - sys.exit(1) - else: - progLanguages.append(v) - elif k in ["-e", "--eol"]: - if v.lower() == "cr": - newline = '\r' - elif v.lower() == "lf": - newline = '\n' - elif v.lower() == "crlf": - newline = '\r\n' - - if not args: - usage() - - if outputFileName == "": - sys.stderr.write("No output file given. Aborting\n") - sys.exit(1) - - if len(progLanguages) == 0: - progLanguages = ["Python3"] - - for progLanguage in sorted(progLanguages): - basename = "" - apis = [] - basesDict = {} - - supportedExtensions = ( - DocumentationTools.supportedExtensionsDictForApis[progLanguage] - ) - supportedExtensions.extend(extensions) - if "%L" in outputFileName: - outputFile = outputFileName.replace("%L", progLanguage) - else: - if len(progLanguages) == 1: - outputFile = outputFileName - else: - root, ext = os.path.splitext(outputFileName) - outputFile = "{0}-{1}{2}".format(root, progLanguage.lower(), - ext) - basesFile = os.path.splitext(outputFile)[0] + '.bas' - - for arg in args: - if os.path.isdir(arg): - if os.path.exists(os.path.join( - arg, Utilities.joinext("__init__", ".py"))): - basename = os.path.dirname(arg) - if arg == '.': - sys.stderr.write("The directory '.' is a package.\n") - sys.stderr.write( - "Please repeat the call giving its real name.\n") - sys.stderr.write("Ignoring the directory.\n") - continue - else: - basename = arg - if basename: - basename = "{0}{1}".format(basename, os.sep) - - if recursive and not os.path.islink(arg): - names = [arg] + Utilities.getDirs(arg, excludeDirs) - else: - names = [arg] - else: - basename = "" - names = [arg] - - for filename in sorted(names): - inpackage = False - if os.path.isdir(filename): - files = [] - for ext in supportedExtensions: - files.extend(glob.glob(os.path.join( - filename, Utilities.joinext("*", ext)))) - initFile = os.path.join( - filename, Utilities.joinext("__init__", ext)) - if initFile in files: - inpackage = True - files.remove(initFile) - files.insert(0, initFile) - elif progLanguage != "Python3": - # assume package - inpackage = True - else: - if ( - Utilities.isWindowsPlatform() and - glob.has_magic(filename) - ): - files = glob.glob(filename) - else: - files = [filename] - - for file in files: - skipIt = False - for pattern in excludePatterns: - if fnmatch.fnmatch(os.path.basename(file), pattern): - skipIt = True - break - if skipIt: - continue - - try: - module = Utilities.ModuleParser.readModule( - file, - basename=basename, inpackage=inpackage, - ignoreBuiltinModules=ignoreBuiltinModules) - apiGenerator = APIGenerator(module) - api = apiGenerator.genAPI(True, basePackage, - includePrivate) - bases = apiGenerator.genBases(includePrivate) - except IOError as v: - sys.stderr.write("{0} error: {1}\n".format(file, v[1])) - continue - except ImportError as v: - sys.stderr.write("{0} error: {1}\n".format(file, v)) - continue - - for apiEntry in api: - if apiEntry not in apis: - apis.append(apiEntry) - for basesEntry in bases: - if bases[basesEntry]: - basesDict[basesEntry] = bases[basesEntry][:] - sys.stdout.write("-- {0} -- {1} ok\n".format( - progLanguage, file)) - - outdir = os.path.dirname(outputFile) - if outdir and not os.path.exists(outdir): - os.makedirs(outdir) - try: - out = open(outputFile, "w", encoding="utf-8", newline=newline) - out.write("\n".join(sorted(apis)) + "\n") - out.close() - except IOError as v: - sys.stderr.write("{0} error: {1}\n".format(outputFile, v[1])) - sys.exit(3) - # try: - # out = open(basesFile, "w", encoding="utf-8", newline=newline) - # for baseEntry in sorted(basesDict.keys()): - # out.write("{0} {1}\n".format( - # baseEntry, " ".join(sorted(basesDict[baseEntry])))) - # out.close() - # except IOError as v: - # sys.stderr.write("{0} error: {1}\n".format(basesFile, v[1])) - # sys.exit(3) - - sys.stdout.write('\nDone.\n') - sys.exit(0) - - -if __name__ == '__main__': - main() - -# -# eflag: noqa = M801 -- Gitee From 909eefd9f4d3b8f2b69381781d67637005f4bff4 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 20:27:21 +0800 Subject: [PATCH 012/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E5=88=A0=E9=99=A4=E7=A9=BA=E7=99=BD=E7=9A=84simpleedi?= =?UTF-8?q?tor=EF=BC=8C=E8=B0=83=E6=95=B4translate.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../errors_translation/translate.py | 46 +++++++++++-------- .../codeeditor/simpleeditor/__init__.py | 5 -- .../code_editor/codeeditor/simpleeditor/lexer | 0 3 files changed, 28 insertions(+), 23 deletions(-) delete mode 100644 packages/code_editor/codeeditor/simpleeditor/__init__.py delete mode 100644 packages/code_editor/codeeditor/simpleeditor/lexer diff --git a/packages/code_editor/codeeditor/errors_translation/translate.py b/packages/code_editor/codeeditor/errors_translation/translate.py index 2d57d4a2..14771df8 100644 --- a/packages/code_editor/codeeditor/errors_translation/translate.py +++ b/packages/code_editor/codeeditor/errors_translation/translate.py @@ -1,20 +1,30 @@ +""" +将translations.txt转换为flake8_trans.json,以供PyMiner使用。 +""" + import json -import os +import re +from pathlib import Path +from pprint import pprint + + +def main(): + data = {} + src = Path(__file__).parent.absolute() / 'translations.txt' + dst = Path(__file__).parent.parent.absolute() / 'flake8_trans.json' + with open(src, 'r', encoding='utf-8') as file: + lines = file.readlines() + lines = [line.replace('\n', '') for line in lines] + lines = [line for line in lines if line.startswith(('E', 'W', 'C', 'F'))] + lines = [list(line.strip().split(maxsplit=1)) for line in lines] + lines = [[word.strip() for word in words] for words in lines if len(words) >= 1] + [words.append('') for words in lines if len(words) == 1] + lines = [(id, re.sub(' +', ' ', message)) for id, message in lines] + [data.__setitem__(id, message) for id, message in lines] + pprint(data) + with open(dst, 'w') as file: + json.dump(data, file, indent=4) + -d = {} -with open('translations.txt', 'r', encoding='utf-8') as f: - for l in f.read().split('\n'): - if l.startswith(('E', 'W', 'C', 'F')): - try: - l1 = l.split() - id = l1[0].strip() - print(l1, l) - msg = ' '.join([x.strip() for x in l1[1:]]) - d[id] = msg - except: - pass -l.split() -print(d) -path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'flake8_trans.json') -with open(path, 'w') as f: - json.dump(d, f, indent=4) +if __name__ == '__main__': + main() diff --git a/packages/code_editor/codeeditor/simpleeditor/__init__.py b/packages/code_editor/codeeditor/simpleeditor/__init__.py deleted file mode 100644 index cbfee26a..00000000 --- a/packages/code_editor/codeeditor/simpleeditor/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/1/18 8:01 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: __init__.py.py diff --git a/packages/code_editor/codeeditor/simpleeditor/lexer b/packages/code_editor/codeeditor/simpleeditor/lexer deleted file mode 100644 index e69de29b..00000000 -- Gitee From f1c537775525dccf07bdb0c5b2fb360f9356e71c Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 20:27:48 +0800 Subject: [PATCH 013/108] =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=B8=A4=E4=B8=AA?= =?UTF-8?q?=E6=97=A0=E7=94=A8=E7=9A=84ui=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/codeeditor/ui/formeditor.py | 84 ------------------- .../code_editor/codeeditor/ui/gotoline.py | 60 ------------- 2 files changed, 144 deletions(-) delete mode 100644 packages/code_editor/codeeditor/ui/formeditor.py delete mode 100644 packages/code_editor/codeeditor/ui/gotoline.py diff --git a/packages/code_editor/codeeditor/ui/formeditor.py b/packages/code_editor/codeeditor/ui/formeditor.py deleted file mode 100644 index 4c5f4343..00000000 --- a/packages/code_editor/codeeditor/ui/formeditor.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'formeditor.ui' -## -## Created by: Qt User Interface Compiler version 5.15.2 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide2.QtCore import * -from PySide2.QtGui import * -from PySide2.QtWidgets import * - -from pmgwidgets import PMGQsciWidget - - -class Ui_FormEditor(object): - def setupUi(self, FormEditor): - if not FormEditor.objectName(): - FormEditor.setObjectName(u"FormEditor") - FormEditor.resize(800, 600) - self.gridLayout = QGridLayout(FormEditor) - self.gridLayout.setObjectName(u"gridLayout") - self.gridLayout.setHorizontalSpacing(20) - self.gridLayout.setVerticalSpacing(3) - self.gridLayout.setContentsMargins(0, 0, 0, 3) - self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) - - self.gridLayout.addItem(self.horizontalSpacer, 1, 0, 1, 1) - - self.label_status_length = QLabel(FormEditor) - self.label_status_length.setObjectName(u"label_status_length") - - self.gridLayout.addWidget(self.label_status_length, 1, 1, 1, 1) - - self.label_status_encoding = QLabel(FormEditor) - self.label_status_encoding.setObjectName(u"label_status_encoding") - - self.gridLayout.addWidget(self.label_status_encoding, 1, 5, 1, 1) - - self.label_status_sel = QLabel(FormEditor) - self.label_status_sel.setObjectName(u"label_status_sel") - - self.gridLayout.addWidget(self.label_status_sel, 1, 3, 1, 1) - - self.label_status_ln_col = QLabel(FormEditor) - self.label_status_ln_col.setObjectName(u"label_status_ln_col") - - self.gridLayout.addWidget(self.label_status_ln_col, 1, 2, 1, 1) - - self.label_status_eol = QLabel(FormEditor) - self.label_status_eol.setObjectName(u"label_status_eol") - - self.gridLayout.addWidget(self.label_status_eol, 1, 4, 1, 1) - - self.textEdit = PMGQsciWidget(FormEditor) - self.textEdit.setObjectName(u"textEdit") - self.textEdit.setFrameShape(QFrame.NoFrame) - - self.gridLayout.addWidget(self.textEdit, 0, 0, 1, 7) - - self.gridLayout.setRowStretch(0, 1) - - self.retranslateUi(FormEditor) - - QMetaObject.connectSlotsByName(FormEditor) - # setupUi - - def retranslateUi(self, FormEditor): - FormEditor.setWindowTitle(QCoreApplication.translate("FormEditor", u"Form", None)) - self.label_status_length.setText(QCoreApplication.translate("FormEditor", u"Length:{0} Lines:{1}", None)) - self.label_status_encoding.setText(QCoreApplication.translate("FormEditor", u"UTF-8", None)) - self.label_status_sel.setText(QCoreApplication.translate("FormEditor", u"Sel:{0} | {1}", None)) - self.label_status_ln_col.setText(QCoreApplication.translate("FormEditor", u"Ln:{0} Col:{1}", None)) - self.label_status_eol.setText(QCoreApplication.translate("FormEditor", u"Unix(LF)", None)) -#if QT_CONFIG(tooltip) - self.textEdit.setToolTip("") -#endif // QT_CONFIG(tooltip) -#if QT_CONFIG(whatsthis) - self.textEdit.setWhatsThis("") -#endif // QT_CONFIG(whatsthis) - # retranslateUi - diff --git a/packages/code_editor/codeeditor/ui/gotoline.py b/packages/code_editor/codeeditor/ui/gotoline.py deleted file mode 100644 index f9234841..00000000 --- a/packages/code_editor/codeeditor/ui/gotoline.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'gotoline.ui' -## -## Created by: Qt User Interface Compiler version 5.15.2 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide2.QtCore import * -from PySide2.QtGui import * -from PySide2.QtWidgets import * - - -class Ui_DialogGoto(object): - def setupUi(self, DialogGoto): - if not DialogGoto.objectName(): - DialogGoto.setObjectName(u"DialogGoto") - DialogGoto.resize(300, 94) - self.gridLayout = QGridLayout(DialogGoto) - self.gridLayout.setObjectName(u"gridLayout") - self.gridLayout.setContentsMargins(-1, 18, -1, -1) - self.lineEdit = QLineEdit(DialogGoto) - self.lineEdit.setObjectName(u"lineEdit") - - self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1) - - self.label = QLabel(DialogGoto) - self.label.setObjectName(u"label") - font = QFont() - font.setBold(True) - font.setWeight(75) - self.label.setFont(font) - - self.gridLayout.addWidget(self.label, 0, 0, 1, 1) - - self.buttonBox = QDialogButtonBox(DialogGoto) - self.buttonBox.setObjectName(u"buttonBox") - self.buttonBox.setOrientation(Qt.Horizontal) - self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok) - - self.gridLayout.addWidget(self.buttonBox, 2, 0, 1, 2) - - self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) - - self.gridLayout.addItem(self.verticalSpacer, 1, 1, 1, 1) - - - self.retranslateUi(DialogGoto) - self.buttonBox.rejected.connect(DialogGoto.reject) - - QMetaObject.connectSlotsByName(DialogGoto) - # setupUi - - def retranslateUi(self, DialogGoto): - DialogGoto.setWindowTitle(QCoreApplication.translate("DialogGoto", u"Go to Line/Column", None)) - self.label.setText(QCoreApplication.translate("DialogGoto", u"[Line] [:column]:", None)) - # retranslateUi - -- Gitee From 5596028003b26c6def5a282e671a29679e6b01b4 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 20:31:02 +0800 Subject: [PATCH 014/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pmgwidgets/utilities/source/iconutils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pmgwidgets/utilities/source/iconutils.py b/pmgwidgets/utilities/source/iconutils.py index 2d4aca90..226acb2b 100644 --- a/pmgwidgets/utilities/source/iconutils.py +++ b/pmgwidgets/utilities/source/iconutils.py @@ -1,3 +1,5 @@ +from typing import Tuple + from PySide2.QtGui import QIcon, QPixmap @@ -7,10 +9,8 @@ def create_icon(icon_path: str = ":/pyqt/source/images/New.png"): return icon -def colorTup2Str(self, value: tuple) -> str: - if value is None: - return None - strcolor = '#' +def color_rgb_to_str(value: Tuple[int, ...]) -> str: + result = '#' for i in value: - strcolor += hex(int(i))[-2:].replace('x', '0') - return strcolor + result += hex(int(i))[-2:].replace('x', '0') + return result -- Gitee From d15d8ec54477b1d95a5d921d44b4ad75896cffa6 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 20:57:59 +0800 Subject: [PATCH 015/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/codeeditor/abstracteditor.py | 10 +---- .../code_editor/codeeditor/markdowneditor.py | 38 +++---------------- 2 files changed, 6 insertions(+), 42 deletions(-) diff --git a/packages/code_editor/codeeditor/abstracteditor.py b/packages/code_editor/codeeditor/abstracteditor.py index 920e8170..4a1443ad 100644 --- a/packages/code_editor/codeeditor/abstracteditor.py +++ b/packages/code_editor/codeeditor/abstracteditor.py @@ -28,20 +28,12 @@ Created on 2020/9/7 __version__ = '0.1' -import ast -import json import logging import os -import re -import time -from itertools import groupby -from typing import TYPE_CHECKING, List, Iterable, Dict, Set, Tuple, Any +from typing import Dict, Any -from PySide2.QtCore import QDir from PySide2.QtWidgets import QWidget, QMessageBox -from pmgwidgets import in_unit_test - logger = logging.getLogger(__name__) diff --git a/packages/code_editor/codeeditor/markdowneditor.py b/packages/code_editor/codeeditor/markdowneditor.py index bacfa24b..672be483 100644 --- a/packages/code_editor/codeeditor/markdowneditor.py +++ b/packages/code_editor/codeeditor/markdowneditor.py @@ -1,14 +1,13 @@ import os -import time import re -from PySide2.QtWidgets import QHBoxLayout, QMessageBox -from PySide2.QtGui import QKeyEvent, QFocusEvent, QMouseEvent +import time + from PySide2.QtCore import Qt +from PySide2.QtGui import QKeyEvent +from PySide2.QtWidgets import QHBoxLayout, QMessageBox -from packages.qt_vditor.client import Window from packages.code_editor.codeeditor.abstracteditor import PMAbstractEditor - -from pmgwidgets import in_unit_test +from packages.qt_vditor.client import Window class PMMarkdownEditor(PMAbstractEditor): @@ -70,29 +69,6 @@ class PMMarkdownEditor(PMAbstractEditor): """ return False - def slot_about_close(self, save_all=False): - """ - 当点击右上角叉号时触发的方法,如果检测到未保存的更改,会阻止关闭并弹出窗口让用户确认保存。 - Args: - save_all: - - Returns: - - """ - - if not self.modified(): - return QMessageBox.Discard - buttons = QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel - if save_all: - buttons |= QMessageBox.SaveAll # 保存全部 - buttons |= QMessageBox.NoToAll # 放弃所有 - ret = QMessageBox.question(self, self.tr('Save'), self.tr('Save file "{0}"?').format(self.filename()), buttons, - QMessageBox.Save) - if ret == QMessageBox.Save or ret == QMessageBox.SaveAll: - if not self.save(): - return QMessageBox.Cancel - return ret - def slot_save(self) -> None: self.textEdit.save_file() self.last_save_time = time.time() @@ -115,7 +91,3 @@ class PMMarkdownEditor(PMAbstractEditor): return '' else: return code_blocks[0] - - def mousePressEvent(self, a0: QMouseEvent) -> None: - print('mouse pressed') - super(PMMarkdownEditor, self).mousePressEvent(a0) -- Gitee From 8fd1f6d168f042466e9f28a10c1cde45fb1f28eb Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 21:08:42 +0800 Subject: [PATCH 016/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qtpyeditor/codeeditor/abstracteditor.py | 8 ------ .../qtpyeditor/codeeditor/baseeditor.py | 25 ++++++++----------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py index 04a123de..ba2b1c5d 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py @@ -69,14 +69,6 @@ class PMAbstractEditor(QWidget): """ pass - def _init_lexer(self, lexer: 'QsciLexer') -> None: - """ - 初始化语法解析器 - - :return: None - """ - pass - def _init_signals(self) -> None: """ 初始化信号绑定 diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py index f1a73ef4..4ae2c14e 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py @@ -36,16 +36,17 @@ __version__ = '0.1' import logging import os import time -from typing import TYPE_CHECKING, List, Dict, Any +from typing import TYPE_CHECKING, List, Dict, Any, Callable from PySide2.QtCore import QDir, QCoreApplication, Qt, QPoint, Signal, QTranslator, QLocale, SignalInstance -from PySide2.QtGui import QIcon, QKeySequence, QTextDocument, QTextCursor +from PySide2.QtGui import QIcon, QKeySequence, QTextDocument, QTextCursor, QCloseEvent from PySide2.QtWidgets import QWidget, QMessageBox, QFileDialog, QAction, QShortcut, QDialog, QVBoxLayout, QPushButton, \ QHBoxLayout, QApplication, QLabel -from packages.code_editor.codeeditor.qtpyeditor.codeeditor.abstracteditor import PMAbstractEditor -from packages.code_editor.codeeditor.qtpyeditor.ui.gotoline import Ui_DialogGoto from pmgwidgets.widgets.composited import PMGPanel +from ..codeeditor.abstracteditor import PMAbstractEditor +from ...qtpyeditor.Utilities import decode +from ...qtpyeditor.ui.gotoline import Ui_DialogGoto if TYPE_CHECKING: from packages.code_editor.codeeditor.qtpyeditor.codeedit import PMBaseCodeEdit @@ -54,6 +55,8 @@ logger = logging.getLogger(__name__) class GotoLineDialog(QDialog, Ui_DialogGoto): + tr: Callable[[str], str] + def __init__(self, parent=None): super(GotoLineDialog, self).__init__(parent) self.current_line = -1 @@ -100,6 +103,8 @@ class GotoLineDialog(QDialog, Ui_DialogGoto): class FindDialog(QDialog): + tr: Callable[[str], str] + def __init__(self, parent=None, text_editor: 'PMGBaseEditor' = None): super(FindDialog, self).__init__(parent) self.text_editor = text_editor @@ -157,7 +162,7 @@ class FindDialog(QDialog): def replace_all(self): settings = self.settings_panel.get_value() text_to_replace = self.settings_panel.widgets_dic['text_to_replace'].get_value() - while (1): + while 1: b = self.text_editor.search_word(forward=True, **settings) if b: self.text_edit.replace_selection(text_to_replace) @@ -262,14 +267,6 @@ class PMGBaseEditor(PMAbstractEditor): text_cursor.setPosition(pos) self.text_edit.setTextCursor(text_cursor) - def _init_lexer(self, lexer: 'QsciLexer') -> None: - """ - 初始化语法解析器 - - :return: None - """ - pass - def search_word(self, text_to_find: str, wrap: bool, regex: bool, case_sensitive: bool, whole_word: bool, forward: bool, index=-1, line=-1, **kwargs) -> bool: find_flags = 0 @@ -286,7 +283,6 @@ class PMGBaseEditor(PMAbstractEditor): # print(find_flags) ret = self.text_edit.find(text_to_find, options=find_flags) cursor_pos = self.text_edit.get_cursor_position() - print(ret, wrap) if wrap and (not ret): cursor = self.text_edit.textCursor() cursor.clearSelection() @@ -341,7 +337,6 @@ class PMGBaseEditor(PMAbstractEditor): :type path: str :return: None """ - from packages.code_editor.codeeditor.qtpyeditor.Utilities import decode self._path = '' try: # 读取文件内容并加载 -- Gitee From 23d07690d5bedb0ac210da363955ffc562744f62 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 21:22:03 +0800 Subject: [PATCH 017/108] =?UTF-8?q?=E7=A7=BB=E5=8A=A8base=5Feditor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/codeeditor/markdowneditor.py | 2 +- .../qtpyeditor/icons/autocomp/class.png | Bin 10159 -> 0 bytes .../qtpyeditor/icons/autocomp/function.png | Bin 8486 -> 0 bytes .../qtpyeditor/icons/autocomp/instance.png | Bin 9527 -> 0 bytes .../qtpyeditor/icons/autocomp/keyword.png | Bin 9868 -> 0 bytes .../qtpyeditor/icons/autocomp/module.png | Bin 11126 -> 0 bytes .../qtpyeditor/icons/autocomp/param.png | Bin 9525 -> 0 bytes .../qtpyeditor/icons/autocomp/path.png | Bin 3717 -> 0 bytes .../qtpyeditor/icons/autocomp/property.png | Bin 5226 -> 0 bytes .../qtpyeditor/icons/autocomp/statement.png | Bin 10369 -> 0 bytes .../codeeditor/qtpyeditor/linenumber.py | 8 ++++---- packages/code_editor/widgets/__init__.py | 0 .../code_editor/widgets/editors/__init__.py | 0 .../editors/base_editor.py} | 2 -- .../code_editor/widgets/text_edit/__init__.py | 0 15 files changed, 5 insertions(+), 7 deletions(-) create mode 100644 packages/code_editor/widgets/__init__.py create mode 100644 packages/code_editor/widgets/editors/__init__.py rename packages/code_editor/{codeeditor/abstracteditor.py => widgets/editors/base_editor.py} (99%) create mode 100644 packages/code_editor/widgets/text_edit/__init__.py diff --git a/packages/code_editor/codeeditor/markdowneditor.py b/packages/code_editor/codeeditor/markdowneditor.py index 672be483..e49875ef 100644 --- a/packages/code_editor/codeeditor/markdowneditor.py +++ b/packages/code_editor/codeeditor/markdowneditor.py @@ -6,7 +6,7 @@ from PySide2.QtCore import Qt from PySide2.QtGui import QKeyEvent from PySide2.QtWidgets import QHBoxLayout, QMessageBox -from packages.code_editor.codeeditor.abstracteditor import PMAbstractEditor +from packages.code_editor.widgets.editors.base_editor import PMAbstractEditor from packages.qt_vditor.client import Window diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/class.png b/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/class.png index 0043dfea1927fd9fb0c2e112a2d3e0beb0ebaf26..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 10159 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 diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/module.png b/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/module.png index 2caa6f11b90b06e75e3bc915bb340e644376c9bd..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 11126 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 diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/param.png b/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/param.png index 96bd66a86f4685a80eb3e9d159c2d10732ace076..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 9525 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 K8&Lm6gxG1gpbUOewy5d>WgRyZ#l006LR;?)e8 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 diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/property.png b/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/property.png index c4a9a513b6322ea97ed633d06179a34aa03b31b4..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 5226 zcmchbc{J4D|Ht3&nX!#x(%6%Uv4==@8M1F7WM9W_$R5JTC%YO-NXou1*@aNX8nS24 zzLhO9B>eRKob&tr{`vj;`{Ukw&VAk2>z;egJLe>jod!%?H_qr1Z%rU5>^lEZWA z#BcG=-!=alBc!`uTJbhwHhs0)HS&*->`j^NUKyEZxxAF}+k1=0O>cYqf5>cW64>=H zNMl`)4yOd_S=*4o5UFfgtz1Y11{)X!fkgF!daY>?FS6adw78gP2t-YbMbJL*9tQZo zHDr9l@l*Ibn*trG_IBc}iC1>THfJG|RBf7&37{#P-K|2AM zX~~C-|M1<=4s(s1P;1if&LK5=B2>*lbe07P=u+m-eMb35hWp2ziykH?D`}UK?wCwM zqC}lXr4J|w)LydW*79LzRq(j6y3m(*ANu`hJH53W)w3dv1Bo@*zzdW}g11H<35c5%laWq0 z@iwNtHDZ4aUodP`xVi(R3ddT9Igem`_pt{Jp1JK2Yvurz-X90WNSdzHCC8178_d~B zZlsS6E<>IrK$hM8D@!?-Q2AA*kDXq`O@8Sy3;gD(tBBTuAx3d5bG7!Lc^X}AHO79l zQ0mZd*aB>#71ppQvOHufAHXNzD!maMf$tUQX1Kgr&=l%}fYzy=P6es<>B>c#wud@q zR=@c%$(BKMLHLIalFgY){8DBo+%1*^Ix>=OhA!rX%(#&exTO=X@P}NbnpTRNV%}HA zmpgs3uydjYhYBK@f?T4BPuH8bEZa*k8c9(PIpXC}E3 zA-@?gLRl+GcOvzZ^@5=AkuyYa$K65U<>Gd*Fm#Q@v9iukR8E*I*ZHO=j8HZ5o%f0< z?!mDGkOnsAG2482iVMc9ys-I1;%DCLwDg6$yapiF&Zp^?thh9VCgwP-?t;~I9dp2W zy7lq_liyVB<@aTaHZCEB;}zsNH{}E5DÍ#yZ&4?~I4ErGWw2N(ogn{zqQ6)<#- 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 AW&i*H diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/statement.png b/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/statement.png index 8b2f1be41615c474d2045808a010b0466560d287..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 10369 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 a#LPCo41tf1a<9@tfQ*C!yh_X<@c#i{&82w& diff --git a/packages/code_editor/codeeditor/qtpyeditor/linenumber.py b/packages/code_editor/codeeditor/qtpyeditor/linenumber.py index b8e95561..d111e44f 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/linenumber.py +++ b/packages/code_editor/codeeditor/qtpyeditor/linenumber.py @@ -3,16 +3,16 @@ # @Author: Zhanyi Hou # @Email: 1295752786@qq.com # @File: linenumber.py -#!/usr/bin/python3 +# !/usr/bin/python3 # QcodeEditor.py by acbetter. # 来源:https://stackoverflow.com/questions/40386194/create-text-area-textedit-with-line-number-in-pyqt # -*- coding: utf-8 -*- import os -from PySide2.QtGui import QFontDatabase from PySide2.QtCore import Qt, QRect, QSize -from PySide2.QtWidgets import QWidget, QPlainTextEdit, QTextEdit from PySide2.QtGui import QColor, QPainter, QTextFormat, QFont +from PySide2.QtGui import QFontDatabase +from PySide2.QtWidgets import QWidget, QPlainTextEdit, QTextEdit import utils @@ -144,4 +144,4 @@ if __name__ == '__main__': app = QApplication(sys.argv) codeEditor = QCodeEditor() codeEditor.show() - sys.exit(app.exec_()) \ No newline at end of file + sys.exit(app.exec_()) diff --git a/packages/code_editor/widgets/__init__.py b/packages/code_editor/widgets/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/code_editor/widgets/editors/__init__.py b/packages/code_editor/widgets/editors/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/code_editor/codeeditor/abstracteditor.py b/packages/code_editor/widgets/editors/base_editor.py similarity index 99% rename from packages/code_editor/codeeditor/abstracteditor.py rename to packages/code_editor/widgets/editors/base_editor.py index 4a1443ad..cafe8b85 100644 --- a/packages/code_editor/codeeditor/abstracteditor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -26,8 +26,6 @@ Created on 2020/9/7 @description: Code Editor """ -__version__ = '0.1' - import logging import os from typing import Dict, Any diff --git a/packages/code_editor/widgets/text_edit/__init__.py b/packages/code_editor/widgets/text_edit/__init__.py new file mode 100644 index 00000000..e69de29b -- Gitee From aca3bae84935a48d099c43b3006529efdda06f90 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 23:33:08 +0800 Subject: [PATCH 018/108] =?UTF-8?q?=E7=A7=BB=E5=8A=A8python=5Feditor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/codeeditor/tabwidget.py | 6 +-- .../editors/python_editor.py} | 37 +++++++++++-------- 2 files changed, 24 insertions(+), 19 deletions(-) rename packages/code_editor/{codeeditor/pythoneditor.py => widgets/editors/python_editor.py} (95%) diff --git a/packages/code_editor/codeeditor/tabwidget.py b/packages/code_editor/codeeditor/tabwidget.py index 23ccb460..3a7f3916 100644 --- a/packages/code_editor/codeeditor/tabwidget.py +++ b/packages/code_editor/codeeditor/tabwidget.py @@ -21,11 +21,11 @@ translated into English by those websites. __version__ = '0.1' -import sys import cgitb import logging import os import re +import sys import time from contextlib import redirect_stdout from io import StringIO @@ -44,7 +44,7 @@ from packages.code_editor.codeeditor.qtpyeditor import PythonHighlighter from pmgwidgets import PMDockObject, in_unit_test, PMGFileSystemWatchdog, UndoManager if TYPE_CHECKING or in_unit_test(): - from packages.code_editor.codeeditor.pythoneditor import PMPythonEditor + from ..widgets.editors.python_editor import PMPythonEditor # from packages.code_editor.codeeditor.baseeditor import PMBaseEditor # from packages.code_editor.codeeditor.cppeditor import PMCPPEditor # from packages.code_editor.codeeditor.cythoneditor import PMCythonEditor @@ -54,7 +54,7 @@ if TYPE_CHECKING or in_unit_test(): # from packages.code_editor.codeeditor.ui.findinpath import FindInPathWidget else: # from codeeditor.baseeditor import PMBaseEditor - from .pythoneditor import PMPythonEditor + from ..widgets.editors.python_editor import PMPythonEditor # from codeeditor.cppeditor import PMCPPEditor # from codeeditor.cythoneditor import PMCythonEditor from .markdowneditor import PMMarkdownEditor diff --git a/packages/code_editor/codeeditor/pythoneditor.py b/packages/code_editor/widgets/editors/python_editor.py similarity index 95% rename from packages/code_editor/codeeditor/pythoneditor.py rename to packages/code_editor/widgets/editors/python_editor.py index 7944e984..3999a3ea 100644 --- a/packages/code_editor/codeeditor/pythoneditor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -17,23 +17,18 @@ import json import logging import os import re +from functools import cached_property +from pathlib import Path from typing import List, Tuple, Optional -from PySide2.QtCore import QDir, Qt, Signal, SignalInstance -from PySide2.QtGui import QCloseEvent, QKeySequence -from PySide2.QtWidgets import QMessageBox, QMenu, QAction, QShortcut +from PySide2.QtCore import SignalInstance, Signal, Qt, QDir +from PySide2.QtGui import QKeySequence, QCloseEvent +from PySide2.QtWidgets import QAction, QShortcut, QMessageBox from yapf.yapflib import py3compat, yapf_api from packages.code_editor.codeeditor.qtpyeditor import PMGPythonEditor, PythonHighlighter -from pmgwidgets import in_unit_test, PMGPanelDialog, parse_simplified_pmgjson, run_python_file_in_terminal, \ - PMGOneShotThreadRunner - -with open(os.path.join(os.path.dirname(__file__), 'flake8_trans.json'), 'rb') as f: # 打开flake8提示信息的文件翻译。 - flake8_translations = json.load(f) - -show_as_errors = {'E999'} -show_as_warnings = set() -show_as_infos = {'F841', 'F401', 'F403', 'F405', 'E303'} # 分别是变量导入未使用、定义未使用、使用*导入以及无法推断可能由*导入的类型。 +from pmgwidgets import in_unit_test, PMGOneShotThreadRunner, run_python_file_in_terminal, parse_simplified_pmgjson, \ + PMGPanelDialog logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -44,8 +39,18 @@ class PMPythonEditor(PMGPythonEditor): 自定义编辑器控件 """ signal_goto_definition: SignalInstance = Signal(str, int, int) + + SHOW_AS_ERROR = {'E999'} + SHOW_AS_WARNING = set() + SHOW_AS_INFO = {'F841', 'F401', 'F403', 'F405', 'E303'} # 分别是变量导入未使用、定义未使用、使用*导入以及无法推断可能由*导入的类型。 help_runner: 'PMGOneShotThreadRunner' + @cached_property + def flake8_translations(self): + with open(Path(__file__).parent.absolute() / 'flake8_trans.json', 'rb') as f: # 打开flake8提示信息的文件翻译。 + result = json.load(f) + return result + def __init__(self, parent=None): super(PMPythonEditor, self).__init__(parent) # , comment_string='# ') self.browser_id = None @@ -157,7 +162,7 @@ class PMPythonEditor(PMGPythonEditor): if msgid == 'F821' and len(matches) == 1 and matches[0].strip( '\'') in var_names_in_workspace: # 排除工作空间中已经定义的变量! return '' - message = flake8_translations.get(msgid) + message = self.flake8_translations.get(msgid) if message is not None: try: if len(matches) == 1: @@ -203,11 +208,11 @@ class PMPythonEditor(PMGPythonEditor): :param n_type: :return: """ - if n_type == 'E999': + if n_type in self.SHOW_AS_ERROR: return PythonHighlighter.ERROR - elif n_type in show_as_infos: + elif n_type in self.SHOW_AS_INFO: return PythonHighlighter.HINT - elif n_type in show_as_warnings: + elif n_type in self.SHOW_AS_WARNING: return PythonHighlighter.WARNING if n_type.startswith('E'): -- Gitee From c68c6522af8b96c935ebd42a2fd6932906b7a7ec Mon Sep 17 00:00:00 2001 From: wolfpan Date: Mon, 2 Aug 2021 23:34:27 +0800 Subject: [PATCH 019/108] =?UTF-8?q?=E7=A7=BB=E5=8A=A8markdown=5Feditor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/codeeditor/tabwidget.py | 4 ++-- .../markdowneditor.py => widgets/editors/markdown_editor.py} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename packages/code_editor/{codeeditor/markdowneditor.py => widgets/editors/markdown_editor.py} (98%) diff --git a/packages/code_editor/codeeditor/tabwidget.py b/packages/code_editor/codeeditor/tabwidget.py index 3a7f3916..4bdf970e 100644 --- a/packages/code_editor/codeeditor/tabwidget.py +++ b/packages/code_editor/codeeditor/tabwidget.py @@ -48,7 +48,7 @@ if TYPE_CHECKING or in_unit_test(): # from packages.code_editor.codeeditor.baseeditor import PMBaseEditor # from packages.code_editor.codeeditor.cppeditor import PMCPPEditor # from packages.code_editor.codeeditor.cythoneditor import PMCythonEditor - from packages.code_editor.codeeditor.markdowneditor import PMMarkdownEditor + from packages.code_editor.widgets.editors.markdown_editor import PMMarkdownEditor from packages.code_editor.debugger import PMDebugConsoleTabWidget # from packages.code_editor.codeeditor.ui.findinpath import FindInPathWidget @@ -57,7 +57,7 @@ else: from ..widgets.editors.python_editor import PMPythonEditor # from codeeditor.cppeditor import PMCPPEditor # from codeeditor.cythoneditor import PMCythonEditor - from .markdowneditor import PMMarkdownEditor + from ..widgets.editors.markdown_editor import PMMarkdownEditor EDITOR_TYPE = Optional[Union['PMBaseEditor', 'PMCythonEditor', 'PMPythonEditor', 'PMCPPEditor', 'PMMarkdownEditor']] logger = logging.getLogger(__name__) diff --git a/packages/code_editor/codeeditor/markdowneditor.py b/packages/code_editor/widgets/editors/markdown_editor.py similarity index 98% rename from packages/code_editor/codeeditor/markdowneditor.py rename to packages/code_editor/widgets/editors/markdown_editor.py index e49875ef..798d1e6a 100644 --- a/packages/code_editor/codeeditor/markdowneditor.py +++ b/packages/code_editor/widgets/editors/markdown_editor.py @@ -90,4 +90,4 @@ class PMMarkdownEditor(PMAbstractEditor): QMessageBox.warning(self, '未集成功能', '暂不支持运行含有两段及以上代码的Markdown文件!') return '' else: - return code_blocks[0] + return code_blocks[0] \ No newline at end of file -- Gitee From 0eaaa7fdc485834fb0ee5c128502c788abaffead Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 00:36:52 +0800 Subject: [PATCH 020/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../icons/autocomp/class.png => __init__.py} | 0 .../{codeeditor => assets}/flake8_trans.json | 0 .../icons/autocomp/class.png} | 0 .../icons/autocomp/function.png} | 0 .../icons/autocomp/instance.png} | 0 .../icons/autocomp/keyword.png} | 0 .../icons/autocomp/module.png} | 0 .../icons/autocomp/param.png} | 0 .../icons/autocomp/path.png} | 0 .../icons/autocomp/property.png} | 0 .../assets/icons/autocomp/statement.png | 0 .../icons/breakpoint.svg | 0 .../qtpyeditor => assets}/icons/copy.svg | 0 .../qtpyeditor => assets}/icons/debug.svg | 0 .../qtpyeditor => assets}/icons/format.svg | 0 .../qtpyeditor => assets}/icons/help.svg | 0 .../qtpyeditor => assets}/icons/python.svg | 0 .../qtpyeditor => assets}/icons/run.svg | 0 .../qtpyeditor => assets}/icons/save.svg | 0 .../qtpyeditor => assets}/icons/spate.svg | 0 .../qtpyeditor/codeedit/basecodeedit.py | 130 +---------------- packages/code_editor/icons/breakpoint.svg | 1 - packages/code_editor/icons/copy.svg | 1 - packages/code_editor/icons/debug.svg | 1 - packages/code_editor/icons/format.svg | 1 - packages/code_editor/icons/help.svg | 1 - packages/code_editor/icons/python.svg | 1 - packages/code_editor/icons/run.svg | 1 - packages/code_editor/icons/save.svg | 1 - packages/code_editor/icons/spate.svg | 1 - packages/code_editor/scripts/__init__.py | 0 .../flake8_error_msg_translator/__init__.py | 0 .../flake8_error_msg_translator}/translate.py | 2 +- .../translations.txt | 0 packages/code_editor/settings.py | 11 ++ .../auto_complete_dropdown/__init__.py | 0 .../base_auto_complete_dropdown.py | 133 ++++++++++++++++++ packages/code_editor/widgets/base_object.py | 5 + .../widgets/editors/python_editor.py | 3 +- 39 files changed, 159 insertions(+), 134 deletions(-) rename packages/code_editor/{codeeditor/qtpyeditor/icons/autocomp/class.png => __init__.py} (100%) rename packages/code_editor/{codeeditor => assets}/flake8_trans.json (100%) rename packages/code_editor/{codeeditor/qtpyeditor/icons/autocomp/function.png => assets/icons/autocomp/class.png} (100%) rename packages/code_editor/{codeeditor/qtpyeditor/icons/autocomp/instance.png => assets/icons/autocomp/function.png} (100%) rename packages/code_editor/{codeeditor/qtpyeditor/icons/autocomp/keyword.png => assets/icons/autocomp/instance.png} (100%) rename packages/code_editor/{codeeditor/qtpyeditor/icons/autocomp/module.png => assets/icons/autocomp/keyword.png} (100%) rename packages/code_editor/{codeeditor/qtpyeditor/icons/autocomp/param.png => assets/icons/autocomp/module.png} (100%) rename packages/code_editor/{codeeditor/qtpyeditor/icons/autocomp/path.png => assets/icons/autocomp/param.png} (100%) rename packages/code_editor/{codeeditor/qtpyeditor/icons/autocomp/property.png => assets/icons/autocomp/path.png} (100%) rename packages/code_editor/{codeeditor/qtpyeditor/icons/autocomp/statement.png => assets/icons/autocomp/property.png} (100%) create mode 100644 packages/code_editor/assets/icons/autocomp/statement.png rename packages/code_editor/{codeeditor/qtpyeditor => assets}/icons/breakpoint.svg (100%) rename packages/code_editor/{codeeditor/qtpyeditor => assets}/icons/copy.svg (100%) rename packages/code_editor/{codeeditor/qtpyeditor => assets}/icons/debug.svg (100%) rename packages/code_editor/{codeeditor/qtpyeditor => assets}/icons/format.svg (100%) rename packages/code_editor/{codeeditor/qtpyeditor => assets}/icons/help.svg (100%) rename packages/code_editor/{codeeditor/qtpyeditor => assets}/icons/python.svg (100%) rename packages/code_editor/{codeeditor/qtpyeditor => assets}/icons/run.svg (100%) rename packages/code_editor/{codeeditor/qtpyeditor => assets}/icons/save.svg (100%) rename packages/code_editor/{codeeditor/qtpyeditor => assets}/icons/spate.svg (100%) delete mode 100644 packages/code_editor/icons/breakpoint.svg delete mode 100644 packages/code_editor/icons/copy.svg delete mode 100644 packages/code_editor/icons/debug.svg delete mode 100644 packages/code_editor/icons/format.svg delete mode 100644 packages/code_editor/icons/help.svg delete mode 100644 packages/code_editor/icons/python.svg delete mode 100644 packages/code_editor/icons/run.svg delete mode 100644 packages/code_editor/icons/save.svg delete mode 100644 packages/code_editor/icons/spate.svg create mode 100644 packages/code_editor/scripts/__init__.py create mode 100644 packages/code_editor/scripts/flake8_error_msg_translator/__init__.py rename packages/code_editor/{codeeditor/errors_translation => scripts/flake8_error_msg_translator}/translate.py (91%) rename packages/code_editor/{codeeditor/errors_translation => scripts/flake8_error_msg_translator}/translations.txt (100%) create mode 100644 packages/code_editor/settings.py create mode 100644 packages/code_editor/widgets/auto_complete_dropdown/__init__.py create mode 100644 packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py create mode 100644 packages/code_editor/widgets/base_object.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/class.png b/packages/code_editor/__init__.py similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/class.png rename to packages/code_editor/__init__.py diff --git a/packages/code_editor/codeeditor/flake8_trans.json b/packages/code_editor/assets/flake8_trans.json similarity index 100% rename from packages/code_editor/codeeditor/flake8_trans.json rename to packages/code_editor/assets/flake8_trans.json diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/function.png b/packages/code_editor/assets/icons/autocomp/class.png similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/function.png rename to packages/code_editor/assets/icons/autocomp/class.png diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/instance.png b/packages/code_editor/assets/icons/autocomp/function.png similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/instance.png rename to packages/code_editor/assets/icons/autocomp/function.png diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/keyword.png b/packages/code_editor/assets/icons/autocomp/instance.png similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/keyword.png rename to packages/code_editor/assets/icons/autocomp/instance.png diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/module.png b/packages/code_editor/assets/icons/autocomp/keyword.png similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/module.png rename to packages/code_editor/assets/icons/autocomp/keyword.png diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/param.png b/packages/code_editor/assets/icons/autocomp/module.png similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/param.png rename to packages/code_editor/assets/icons/autocomp/module.png diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/path.png b/packages/code_editor/assets/icons/autocomp/param.png similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/path.png rename to packages/code_editor/assets/icons/autocomp/param.png diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/property.png b/packages/code_editor/assets/icons/autocomp/path.png similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/property.png rename to packages/code_editor/assets/icons/autocomp/path.png diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/statement.png b/packages/code_editor/assets/icons/autocomp/property.png similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/autocomp/statement.png rename to packages/code_editor/assets/icons/autocomp/property.png diff --git a/packages/code_editor/assets/icons/autocomp/statement.png b/packages/code_editor/assets/icons/autocomp/statement.png new file mode 100644 index 00000000..e69de29b diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/breakpoint.svg b/packages/code_editor/assets/icons/breakpoint.svg similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/breakpoint.svg rename to packages/code_editor/assets/icons/breakpoint.svg diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/copy.svg b/packages/code_editor/assets/icons/copy.svg similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/copy.svg rename to packages/code_editor/assets/icons/copy.svg diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/debug.svg b/packages/code_editor/assets/icons/debug.svg similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/debug.svg rename to packages/code_editor/assets/icons/debug.svg diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/format.svg b/packages/code_editor/assets/icons/format.svg similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/format.svg rename to packages/code_editor/assets/icons/format.svg diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/help.svg b/packages/code_editor/assets/icons/help.svg similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/help.svg rename to packages/code_editor/assets/icons/help.svg diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/python.svg b/packages/code_editor/assets/icons/python.svg similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/python.svg rename to packages/code_editor/assets/icons/python.svg diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/run.svg b/packages/code_editor/assets/icons/run.svg similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/run.svg rename to packages/code_editor/assets/icons/run.svg diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/save.svg b/packages/code_editor/assets/icons/save.svg similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/save.svg rename to packages/code_editor/assets/icons/save.svg diff --git a/packages/code_editor/codeeditor/qtpyeditor/icons/spate.svg b/packages/code_editor/assets/icons/spate.svg similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/icons/spate.svg rename to packages/code_editor/assets/icons/spate.svg diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py index 220c0591..19a94257 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py @@ -5,7 +5,6 @@ # @File: basecodeedit.py import contextlib import logging -import os import re import time from itertools import groupby @@ -13,14 +12,16 @@ from queue import Queue from typing import List, Tuple, Dict, TYPE_CHECKING, Callable from PySide2.QtCore import Qt, QModelIndex, Signal, QTimer, QUrl, SignalInstance -from PySide2.QtGui import QDropEvent, QPixmap, QDragEnterEvent -from PySide2.QtGui import QTextCursor, QKeyEvent, QMouseEvent, QIcon, QFocusEvent -from PySide2.QtWidgets import QApplication, QWidget, QPlainTextEdit, QTableWidget, QTableWidgetItem, QHeaderView +from PySide2.QtGui import QDropEvent, QDragEnterEvent +from PySide2.QtGui import QTextCursor, QKeyEvent, QMouseEvent, QFocusEvent +from PySide2.QtWidgets import QApplication, QWidget, QPlainTextEdit from packages.code_editor.codeeditor.grammar_analyzer import GrammarAnalyzer from packages.code_editor.codeeditor.qtpyeditor.highlighters.python import PythonHighlighter from packages.code_editor.codeeditor.qtpyeditor.linenumber import QCodeEditor from packages.code_editor.codeeditor.qtpyeditor.syntaxana import getIndent +from packages.code_editor.widgets.auto_complete_dropdown.base_auto_complete_dropdown import \ + BaseAutoCompleteDropdownWidget if TYPE_CHECKING: from packages.code_editor.codeeditor.qtpyeditor import PMGPythonEditor @@ -30,123 +31,6 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) -def create_icons(): - icons = {} - icon_folder = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'icons', 'autocomp') - for icon_file_name in os.listdir(icon_folder): - icon_abso_path = os.path.join(icon_folder, icon_file_name) - icon1 = QIcon() # create_icon(icon_abso_path) - icon1.addPixmap(QPixmap(icon_abso_path), QIcon.Normal, QIcon.Off) - logging.debug(f'loading {icon_file_name}') - icons[icon_file_name[:-4]] = icon1 - - return icons - - -class AutoCompleteDropdownWidget(QTableWidget): - ROLE_NAME = 15 - ROLE_TYPE = 16 - ROLE_COMPLETE = 17 - ROLE_COMPLETION = 18 - - # 为原生PySide2的类型添加类型提示 - parent: 'Callable[[], PMBaseCodeEdit]' - verticalHeader: 'Callable[[], QHeaderView]' - - def __init__(self, parent: 'PMBaseCodeEdit' = None): - super().__init__(parent) - self.icons = create_icons() - self.verticalHeader().setDefaultSectionSize(20) - self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) - self.horizontalHeader().hide() - self.setStyleSheet("AutoCompList{selection-background-color: #999999;}") - self.verticalHeader().setMinimumWidth(20) - # self.horizontalHeader().setMinimumWidth(300) - # self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) - - def hide_autocomp(self): - """隐藏自动补全菜单并且主界面设置焦点。""" - self.hide() - self.parent().setFocus() - - def count(self): - return self.rowCount() - - def keyPressEvent(self, event: QKeyEvent) -> None: - parent = self.parent() - if self.isVisible(): - if event.key() == Qt.Key_Return or event.key() == Qt.Key_Tab: - parent._insert_autocomp() - parent.setFocus() - event.accept() - return - elif event.key() == Qt.Key_Escape: - self.hide() - parent.setFocus() - return - elif event.key() == Qt.Key_Up or event.key() == Qt.Key_Down: - super().keyPressEvent(event) - event.accept() - return - elif event.key() == Qt.Key_Left or event.key() == Qt.Key_Right: - self.hide_autocomp() - elif event.key() == Qt.Key_Control or event.key() == Qt.Key_Alt: # 按下Ctrl键时,不关闭界面,因为可能存在快捷键。 - pass - else: - if (Qt.Key_0 <= event.key() <= Qt.Key_9) and ( - event.modifiers() == Qt.ControlModifier or event.modifiers() == Qt.AltModifier): - index = event.key() - Qt.Key_0 - if 0 <= index < self.count(): - self.setCurrentItem(self.item(index, 0)) - parent._insert_autocomp() - parent.setFocus() - self.hide() - event.accept() - return - self.hide_autocomp() - event.ignore() - return - super().keyPressEvent(event) - event.ignore() - - def set_completions(self, completions: List['CompletionResult']): - """module, class, instance, function, param, path, keyword, property and statement.""" - t0 = time.time() - self.setRowCount(0) - self.setRowCount(len(completions)) - self.setColumnCount(1) - labels = [] - for i, completion in enumerate(completions): - item = QTableWidgetItem(completion.name) - item.setData(AutoCompleteDropdownWidget.ROLE_NAME, completion.name) - - item.setData(AutoCompleteDropdownWidget.ROLE_COMPLETION, completion) - item.setText(completion.name) - if i < 30: # 当条目数太多的时候,不能添加图标,否则速度会非常慢 - icon = self.icons.get(completion.type) - if icon is not None: - item.setIcon(icon) - - self.setItem(i, 0, item) - if 0 <= i <= 9: - labels.append(str(i)) - else: - labels.append('') - self.setVerticalHeaderLabels(labels) - self.show() - self.setFocus() - self.setCurrentItem(self.item(0, 0)) - t1 = time.time() - logger.info(f'completion time:{t1 - t0},completion list length:{len(completions)}') - - def get_complete(self, row: int) -> Tuple[str, str]: - return self.item(row, 0).data(AutoCompleteDropdownWidget.ROLE_COMPLETION).complete, self.item(row, 0).data( - AutoCompleteDropdownWidget.ROLE_COMPLETION).type - - def get_text(self, row: int) -> str: - return self.item(row, 0).text() - - class PMBaseCodeEdit(QCodeEditor): """ 与语言无关的编辑器相关操作应该定义在这里。 @@ -183,7 +67,7 @@ class PMBaseCodeEdit(QCodeEditor): self.textChanged.connect(self.on_text_changed) - self.popup_hint_widget = AutoCompleteDropdownWidget(self) + self.popup_hint_widget = BaseAutoCompleteDropdownWidget(self) self.popup_hint_widget.doubleClicked.connect(self._insert_autocomp) self.popup_hint_widget.hide() self.setContextMenuPolicy(Qt.CustomContextMenu) @@ -675,7 +559,7 @@ class PMBaseCodeEdit(QCodeEditor): if __name__ == '__main__': app = QApplication([]) - e = AutoCompleteDropdownWidget() + e = BaseAutoCompleteDropdownWidget() e.show() diff --git a/packages/code_editor/icons/breakpoint.svg b/packages/code_editor/icons/breakpoint.svg deleted file mode 100644 index dab9197a..00000000 --- a/packages/code_editor/icons/breakpoint.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/code_editor/icons/copy.svg b/packages/code_editor/icons/copy.svg deleted file mode 100644 index aee10173..00000000 --- a/packages/code_editor/icons/copy.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/code_editor/icons/debug.svg b/packages/code_editor/icons/debug.svg deleted file mode 100644 index 2968957d..00000000 --- a/packages/code_editor/icons/debug.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/code_editor/icons/format.svg b/packages/code_editor/icons/format.svg deleted file mode 100644 index 949e870f..00000000 --- a/packages/code_editor/icons/format.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/code_editor/icons/help.svg b/packages/code_editor/icons/help.svg deleted file mode 100644 index a4b4513e..00000000 --- a/packages/code_editor/icons/help.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/code_editor/icons/python.svg b/packages/code_editor/icons/python.svg deleted file mode 100644 index 1e64a0d2..00000000 --- a/packages/code_editor/icons/python.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/code_editor/icons/run.svg b/packages/code_editor/icons/run.svg deleted file mode 100644 index 14358012..00000000 --- a/packages/code_editor/icons/run.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/code_editor/icons/save.svg b/packages/code_editor/icons/save.svg deleted file mode 100644 index e81f6b5b..00000000 --- a/packages/code_editor/icons/save.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/code_editor/icons/spate.svg b/packages/code_editor/icons/spate.svg deleted file mode 100644 index 1b675a07..00000000 --- a/packages/code_editor/icons/spate.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/code_editor/scripts/__init__.py b/packages/code_editor/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/code_editor/scripts/flake8_error_msg_translator/__init__.py b/packages/code_editor/scripts/flake8_error_msg_translator/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/code_editor/codeeditor/errors_translation/translate.py b/packages/code_editor/scripts/flake8_error_msg_translator/translate.py similarity index 91% rename from packages/code_editor/codeeditor/errors_translation/translate.py rename to packages/code_editor/scripts/flake8_error_msg_translator/translate.py index 14771df8..b3270e65 100644 --- a/packages/code_editor/codeeditor/errors_translation/translate.py +++ b/packages/code_editor/scripts/flake8_error_msg_translator/translate.py @@ -11,7 +11,7 @@ from pprint import pprint def main(): data = {} src = Path(__file__).parent.absolute() / 'translations.txt' - dst = Path(__file__).parent.parent.absolute() / 'flake8_trans.json' + dst = Path(__file__).parent.parent.parent.absolute() / 'assets' / 'flake8_trans.json' with open(src, 'r', encoding='utf-8') as file: lines = file.readlines() lines = [line.replace('\n', '') for line in lines] diff --git a/packages/code_editor/codeeditor/errors_translation/translations.txt b/packages/code_editor/scripts/flake8_error_msg_translator/translations.txt similarity index 100% rename from packages/code_editor/codeeditor/errors_translation/translations.txt rename to packages/code_editor/scripts/flake8_error_msg_translator/translations.txt diff --git a/packages/code_editor/settings.py b/packages/code_editor/settings.py new file mode 100644 index 00000000..9c06fe31 --- /dev/null +++ b/packages/code_editor/settings.py @@ -0,0 +1,11 @@ +""" +定义该插件下的一些常用变量及配置项,包括资源文件夹位置等内容。 +""" + +from pathlib import Path + + +class Settings: + base_dir = Path(__file__).parent.absolute() + assets_dir = base_dir / 'assets' + icons_dir = assets_dir / 'icons' diff --git a/packages/code_editor/widgets/auto_complete_dropdown/__init__.py b/packages/code_editor/widgets/auto_complete_dropdown/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py b/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py new file mode 100644 index 00000000..bd029df3 --- /dev/null +++ b/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py @@ -0,0 +1,133 @@ +import logging +import os +import time +from functools import cached_property +from typing import List, Tuple, TYPE_CHECKING, Callable, Dict + +from PySide2.QtCore import Qt +from PySide2.QtGui import QIcon, QPixmap, QKeyEvent +from PySide2.QtWidgets import QTableWidget, QHeaderView, QTableWidgetItem + +from ..base_object import CodeEditorBaseObject + +if TYPE_CHECKING: + from jedi.api.classes import Completion as CompletionResult + from packages.code_editor.codeeditor.qtpyeditor.codeedit import PMBaseCodeEdit + +logger = logging.getLogger('code_editor.base_auto_comp_dropdown') +logger.setLevel(logging.DEBUG) + + +class BaseAutoCompleteDropdownWidget(CodeEditorBaseObject, QTableWidget): + ROLE_NAME = 15 + ROLE_TYPE = 16 + ROLE_COMPLETE = 17 + ROLE_COMPLETION = 18 + + # 为原生PySide2的类型添加类型提示 + parent: 'Callable[[], PMBaseCodeEdit]' + verticalHeader: 'Callable[[], QHeaderView]' + + def __init__(self, parent: 'PMBaseCodeEdit' = None): + super().__init__(parent) + self.verticalHeader().setDefaultSectionSize(20) + self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self.horizontalHeader().hide() + self.setStyleSheet("AutoCompList{selection-background-color: #999999;}") + self.verticalHeader().setMinimumWidth(20) + # self.horizontalHeader().setMinimumWidth(300) + # self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + + @cached_property + def icons(self) -> Dict[str, QIcon]: + icons = {} + icon_folder = self.settings.icons_dir / 'autocomp' + for icon_file_name in os.listdir(icon_folder): + icon_abso_path = icon_folder / icon_file_name + icon1 = QIcon() # create_icon(icon_abso_path) + icon1.addPixmap(QPixmap(str(icon_abso_path)), QIcon.Normal, QIcon.Off) + logger.debug(f'loading icon {icon_file_name}') + icons[icon_file_name[:-4]] = icon1 + return icons + + def hide_autocomp(self): + """隐藏自动补全菜单并且主界面设置焦点。""" + self.hide() + self.parent().setFocus() + + def count(self): + return self.rowCount() + + def keyPressEvent(self, event: QKeyEvent) -> None: + parent = self.parent() + if self.isVisible(): + if event.key() == Qt.Key_Return or event.key() == Qt.Key_Tab: + parent._insert_autocomp() + parent.setFocus() + event.accept() + return + elif event.key() == Qt.Key_Escape: + self.hide() + parent.setFocus() + return + elif event.key() == Qt.Key_Up or event.key() == Qt.Key_Down: + super().keyPressEvent(event) + event.accept() + return + elif event.key() == Qt.Key_Left or event.key() == Qt.Key_Right: + self.hide_autocomp() + elif event.key() == Qt.Key_Control or event.key() == Qt.Key_Alt: # 按下Ctrl键时,不关闭界面,因为可能存在快捷键。 + pass + else: + if (Qt.Key_0 <= event.key() <= Qt.Key_9) and ( + event.modifiers() == Qt.ControlModifier or event.modifiers() == Qt.AltModifier): + index = event.key() - Qt.Key_0 + if 0 <= index < self.count(): + self.setCurrentItem(self.item(index, 0)) + parent._insert_autocomp() + parent.setFocus() + self.hide() + event.accept() + return + self.hide_autocomp() + event.ignore() + return + super().keyPressEvent(event) + event.ignore() + + def set_completions(self, completions: List['CompletionResult']): + """module, class, instance, function, param, path, keyword, property and statement.""" + t0 = time.time() + self.setRowCount(0) + self.setRowCount(len(completions)) + self.setColumnCount(1) + labels = [] + for i, completion in enumerate(completions): + item = QTableWidgetItem(completion.name) + item.setData(BaseAutoCompleteDropdownWidget.ROLE_NAME, completion.name) + + item.setData(BaseAutoCompleteDropdownWidget.ROLE_COMPLETION, completion) + item.setText(completion.name) + if i < 30: # 当条目数太多的时候,不能添加图标,否则速度会非常慢 + icon = self.icons.get(completion.type) + if icon is not None: + item.setIcon(icon) + + self.setItem(i, 0, item) + if 0 <= i <= 9: + labels.append(str(i)) + else: + labels.append('') + self.setVerticalHeaderLabels(labels) + self.show() + self.setFocus() + self.setCurrentItem(self.item(0, 0)) + t1 = time.time() + logger.info(f'completion time:{t1 - t0},completion list length:{len(completions)}') + + def get_complete(self, row: int) -> Tuple[str, str]: + return self.item(row, 0).data(BaseAutoCompleteDropdownWidget.ROLE_COMPLETION).complete, self.item(row, 0).data( + BaseAutoCompleteDropdownWidget.ROLE_COMPLETION).type + + def get_text(self, row: int) -> str: + return self.item(row, 0).text() diff --git a/packages/code_editor/widgets/base_object.py b/packages/code_editor/widgets/base_object.py new file mode 100644 index 00000000..3cdfe901 --- /dev/null +++ b/packages/code_editor/widgets/base_object.py @@ -0,0 +1,5 @@ +from ..settings import Settings + + +class CodeEditorBaseObject: + settings = Settings() diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index 3999a3ea..35aa895a 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -47,7 +47,8 @@ class PMPythonEditor(PMGPythonEditor): @cached_property def flake8_translations(self): - with open(Path(__file__).parent.absolute() / 'flake8_trans.json', 'rb') as f: # 打开flake8提示信息的文件翻译。 + """Flake8的翻译内容""" + with open(Path(__file__).parent.parent.parent.absolute() / 'assets' / 'flake8_trans.json', 'rb') as f: result = json.load(f) return result -- Gitee From 2b5a2258ffc15cda810cebc1d00349f16e59254e Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 00:43:32 +0800 Subject: [PATCH 021/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qtpyeditor/codeedit/__init__.py | 7 ---- .../qtpyeditor/codeeditor/abstracteditor.py | 2 +- .../qtpyeditor/codeeditor/baseeditor.py | 2 +- .../qtpyeditor/codeeditor/pythoneditor.py | 2 +- .../base_auto_complete_dropdown.py | 2 +- .../text_edit/base_text_edit.py} | 38 +++---------------- .../text_edit/python_text_edit.py} | 23 ++--------- .../test_gui/test_python_edit.py | 2 +- 8 files changed, 14 insertions(+), 64 deletions(-) delete mode 100644 packages/code_editor/codeeditor/qtpyeditor/codeedit/__init__.py rename packages/code_editor/{codeeditor/qtpyeditor/codeedit/basecodeedit.py => widgets/text_edit/base_text_edit.py} (95%) rename packages/code_editor/{codeeditor/qtpyeditor/codeedit/pythonedit.py => widgets/text_edit/python_text_edit.py} (89%) diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/__init__.py b/packages/code_editor/codeeditor/qtpyeditor/codeedit/__init__.py deleted file mode 100644 index 09d0e2c6..00000000 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/1/30 11:36 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: __init__.py -from .basecodeedit import PMBaseCodeEdit -from .pythonedit import PMPythonCodeEdit diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py index ba2b1c5d..5e4ea13f 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py @@ -40,7 +40,7 @@ from typing import Dict, Any, TYPE_CHECKING from PySide2.QtWidgets import QWidget, QMessageBox if TYPE_CHECKING: - from packages.code_editor.codeeditor.qtpyeditor.codeedit import PMBaseCodeEdit + from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit logger = logging.getLogger(__name__) diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py index 4ae2c14e..b5bd45ae 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py @@ -49,7 +49,7 @@ from ...qtpyeditor.Utilities import decode from ...qtpyeditor.ui.gotoline import Ui_DialogGoto if TYPE_CHECKING: - from packages.code_editor.codeeditor.qtpyeditor.codeedit import PMBaseCodeEdit + from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit logger = logging.getLogger(__name__) diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/pythoneditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/pythoneditor.py index b9ac422a..7e45c8d8 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/pythoneditor.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/pythoneditor.py @@ -18,7 +18,7 @@ from typing import Dict from PySide2.QtWidgets import QApplication -from packages.code_editor.codeeditor.qtpyeditor.codeedit import PMPythonCodeEdit +from packages.code_editor.widgets.text_edit.python_text_edit import PMPythonCodeEdit from packages.code_editor.codeeditor.qtpyeditor.codeeditor import PMGBaseEditor logger = logging.getLogger('code_editor.pythoneditor') diff --git a/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py b/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py index bd029df3..399489d9 100644 --- a/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py +++ b/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py @@ -12,7 +12,7 @@ from ..base_object import CodeEditorBaseObject if TYPE_CHECKING: from jedi.api.classes import Completion as CompletionResult - from packages.code_editor.codeeditor.qtpyeditor.codeedit import PMBaseCodeEdit + from ..text_edit.base_text_edit import PMBaseCodeEdit logger = logging.getLogger('code_editor.base_auto_comp_dropdown') logger.setLevel(logging.DEBUG) diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py b/packages/code_editor/widgets/text_edit/base_text_edit.py similarity index 95% rename from packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py rename to packages/code_editor/widgets/text_edit/base_text_edit.py index 19a94257..947bc54c 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/basecodeedit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -1,32 +1,22 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/1/18 9:26 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: basecodeedit.py import contextlib import logging import re import time from itertools import groupby from queue import Queue -from typing import List, Tuple, Dict, TYPE_CHECKING, Callable +from typing import Callable, Tuple, Dict, List -from PySide2.QtCore import Qt, QModelIndex, Signal, QTimer, QUrl, SignalInstance -from PySide2.QtGui import QDropEvent, QDragEnterEvent -from PySide2.QtGui import QTextCursor, QKeyEvent, QMouseEvent, QFocusEvent -from PySide2.QtWidgets import QApplication, QWidget, QPlainTextEdit +from PySide2.QtCore import SignalInstance, Signal, Qt, QTimer, QModelIndex, QUrl +from PySide2.QtGui import QFocusEvent, QTextCursor, QMouseEvent, QKeyEvent, QDragEnterEvent, QDropEvent +from PySide2.QtWidgets import QPlainTextEdit, QWidget, QApplication from packages.code_editor.codeeditor.grammar_analyzer import GrammarAnalyzer -from packages.code_editor.codeeditor.qtpyeditor.highlighters.python import PythonHighlighter +from packages.code_editor.codeeditor.qtpyeditor import PythonHighlighter from packages.code_editor.codeeditor.qtpyeditor.linenumber import QCodeEditor from packages.code_editor.codeeditor.qtpyeditor.syntaxana import getIndent from packages.code_editor.widgets.auto_complete_dropdown.base_auto_complete_dropdown import \ BaseAutoCompleteDropdownWidget -if TYPE_CHECKING: - from packages.code_editor.codeeditor.qtpyeditor import PMGPythonEditor - from jedi.api.classes import Completion as CompletionResult - logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -555,21 +545,3 @@ class PMBaseCodeEdit(QCodeEditor): self.signal_file_dropped.emit(file) except Exception as exception: logger.exception(exception) - - -if __name__ == '__main__': - app = QApplication([]) - e = BaseAutoCompleteDropdownWidget() - e.show() - - - class A: - pass - - - c = A() - c.name = 'aaaaa' - c.type = 'module' - c.complete = 'aaa' - e.set_completions([c, c, c, c]) - app.exec_() diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py b/packages/code_editor/widgets/text_edit/python_text_edit.py similarity index 89% rename from packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py rename to packages/code_editor/widgets/text_edit/python_text_edit.py index aaaa5ac7..b08c33d5 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeedit/pythonedit.py +++ b/packages/code_editor/widgets/text_edit/python_text_edit.py @@ -1,22 +1,14 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/1/30 11:36 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: pythonedit.py import logging import time -from typing import List, TYPE_CHECKING +from typing import List from PySide2.QtCore import QPoint, QModelIndex from PySide2.QtGui import QTextCursor, QMouseEvent -from PySide2.QtWidgets import QLabel, QApplication +from PySide2.QtWidgets import QLabel +from packages.code_editor.codeeditor.qtpyeditor import PythonHighlighter from packages.code_editor.codeeditor.qtpyeditor.Utilities import AutoCompThread -from packages.code_editor.codeeditor.qtpyeditor.codeedit import PMBaseCodeEdit -from packages.code_editor.codeeditor.qtpyeditor.highlighters import PythonHighlighter - -if TYPE_CHECKING: - from jedi.api.classes import Completion as CompletionResult +from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -132,10 +124,3 @@ class PMPythonCodeEdit(PMBaseCodeEdit): self.hint_widget.setText(text.strip()) self.hint_widget.setVisible(flag) event.ignore() - - -if __name__ == '__main__': - app = QApplication([]) - e = PMPythonCodeEdit() - e.show() - app.exec_() diff --git a/tests/test_code_editor/test_gui/test_python_edit.py b/tests/test_code_editor/test_gui/test_python_edit.py index 0e84171f..5a0af330 100644 --- a/tests/test_code_editor/test_gui/test_python_edit.py +++ b/tests/test_code_editor/test_gui/test_python_edit.py @@ -1,4 +1,4 @@ -from packages.code_editor.codeeditor.qtpyeditor.codeedit import PMPythonCodeEdit +from packages.code_editor.widgets.text_edit.python_text_edit import PMPythonCodeEdit def test_myapp(qtbot): -- Gitee From 29e481ac4b98d50f9f593e98d5542325146efa5a Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 00:52:16 +0800 Subject: [PATCH 022/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_gui/test_python_code_edit.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/test_code_editor/test_gui/test_python_code_edit.py diff --git a/tests/test_code_editor/test_gui/test_python_code_edit.py b/tests/test_code_editor/test_gui/test_python_code_edit.py new file mode 100644 index 00000000..8ab8ddb9 --- /dev/null +++ b/tests/test_code_editor/test_gui/test_python_code_edit.py @@ -0,0 +1,16 @@ +from packages.code_editor.widgets.text_edit.python_text_edit import PMPythonCodeEdit + + +def test_myapp(qtbot): + # TODO 用例错误 + editor = PMPythonCodeEdit() + qtbot.addWidget(editor) + editor.show() + qtbot.waitForWindowShown(editor) + + editor.setPlainText("abcdefg = 123\n" * 100) + editor.highlighter.registerHighlight(5, 3, 7, editor.highlighter.DEHIGHLIGHT, 'This is an Dehighlight') + editor.highlighter.registerHighlight(3, 1, 7, editor.highlighter.WARNING, 'This is an warning') + editor.highlighter.rehighlight() + + qtbot.wait(1000) -- Gitee From ac5893f741db3840fc2d0a01c6e5210a519e651f Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 00:59:38 +0800 Subject: [PATCH 023/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codeeditor/qtpyeditor/__init__.py | 1 - .../qtpyeditor/codeeditor/__init__.py | 1 - .../qtpyeditor/codeeditor/pythoneditor.py | 54 ------------------- .../widgets/editors/python_editor.py | 48 +++++++++++++---- 4 files changed, 38 insertions(+), 66 deletions(-) delete mode 100644 packages/code_editor/codeeditor/qtpyeditor/codeeditor/pythoneditor.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/__init__.py b/packages/code_editor/codeeditor/qtpyeditor/__init__.py index 77d67299..10c0349b 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/__init__.py +++ b/packages/code_editor/codeeditor/qtpyeditor/__init__.py @@ -5,4 +5,3 @@ # @File: __init__.py from .highlighters import PythonHighlighter -from .codeeditor import PMGPythonEditor diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/__init__.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/__init__.py index 95b43fe1..5e962a8e 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/__init__.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/__init__.py @@ -5,4 +5,3 @@ # @File: __init__.py.py from packages.code_editor.codeeditor.qtpyeditor.codeeditor.baseeditor import PMGBaseEditor -from packages.code_editor.codeeditor.qtpyeditor.codeeditor.pythoneditor import PMGPythonEditor \ No newline at end of file diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/pythoneditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/pythoneditor.py deleted file mode 100644 index 7e45c8d8..00000000 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/pythoneditor.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding:utf-8 -*- -# -# 代码高亮部分来源: -# https://blog.csdn.net/xiaoyangyang20/article/details/68923133 -# -# 窗口交互逻辑为本人原创,转载请注明出处! -# -# 自动补全借用了Jedi库,能够达到不错的体验。 -# 文本编辑器采用了一个QThread作为后台线程,避免补全过程发生卡顿。后台线程会延迟结果返回, -# 返回时如果光标位置未发生变化,则可以显示补全菜单,否则认为文本已经改变,就应当进行下一次补全操作。 - -# @Time: 2021/1/18 8:03 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: editor.py -import logging -from typing import Dict - -from PySide2.QtWidgets import QApplication - -from packages.code_editor.widgets.text_edit.python_text_edit import PMPythonCodeEdit -from packages.code_editor.codeeditor.qtpyeditor.codeeditor import PMGBaseEditor - -logger = logging.getLogger('code_editor.pythoneditor') - - -class PMGPythonEditor(PMGBaseEditor): - def __init__(self, parent: 'PMGPythonEditor' = None): - super().__init__(parent=parent) - edit = PMPythonCodeEdit(self) - self.set_edit(edit) - - def update_settings(self, settings: Dict[str, str]): - pass - - def open(self, path: str): - pass - - def autocomp_stop(self): - logger.info('autocomp stopped') - self.text_edit.autocomp_thread.on_exit() - - -if __name__ == '__main__': - app = QApplication([]) - editor = PMPythonCodeEdit() - editor.show() - - editor.setPlainText("abcdefg = 123\n" * 100) - editor.highlighter.registerHighlight(5, 3, 7, editor.highlighter.DEHIGHLIGHT, 'This is an Dehighlight') - editor.highlighter.registerHighlight(3, 1, 7, editor.highlighter.WARNING, 'This is an warning') - editor.highlighter.rehighlight() - - app.exec_() diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index 35aa895a..4e27d44b 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -11,6 +11,21 @@ Created on 2020/9/7 输入if __name__=='__main__':可以触发运行全部代码的功能。 按照PyMiner控件协议,输入# mkval后面接pmgwidgets控件表达式,可以触发控件编辑按钮。 """ +# -*- coding:utf-8 -*- +# +# 代码高亮部分来源: +# https://blog.csdn.net/xiaoyangyang20/article/details/68923133 +# +# 窗口交互逻辑为本人原创,转载请注明出处! +# +# 自动补全借用了Jedi库,能够达到不错的体验。 +# 文本编辑器采用了一个QThread作为后台线程,避免补全过程发生卡顿。后台线程会延迟结果返回, +# 返回时如果光标位置未发生变化,则可以显示补全菜单,否则认为文本已经改变,就应当进行下一次补全操作。 + +# @Time: 2021/1/18 8:03 +# @Author: Zhanyi Hou +# @Email: 1295752786@qq.com +# @File: editor.py import ast import json @@ -19,14 +34,16 @@ import os import re from functools import cached_property from pathlib import Path -from typing import List, Tuple, Optional +from typing import List, Tuple, Optional, Dict from PySide2.QtCore import SignalInstance, Signal, Qt, QDir from PySide2.QtGui import QKeySequence, QCloseEvent from PySide2.QtWidgets import QAction, QShortcut, QMessageBox from yapf.yapflib import py3compat, yapf_api -from packages.code_editor.codeeditor.qtpyeditor import PMGPythonEditor, PythonHighlighter +from packages.code_editor.codeeditor.qtpyeditor import PythonHighlighter +from packages.code_editor.codeeditor.qtpyeditor.codeeditor import PMGBaseEditor +from packages.code_editor.widgets.text_edit.python_text_edit import PMPythonCodeEdit from pmgwidgets import in_unit_test, PMGOneShotThreadRunner, run_python_file_in_terminal, parse_simplified_pmgjson, \ PMGPanelDialog @@ -34,7 +51,7 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) -class PMPythonEditor(PMGPythonEditor): +class PMPythonEditor(PMGBaseEditor): """ 自定义编辑器控件 """ @@ -45,15 +62,9 @@ class PMPythonEditor(PMGPythonEditor): SHOW_AS_INFO = {'F841', 'F401', 'F403', 'F405', 'E303'} # 分别是变量导入未使用、定义未使用、使用*导入以及无法推断可能由*导入的类型。 help_runner: 'PMGOneShotThreadRunner' - @cached_property - def flake8_translations(self): - """Flake8的翻译内容""" - with open(Path(__file__).parent.parent.parent.absolute() / 'assets' / 'flake8_trans.json', 'rb') as f: - result = json.load(f) - return result - def __init__(self, parent=None): super(PMPythonEditor, self).__init__(parent) # , comment_string='# ') + self.set_edit(PMPythonCodeEdit(self)) self.browser_id = None self._extension_names.append('.py') self._parent = parent @@ -73,6 +84,23 @@ class PMPythonEditor(PMGPythonEditor): # self.completer = PMPythonAutocompleter() # self.completer.signal_autocomp_parsed.connect(self.set_api) + def update_settings(self, settings: Dict[str, str]): + pass + + def open(self, path: str): + pass + + def autocomp_stop(self): + logger.info('autocomp stopped') + self.text_edit.autocomp_thread.on_exit() + + @cached_property + def flake8_translations(self): + """Flake8的翻译内容""" + with open(Path(__file__).parent.parent.parent.absolute() / 'assets' / 'flake8_trans.json', 'rb') as f: + result = json.load(f) + return result + def prepare_actions(self): self._init_actions() self._init_signals() -- Gitee From 2d24b19aa523ef34b394854662037b10a283faf3 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 01:08:24 +0800 Subject: [PATCH 024/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qtpyeditor/codeeditor/abstracteditor.py | 260 ------------------ .../qtpyeditor/codeeditor/baseeditor.py | 2 +- .../widgets/editors/base_editor.py | 30 +- 3 files changed, 23 insertions(+), 269 deletions(-) delete mode 100644 packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py deleted file mode 100644 index 5e4ea13f..00000000 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/abstracteditor.py +++ /dev/null @@ -1,260 +0,0 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/2/6 10:29 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: abstracteditor.py -# !/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -编辑器 -编辑器构造参数: -{'language':'Python', -'ext_name':'.py', -'lexer':PythonLexer, -'builtin_keywords':['int','float',...], -'dynamic_keywords':['func','method',...] -} -常用功能: -1、批量缩进、批量取消缩进(语言无关) -2、整理格式(语言相关,需要对应语言进行重写) -3、在终端执行代码(语言相关:需要已知编译器或者解释器的路径。) -4、更新补全选项(语言无关) -5、复制、粘贴、剪切(语言无关) -6、批量注释、批量取消注释(未实现。注意,这部分功能比较复杂,需要该语言的注释符号) -7、查找、替换等(语言无关) -8、保存、打开(需要已知扩展名) -Created on 2020/9/7 -@author: Irony -@email: 892768447@qq.com -@file: editor -@description: Code Editor -""" - -__version__ = '0.1' - -import logging -import os -from typing import Dict, Any, TYPE_CHECKING - -from PySide2.QtWidgets import QWidget, QMessageBox - -if TYPE_CHECKING: - from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit - -logger = logging.getLogger(__name__) - - -class PMAbstractEditor(QWidget): - textEdit: 'PMBaseCodeEdit' - - def __init__(self, parent): - super().__init__(parent) - self.last_save_time = 0 - self.extension_lib = None - - def set_shortcut(self): - pass - - def update_settings(self, settings: Dict[str, Any]): - pass - - def slot_textedit_focusedin(self, e): - pass - - def goto_line(self, line_no: int): - """ - 跳转到对应行列 - :return: - """ - pass - - def _init_signals(self) -> None: - """ - 初始化信号绑定 - - :return: None - """ - pass - - def _init_actions(self) -> None: - """ - 初始化额外菜单项 - - :return: - """ - pass - - def autocomp(self): - pass - - def get_word_under_cursor(self): - pass - - def set_text(self, text: str) -> None: - """ - 设置编辑器内容 - - :type text: str - :param text: 文本内容 - :return: None - """ - pass - - def set_modified(self, modified: bool) -> None: - """ - 设置内容是否被修改 - - :param modified: 是否被修改 True or False - :type: bool - :return: None - """ - pass - - def load_file(self, path: str) -> None: - """ - 加载文件 - - :param path: 文件路径 - :type path: str - :return: None - """ - pass - - def set_encoding(self, encoding: str): - """ - 设置文本编码,仅支持 ASCII 和 UTF-8 - - :param encoding: ascii or gbk or utf-8 - :type: str - :return: - """ - - def slot_about_close(self, save_all=False) -> QMessageBox.StandardButton: - """ - 是否需要关闭以及保存 - - :param save_all: 当整个窗口关闭时增加是否全部关闭 - :return:QMessageBox.StandardButton - """ - if not self.modified(): - return QMessageBox.Discard - buttons = QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel - if save_all: - buttons |= QMessageBox.SaveAll # 保存全部 - buttons |= QMessageBox.NoToAll # 放弃所有 - ret = QMessageBox.question(self, self.tr('Save'), self.tr('Save file "{0}"?').format(self.filename()), buttons, - QMessageBox.Save) - if ret == QMessageBox.Save or ret == QMessageBox.SaveAll: - if not self.save(): - return QMessageBox.Cancel - return ret - - def slot_modification_changed(self, modified: bool) -> None: - """ - 内容被修改槽函数 - - :param modified: 是否被修改 - :type modified: bool - :return: - """ - title = self.windowTitle() - if modified: - if not title.startswith('*'): - self.setWindowTitle('*' + title) - else: - if title.startswith('*'): - self.setWindowTitle(title[1:]) - - def slot_save(self) -> None: - """ - 保存时触发的事件。 - :return: - """ - - def default_save_path(self) -> str: - """ - 获取当前默认存储为的路径 - Default directory. - :return: - """ - return '' - - def slot_text_changed(self) -> None: - pass - - def save(self) -> bool: - """ - 保存文件时调用的方法 - :return: - """ - - def modified(self) -> bool: - """ - 返回内容是否被修改 - - :rtype: bool - :return: 返回内容是否被修改 - """ - return self.textEdit.is_modified() - - def filename(self) -> str: - """ - 返回当前文件名 - - :rtype: str - :return: 返回当前文件名 - """ - return os.path.basename(self._path) - - def path(self) -> str: - """ - 返回当前文件路径 - - :rtype: str - :return: 返回当前文件路径 - """ - return self._path - - def set_path(self, path: str) -> None: - """ - 设置文件路径 - - :param path: 设置文件路径 - :type path: str - :return: None - """ - self._path = path - - title = self.windowTitle() - new_title = os.path.basename(self._path) - if title.startswith('*'): - self.setWindowTitle('*' + new_title) - else: - self.setWindowTitle(new_title) - - def text(self, selected: bool = False) -> str: - """ - 返回编辑器选中或者全部内容 - Args: - selected: - - Returns: - - """ - raise NotImplementedError('该编辑器不支持获取代码文本') - - def slot_file_modified_externally(self): - return - - def change_color_scheme(self, color_scheme_name: str): - pass - - def slot_code_format(self): - pass - - def slot_code_run(self): - pass - - def slot_code_sel_run(self): - pass diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py index b5bd45ae..0b747b63 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py @@ -44,9 +44,9 @@ from PySide2.QtWidgets import QWidget, QMessageBox, QFileDialog, QAction, QShort QHBoxLayout, QApplication, QLabel from pmgwidgets.widgets.composited import PMGPanel -from ..codeeditor.abstracteditor import PMAbstractEditor from ...qtpyeditor.Utilities import decode from ...qtpyeditor.ui.gotoline import Ui_DialogGoto +from ....widgets.editors.base_editor import PMAbstractEditor if TYPE_CHECKING: from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index cafe8b85..328ecc7c 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -36,6 +36,8 @@ logger = logging.getLogger(__name__) class PMAbstractEditor(QWidget): + textEdit: 'PMBaseCodeEdit' + def __init__(self, parent): super().__init__(parent) self.last_save_time = 0 @@ -60,14 +62,6 @@ class PMAbstractEditor(QWidget): """ pass - def _init_lexer(self, lexer: 'QsciLexer') -> None: - """ - 初始化语法解析器 - - :return: None - """ - pass - def _init_signals(self) -> None: """ 初始化信号绑定 @@ -171,6 +165,14 @@ class PMAbstractEditor(QWidget): :return: """ + def default_save_path(self) -> str: + """ + 获取当前默认存储为的路径 + Default directory. + :return: + """ + return '' + def slot_text_changed(self) -> None: pass @@ -237,3 +239,15 @@ class PMAbstractEditor(QWidget): def slot_file_modified_externally(self): return + + def change_color_scheme(self, color_scheme_name: str): + pass + + def slot_code_format(self): + pass + + def slot_code_run(self): + pass + + def slot_code_sel_run(self): + pass -- Gitee From f733d38f7d7363d1a2f04e5ef4d3554ed8ec2eed Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 01:09:49 +0800 Subject: [PATCH 025/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qtpyeditor/codeeditor/baseeditor.py | 50 +---------------- .../code_editor/widgets/dialogs/__init__.py | 0 .../widgets/dialogs/goto_line_dialog.py | 53 +++++++++++++++++++ 3 files changed, 54 insertions(+), 49 deletions(-) create mode 100644 packages/code_editor/widgets/dialogs/__init__.py create mode 100644 packages/code_editor/widgets/dialogs/goto_line_dialog.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py index 0b747b63..df81bca5 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py @@ -45,7 +45,7 @@ from PySide2.QtWidgets import QWidget, QMessageBox, QFileDialog, QAction, QShort from pmgwidgets.widgets.composited import PMGPanel from ...qtpyeditor.Utilities import decode -from ...qtpyeditor.ui.gotoline import Ui_DialogGoto +from ....widgets.dialogs.goto_line_dialog import GotoLineDialog from ....widgets.editors.base_editor import PMAbstractEditor if TYPE_CHECKING: @@ -54,54 +54,6 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) -class GotoLineDialog(QDialog, Ui_DialogGoto): - tr: Callable[[str], str] - - def __init__(self, parent=None): - super(GotoLineDialog, self).__init__(parent) - self.current_line = -1 - self.max_row_count = 0 - self.setupUi(self) - self.buttonBox.accepted.connect(self.run_goto) - self.buttonBox.rejected.connect(self.reject) - - def set_max_row_count(self, length: int): - """ - 设置最大可跳转的行数 - :param length: - :return: - """ - self.max_row_count = length - - def set_current_line(self, line: int): - """ - line:从0开始 - :param line: - :return: - """ - self.current_line = line - self.lineEdit.setText(str(line + 1)) - - def run_goto(self): - """ - 跳转到行 - :return: - """ - text = self.lineEdit.text() - if not text.isdecimal(): - QMessageBox.warning(self, self.tr('Input Value Error'), self.tr('Cannot convert \'%s\' to integer.') % text) - return - line = int(text) - if not 0 <= line < self.max_row_count: - QMessageBox.warning(self, self.tr('Input Value Error'), - self.tr('Line Number {line} out of range!').format(line=line)) - return - self.accept() - - def get_line(self) -> int: - return int(self.lineEdit.text()) - - class FindDialog(QDialog): tr: Callable[[str], str] diff --git a/packages/code_editor/widgets/dialogs/__init__.py b/packages/code_editor/widgets/dialogs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/code_editor/widgets/dialogs/goto_line_dialog.py b/packages/code_editor/widgets/dialogs/goto_line_dialog.py new file mode 100644 index 00000000..4c30b3fc --- /dev/null +++ b/packages/code_editor/widgets/dialogs/goto_line_dialog.py @@ -0,0 +1,53 @@ +from typing import Callable + +from PySide2.QtWidgets import QDialog, QMessageBox + +from packages.code_editor.codeeditor.qtpyeditor.ui.gotoline import Ui_DialogGoto + + +class GotoLineDialog(QDialog, Ui_DialogGoto): + tr: Callable[[str], str] + + def __init__(self, parent=None): + super(GotoLineDialog, self).__init__(parent) + self.current_line = -1 + self.max_row_count = 0 + self.setupUi(self) + self.buttonBox.accepted.connect(self.run_goto) + self.buttonBox.rejected.connect(self.reject) + + def set_max_row_count(self, length: int): + """ + 设置最大可跳转的行数 + :param length: + :return: + """ + self.max_row_count = length + + def set_current_line(self, line: int): + """ + line:从0开始 + :param line: + :return: + """ + self.current_line = line + self.lineEdit.setText(str(line + 1)) + + def run_goto(self): + """ + 跳转到行 + :return: + """ + text = self.lineEdit.text() + if not text.isdecimal(): + QMessageBox.warning(self, self.tr('Input Value Error'), self.tr('Cannot convert \'%s\' to integer.') % text) + return + line = int(text) + if not 0 <= line < self.max_row_count: + QMessageBox.warning(self, self.tr('Input Value Error'), + self.tr('Line Number {line} out of range!').format(line=line)) + return + self.accept() + + def get_line(self) -> int: + return int(self.lineEdit.text()) -- Gitee From f16bd13545b3ff8fc5fb1ff28dd766af51637480 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 01:12:01 +0800 Subject: [PATCH 026/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qtpyeditor/codeeditor/baseeditor.py | 97 +------------------ .../widgets/dialogs/find_dialog.py | 96 ++++++++++++++++++ 2 files changed, 101 insertions(+), 92 deletions(-) create mode 100644 packages/code_editor/widgets/dialogs/find_dialog.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py index df81bca5..a7ace537 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py +++ b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py @@ -36,15 +36,15 @@ __version__ = '0.1' import logging import os import time -from typing import TYPE_CHECKING, List, Dict, Any, Callable +from typing import TYPE_CHECKING, List, Dict, Any from PySide2.QtCore import QDir, QCoreApplication, Qt, QPoint, Signal, QTranslator, QLocale, SignalInstance -from PySide2.QtGui import QIcon, QKeySequence, QTextDocument, QTextCursor, QCloseEvent -from PySide2.QtWidgets import QWidget, QMessageBox, QFileDialog, QAction, QShortcut, QDialog, QVBoxLayout, QPushButton, \ - QHBoxLayout, QApplication, QLabel +from PySide2.QtGui import QIcon, QKeySequence, QTextDocument, QTextCursor +from PySide2.QtWidgets import QWidget, QMessageBox, QFileDialog, QAction, QShortcut, QVBoxLayout, QHBoxLayout, \ + QApplication, QLabel -from pmgwidgets.widgets.composited import PMGPanel from ...qtpyeditor.Utilities import decode +from ....widgets.dialogs.find_dialog import FindDialog from ....widgets.dialogs.goto_line_dialog import GotoLineDialog from ....widgets.editors.base_editor import PMAbstractEditor @@ -54,93 +54,6 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) -class FindDialog(QDialog): - tr: Callable[[str], str] - - def __init__(self, parent=None, text_editor: 'PMGBaseEditor' = None): - super(FindDialog, self).__init__(parent) - self.text_editor = text_editor - self.text_edit: 'PMBaseCodeEdit' = text_editor.text_edit - views = [('line_ctrl', 'text_to_find', self.tr('Text to Find'), ''), - ('line_ctrl', 'text_to_replace', self.tr('Text to Replace'), ''), - ('check_ctrl', 'wrap', self.tr('Wrap'), True), - ('check_ctrl', 'regex', self.tr('Regex'), False), - ('check_ctrl', 'case_sensitive', self.tr('Case Sensitive'), True), - ('check_ctrl', 'whole_word', self.tr('Whole Word'), True), - ] - self.settings_panel = PMGPanel(parent=self, views=views) - self.setLayout(QVBoxLayout()) - self.layout().addWidget(self.settings_panel) - self.button_up = QPushButton(self.tr('Up')) - self.button_down = QPushButton(self.tr('Down')) - self.button_replace = QPushButton(self.tr('Replace')) - self.button_replace_all = QPushButton(self.tr('Replace All')) - - self.button_up.clicked.connect(self.search_up) - self.button_down.clicked.connect(self.search_down) - self.button_replace.clicked.connect(self.replace_current) - self.button_replace_all.clicked.connect(self.replace_all) - - self.button_bar = QHBoxLayout() - self.button_bar.addWidget(self.button_up) - self.button_bar.addWidget(self.button_down) - self.button_bar.addWidget(self.button_replace) - self.button_bar.addWidget(self.button_replace_all) - self.button_bar.setContentsMargins(0, 0, 0, 0) - self.layout().addLayout(self.button_bar) - - def search_up(self): - settings = self.settings_panel.get_value() - self.text_editor.search_word(forward=True, **settings) - - pass - - def search_down(self): - """ - 反方向查找。注意,简单的设置qsci的forward=False是不够的,还需要对位置进行处理。 - 这似乎是QSciScintilla的bug. - """ - settings = self.settings_panel.get_value() - self.text_editor.search_word(forward=False, **settings) - - pass - - def replace_current(self): - text: str = self.settings_panel.widgets_dic['text_to_replace'].get_value() - if self.text_edit.is_text_selected: - self.text_edit.replace_selection(text) - self.search_up() - - def replace_all(self): - settings = self.settings_panel.get_value() - text_to_replace = self.settings_panel.widgets_dic['text_to_replace'].get_value() - while 1: - b = self.text_editor.search_word(forward=True, **settings) - if b: - self.text_edit.replace_selection(text_to_replace) - - else: - break - - def show(self) -> None: - super().show() - if self.text_edit.get_selected_text() != '': - self.settings_panel.set_value({'text_to_find': self.text_edit.get_selected_text()}) - - def show_replace_actions(self, replace_on: bool = False): - self.settings_panel.get_ctrl('text_to_replace').setVisible(replace_on) - self.button_replace.setVisible(replace_on) - self.button_replace_all.setVisible(replace_on) - - def closeEvent(self, a0: 'QCloseEvent') -> None: - pass - # sel = self.text_edit.getCursorPosition() - # self.text_edit.setSelection(sel[0], sel[1], sel[0], sel[1]) - - def close(self) -> bool: - return False - - class PMGBaseEditor(PMAbstractEditor): signal_focused_in: SignalInstance = None signal_new_requested: SignalInstance = Signal(str, int) # 文件路径;文件的打开模式(目前都是0) diff --git a/packages/code_editor/widgets/dialogs/find_dialog.py b/packages/code_editor/widgets/dialogs/find_dialog.py new file mode 100644 index 00000000..fca1238e --- /dev/null +++ b/packages/code_editor/widgets/dialogs/find_dialog.py @@ -0,0 +1,96 @@ +from typing import Callable, TYPE_CHECKING + +from PySide2.QtWidgets import QDialog, QVBoxLayout, QPushButton, QHBoxLayout + +from pmgwidgets import PMGPanel + +if TYPE_CHECKING: + from packages.code_editor.codeeditor.qtpyeditor.codeeditor import PMGBaseEditor + from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit + + +class FindDialog(QDialog): + tr: Callable[[str], str] + + def __init__(self, parent=None, text_editor: 'PMGBaseEditor' = None): + super(FindDialog, self).__init__(parent) + self.text_editor = text_editor + self.text_edit: 'PMBaseCodeEdit' = text_editor.text_edit + views = [('line_ctrl', 'text_to_find', self.tr('Text to Find'), ''), + ('line_ctrl', 'text_to_replace', self.tr('Text to Replace'), ''), + ('check_ctrl', 'wrap', self.tr('Wrap'), True), + ('check_ctrl', 'regex', self.tr('Regex'), False), + ('check_ctrl', 'case_sensitive', self.tr('Case Sensitive'), True), + ('check_ctrl', 'whole_word', self.tr('Whole Word'), True), + ] + self.settings_panel = PMGPanel(parent=self, views=views) + self.setLayout(QVBoxLayout()) + self.layout().addWidget(self.settings_panel) + self.button_up = QPushButton(self.tr('Up')) + self.button_down = QPushButton(self.tr('Down')) + self.button_replace = QPushButton(self.tr('Replace')) + self.button_replace_all = QPushButton(self.tr('Replace All')) + + self.button_up.clicked.connect(self.search_up) + self.button_down.clicked.connect(self.search_down) + self.button_replace.clicked.connect(self.replace_current) + self.button_replace_all.clicked.connect(self.replace_all) + + self.button_bar = QHBoxLayout() + self.button_bar.addWidget(self.button_up) + self.button_bar.addWidget(self.button_down) + self.button_bar.addWidget(self.button_replace) + self.button_bar.addWidget(self.button_replace_all) + self.button_bar.setContentsMargins(0, 0, 0, 0) + self.layout().addLayout(self.button_bar) + + def search_up(self): + settings = self.settings_panel.get_value() + self.text_editor.search_word(forward=True, **settings) + + pass + + def search_down(self): + """ + 反方向查找。注意,简单的设置qsci的forward=False是不够的,还需要对位置进行处理。 + 这似乎是QSciScintilla的bug. + """ + settings = self.settings_panel.get_value() + self.text_editor.search_word(forward=False, **settings) + + pass + + def replace_current(self): + text: str = self.settings_panel.widgets_dic['text_to_replace'].get_value() + if self.text_edit.is_text_selected: + self.text_edit.replace_selection(text) + self.search_up() + + def replace_all(self): + settings = self.settings_panel.get_value() + text_to_replace = self.settings_panel.widgets_dic['text_to_replace'].get_value() + while 1: + b = self.text_editor.search_word(forward=True, **settings) + if b: + self.text_edit.replace_selection(text_to_replace) + + else: + break + + def show(self) -> None: + super().show() + if self.text_edit.get_selected_text() != '': + self.settings_panel.set_value({'text_to_find': self.text_edit.get_selected_text()}) + + def show_replace_actions(self, replace_on: bool = False): + self.settings_panel.get_ctrl('text_to_replace').setVisible(replace_on) + self.button_replace.setVisible(replace_on) + self.button_replace_all.setVisible(replace_on) + + def closeEvent(self, a0: 'QCloseEvent') -> None: + pass + # sel = self.text_edit.getCursorPosition() + # self.text_edit.setSelection(sel[0], sel[1], sel[0], sel[1]) + + def close(self) -> bool: + return False -- Gitee From cac11d684546cc1a637e5e3d35da88fcbeb0e24b Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 01:25:48 +0800 Subject: [PATCH 027/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qtpyeditor/codeeditor/__init__.py | 7 - .../qtpyeditor/codeeditor/baseeditor.py | 588 ------------------ .../widgets/dialogs/find_dialog.py | 2 +- .../widgets/editors/base_editor.py | 388 +++++++++++- .../widgets/editors/python_editor.py | 4 +- 5 files changed, 371 insertions(+), 618 deletions(-) delete mode 100644 packages/code_editor/codeeditor/qtpyeditor/codeeditor/__init__.py delete mode 100644 packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/__init__.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/__init__.py deleted file mode 100644 index 5e962a8e..00000000 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/1/30 18:11 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: __init__.py.py - -from packages.code_editor.codeeditor.qtpyeditor.codeeditor.baseeditor import PMGBaseEditor diff --git a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py b/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py deleted file mode 100644 index a7ace537..00000000 --- a/packages/code_editor/codeeditor/qtpyeditor/codeeditor/baseeditor.py +++ /dev/null @@ -1,588 +0,0 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/1/18 9:23 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: baseeditor.py -# !/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -编辑器 -编辑器构造参数: -{'language':'Python', -'ext_name':'.py', -'lexer':PythonLexer, -'builtin_keywords':['int','float',...], -'dynamic_keywords':['func','method',...] -} -常用功能: -1、批量缩进、批量取消缩进(语言无关) -2、整理格式(语言相关,需要对应语言进行重写) -3、在终端执行代码(语言相关:需要已知编译器或者解释器的路径。) -4、更新补全选项(语言无关) -5、复制、粘贴、剪切(语言无关) -6、批量注释、批量取消注释(未实现。注意,这部分功能比较复杂,需要该语言的注释符号) -7、查找、替换等(语言无关) -8、保存、打开(需要已知扩展名) -Created on 2020/9/7 -@author: Irony -@email: 892768447@qq.com -@file: editor -@description: Code Editor -""" - -__version__ = '0.1' - -import logging -import os -import time -from typing import TYPE_CHECKING, List, Dict, Any - -from PySide2.QtCore import QDir, QCoreApplication, Qt, QPoint, Signal, QTranslator, QLocale, SignalInstance -from PySide2.QtGui import QIcon, QKeySequence, QTextDocument, QTextCursor -from PySide2.QtWidgets import QWidget, QMessageBox, QFileDialog, QAction, QShortcut, QVBoxLayout, QHBoxLayout, \ - QApplication, QLabel - -from ...qtpyeditor.Utilities import decode -from ....widgets.dialogs.find_dialog import FindDialog -from ....widgets.dialogs.goto_line_dialog import GotoLineDialog -from ....widgets.editors.base_editor import PMAbstractEditor - -if TYPE_CHECKING: - from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit - -logger = logging.getLogger(__name__) - - -class PMGBaseEditor(PMAbstractEditor): - signal_focused_in: SignalInstance = None - signal_new_requested: SignalInstance = Signal(str, int) # 文件路径;文件的打开模式(目前都是0) - signal_save_requested: SignalInstance = Signal() - signal_request_find_in_path: SignalInstance = Signal(str) - text_edit: 'PMBaseCodeEdit' - find_dialog: 'FindDialog' - goto_line_dialog: 'GotoLineDialog' - - def __init__(self, parent): - app = QApplication.instance() - trans_editor_tb = QTranslator() - app.trans_editor_tb = trans_editor_tb - trans_editor_tb.load( - os.path.join(os.path.dirname(__file__), 'translations', 'qt_%s.qm' % QLocale.system().name())) - app.installTranslator(trans_editor_tb) - - super().__init__(parent) - self.last_save_time = 0 - self._path = '' - self._modified = False - self._extension_names: List[str] = [] # 该编辑器支持的文件名 - self._indicator_dict: Dict[str, str] = {} - - self.line_number_area = QWidget() - self.line_number_area.setMaximumHeight(60) - self.line_number_area.setMinimumHeight(20) - self.status_label = QLabel() - line_number_area_layout = QHBoxLayout() - line_number_area_layout.addWidget(self.status_label) - line_number_area_layout.setContentsMargins(0, 0, 0, 0) - self.line_number_area.setLayout(line_number_area_layout) - self.modified_status_label = QLabel() - self.modified_status_label.setText('') - line_number_area_layout.addWidget(self.modified_status_label) - - def set_edit(self, edit: 'PMBaseCodeEdit'): - self.text_edit = edit - 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.find_dialog = FindDialog(parent=self, text_editor=self) - self.goto_line_dialog = GotoLineDialog(parent=self) - layout = QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - self.setLayout(layout) - self.layout().addWidget(self.text_edit) - self.layout().addWidget(self.status_label) - - def layout(self) -> QVBoxLayout: - return super(PMGBaseEditor, self).layout() - - def show_cursor_pos(self): - row = self.text_edit.textCursor().block().blockNumber() - col = self.text_edit.textCursor().columnNumber() - self.status_label.setText(f'行:{row + 1},列:{col + 1}') - - def set_shortcut(self): - pass - - def update_settings(self, settings: Dict[str, Any]): - pass - - def slot_text_edit_focusedin(self, e): - pass - - def goto_line(self, line_no: int): - """跳转到对应行列""" - doc: QTextDocument = self.text_edit.document() - lines = doc.blockCount() - assert 1 <= line_no <= lines - pos = doc.findBlockByLineNumber(line_no - 1).position() - text_cursor: QTextCursor = self.text_edit.textCursor() - text_cursor.setPosition(pos) - self.text_edit.setTextCursor(text_cursor) - - def search_word(self, text_to_find: str, wrap: bool, regex: bool, case_sensitive: bool, whole_word: bool, - forward: bool, index=-1, line=-1, **kwargs) -> bool: - find_flags = 0 - # if wrap: - # find_flags = find_flags | QTextDocument.FindFlags - if case_sensitive: - find_flags = find_flags | QTextDocument.FindCaseSensitively - if whole_word: - find_flags = find_flags | QTextDocument.FindWholeWords - if not forward: - find_flags = find_flags | QTextDocument.FindBackward - if find_flags == 0: - find_flags = QTextDocument.FindFlags - # print(find_flags) - ret = self.text_edit.find(text_to_find, options=find_flags) - cursor_pos = self.text_edit.get_cursor_position() - if wrap and (not ret): - cursor = self.text_edit.textCursor() - cursor.clearSelection() - if forward: - cursor.movePosition(QTextCursor.Start) - print('cursor to start!') - - else: - cursor.movePosition(QTextCursor.End) - self.text_edit.setTextCursor(cursor) - ret = self.text_edit.find(text_to_find, options=find_flags) - # print(ret,cursor) - if not ret: - cursor = self.text_edit.textCursor() - cursor.setPosition(cursor_pos) - self.text_edit.setTextCursor(cursor) - return ret - - def autocomp(self): - pass - - def get_word_under_cursor(self): - pass - - def set_text(self, text: str) -> None: - """ - 设置编辑器内容 - - :type text: str - :param text: 文本内容 - :return: None - """ - self.text_edit.setPlainText(text) - - def set_modified(self, modified: bool) -> None: - """ - 设置内容是否被修改 - - :param modified: 是否被修改 True or False - :type: bool - :return: None - """ - self._modified = modified - self.text_edit.modified = modified - self.slot_modification_changed(modified) - - def load_file(self, path: str) -> None: - """ - 加载文件 - - :param path: 文件路径 - :type path: str - :return: None - """ - self._path = '' - try: - # 读取文件内容并加载 - with open(path, 'rb') as fp: - text = fp.read() - text, coding = decode(text) - self.set_encoding(coding) - self.set_text(text) - self.set_modified(False) - self.text_edit.set_eol_status() - except Exception as e: - import traceback - traceback.print_exc() - logger.warning(str(e)) - - self._path = path - self.setWindowTitle(self.filename()) - self.last_save_time = time.time() - self.set_modified(False) - - def set_encoding(self, encoding: str): - """ - 设置文本编码,仅支持 ASCII 和 UTF-8 - - :param encoding: ascii or gbk or utf-8 - :type: str - :return: - """ - - def slot_about_close(self, save_all=False) -> QMessageBox.StandardButton: - """ - 是否需要关闭以及保存 - - :param save_all: 当整个窗口关闭时增加是否全部关闭 - :return:QMessageBox.StandardButton - """ - # QCoreApplication.translate = QCoreApplication.translate - if not self._modified: - return QMessageBox.Discard - - buttons = QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel - if save_all: - buttons |= QMessageBox.SaveAll # 保存全部 - buttons |= QMessageBox.NoToAll # 放弃所有 - ret = QMessageBox.question(self, QCoreApplication.translate('PMGBaseEditor', 'Save'), - QCoreApplication.translate('PMGBaseEditor', 'Save file "{0}"?').format( - self.filename()), buttons, - QMessageBox.Save) - if ret == QMessageBox.Save or ret == QMessageBox.SaveAll: - if not self.save(): - return QMessageBox.Cancel - return ret - - def slot_modification_changed(self, modified: bool) -> None: - """ - 内容被修改槽函数 - - :param modified: 是否被修改 - :type modified: bool - :return: - """ - title = self.windowTitle() - if modified: - if not title.startswith('*'): - self.setWindowTitle('*' + title) - else: - if title.startswith('*'): - self.setWindowTitle(title[1:]) - - def slot_save(self) -> None: - """ - 保存时触发的事件。 - :return: - """ - self.save() - self.set_modified(False) - - def slot_text_changed(self) -> None: - pass - - def is_temp_file(self) -> bool: - """ - 返回文件是否为临时文件 - :return: - """ - tmp_path = QDir.tempPath().replace('\\', '/') - if self._path.replace('\\', '/').startswith(tmp_path): - return True - else: - return False - - def save(self) -> bool: - """ - The method call when saving files. - If the file is not saved yet, the qfiledialog will open save dialog at default_dir,generated by get_default_dir method. - :return: - """ - QCoreApplication.translate = QCoreApplication.translate - path = self._path.replace_selection(os.sep, '/') - default_dir = self.default_save_path() - if path.startswith(QDir.tempPath().replace(os.sep, '/')): - assert os.path.exists(default_dir) or default_dir == '' - # 弹出对话框要求选择真实路径保存 - path, ext = QFileDialog.getSaveFileName(self, QCoreApplication.translate("PMGBaseEditor", 'Save file'), - default_dir, - filter='*.py') - - if not path: - return False - if not path.endswith('.py'): - path += '.py' - self._path = path - try: - with open(self._path, 'wb') as fp: - fp.write(self.text().encode('utf-8', errors='ignore')) - - self.setWindowTitle(os.path.basename(path)) - self.set_modified(False) - self.last_save_time = time.time() - return True - except Exception as e: - # 保存失败 - logger.warning(str(e)) - return False - - def modified(self) -> bool: - """ - 返回内容是否被修改 - - :rtype: bool - :return: 返回内容是否被修改 - """ - return self._modified - - def filename(self) -> str: - """ - 返回当前文件名 - - :rtype: str - :return: 返回当前文件名 - """ - return os.path.basename(self._path) - - def path(self) -> str: - """ - 返回当前文件路径 - - :rtype: str - :return: 返回当前文件路径 - """ - return self._path - - def set_path(self, path: str) -> None: - """ - 设置文件路径 - - :param path: 设置文件路径 - :type path: str - :return: None - """ - self._path = path - - title = self.windowTitle() - new_title = os.path.basename(self._path) - if title.startswith('*'): - self.setWindowTitle('*' + new_title) - else: - self.setWindowTitle(new_title) - - def text(self, selected: bool = False) -> str: - """返回编辑器选中或者全部内容。 - - Args: - selected: True则返回选中的内容,False则返回全部内容 - - Returns: - str, 选中的或全部的代码 - """ - if selected: - return self.text_edit.get_selected_text() - else: - return self.text_edit.toPlainText() - - def slot_file_modified_externally(self): - return - - def _init_actions(self) -> None: - """ - 初始化额外菜单项 - - :return: - """ - # QCoreApplication.translate = QCoreApplication.translate - self.icon_path = os.path.dirname(os.path.dirname(__file__)) # 图标文件路径 - self._action_format = QAction(QIcon(os.path.join(self.icon_path, 'icons/format.svg')), - QCoreApplication.translate("PMGBaseEditor", 'Format Code'), - self.text_edit) - self._action_run_code = QAction(QIcon(os.path.join(self.icon_path, 'icons/run.svg')), - QCoreApplication.translate("PMGBaseEditor", 'Run Code'), - self.text_edit) - self._action_run_sel_code = QAction(QIcon(os.path.join(self.icon_path, 'icons/python.svg')), - QCoreApplication.translate("PMGBaseEditor", 'Run Selected Code'), - self.text_edit) - self._action_save = QAction(QIcon(os.path.join(self.icon_path, 'icons/save.svg')), - QCoreApplication.translate("PMGBaseEditor", 'Save'), - self.text_edit) - self._action_find = QAction(QCoreApplication.translate("PMGBaseEditor", 'Find'), self.text_edit) - self._action_replace = QAction(QCoreApplication.translate("PMGBaseEditor", 'Replace'), self.text_edit) - - self._action_find_in_path = QAction(QCoreApplication.translate('PMGBaseEditor', 'Find In Path'), self.text_edit) - self._action_autocomp = QAction(QCoreApplication.translate("PMGBaseEditor", 'AutoComp'), self.text_edit) - - # 设置快捷键 - self._shortcut_format = QShortcut(QKeySequence('Ctrl+Alt+F'), self.text_edit, context=Qt.WidgetShortcut) - self._action_format.setShortcut(QKeySequence('Ctrl+Alt+F')) - - self._shortcut_autocomp = QShortcut(QKeySequence('Ctrl+P'), self.text_edit, context=Qt.WidgetShortcut) - self._action_autocomp.setShortcut(QKeySequence("Ctrl+P")) - - self._shortcut_run = QShortcut(QKeySequence('Ctrl+R'), self.text_edit, context=Qt.WidgetShortcut) - self._action_run_code.setShortcut(QKeySequence('Ctrl+R')) - - self._shortcut_run_sel = QShortcut(QKeySequence('F9'), self.text_edit, context=Qt.WidgetShortcut) - self._action_run_sel_code.setShortcut(QKeySequence('F9')) - - self._action_save.setShortcut(QKeySequence('Ctrl+S')) - self._shortcut_save = QShortcut(QKeySequence('Ctrl+S'), self.text_edit, context=Qt.WidgetShortcut) - - self._action_find.setShortcut(QKeySequence('Ctrl+F')) - self._shortcut_find = QShortcut(QKeySequence('Ctrl+F'), self.text_edit, context=Qt.WidgetShortcut) - - self._action_replace.setShortcut(QKeySequence('Ctrl+H')) - self._shortcut_replace = QShortcut(QKeySequence('Ctrl+H'), self.text_edit, context=Qt.WidgetShortcut) - - self._action_find_in_path.setShortcut(QKeySequence('Ctrl+Shift+F')) - self._shortcut_find_in_path = QShortcut(QKeySequence('Ctrl+Shift+F'), self.text_edit, context=Qt.WidgetShortcut) - - self._shortcut_goto = QShortcut(QKeySequence('Ctrl+G'), self.text_edit, context=Qt.WidgetShortcut) - - self._action_add_breakpoint = QAction(QIcon(os.path.join(self.icon_path, 'icons/breakpoint.svg')), - QCoreApplication.translate("PMGBaseEditor", 'Add Breakpoint'), - self.text_edit) - self._action_remove_breakpoint = QAction(QCoreApplication.translate("PMGBaseEditor", 'Remove Breakpoint'), - self.text_edit) - - self._action_view_breakpoints = QAction(QCoreApplication.translate("PMGBaseEditor", 'View BreakPoints'), - self.text_edit) - - def _init_signals(self): - """ - 初始化信号绑定 - - :return: None - """ - - # 绑定获得焦点信号 - self.text_edit.signal_focused_in.connect(self.slot_text_edit_focusedin) - # 绑定光标变化信号 - self.text_edit.cursorPositionChanged.connect(self.slot_cursor_position_changed) - # 绑定内容改变信号 - self.text_edit.textChanged.connect(self.slot_text_changed) - # 绑定选中变化信号 - self.text_edit.selectionChanged.connect(self.slot_selection_changed) - # 绑定是否被修改信号 - # self.text_edit.signal_modification)_Changed.connect(self.slot_modification_changed) - # 绑定右键菜单信号 - self.text_edit.customContextMenuRequested.connect(self.slot_custom_context_menu_requested) - # 绑定快捷键信号 - self._action_format.triggered.connect(self.slot_code_format) - self._shortcut_format.activated.connect(self.slot_code_format) - self._action_run_code.triggered.connect(self.slot_code_run) - self._shortcut_run.activated.connect(self.slot_code_run) - self._action_run_sel_code.triggered.connect(self.slot_code_sel_run) - self._shortcut_run_sel.activated.connect(self.slot_code_sel_run) - - self._shortcut_save.activated.connect(self.slot_save) - self._action_save.triggered.connect(self.slot_save) - - self._action_find.triggered.connect(self.slot_find) - self._action_replace.triggered.connect(self.slot_replace) - - self._shortcut_find.activated.connect(self.slot_find) - self._shortcut_replace.activated.connect(self.slot_replace) - - self._action_find_in_path.triggered.connect(self.slot_find_in_path) - self._shortcut_find_in_path.activated.connect(self.slot_find_in_path) - - self._action_autocomp.triggered.connect(self.autocomp) - self._shortcut_autocomp.activated.connect(self.autocomp) - - self._shortcut_goto.activated.connect(self.slot_goto_line) - - # self._action_add_breakpoint.triggered.connect(self.slot_add_breakpoint_triggered) - # self._action_remove_breakpoint.triggered.connect(self.slot_remove_breakpoint_triggered) - - # self._action_view_breakpoints.triggered.connect(self.view_break_points) - - def slot_cursor_position_changed(self): - pass - - def slot_selection_changed(self) -> None: - """ - 选中内容变化槽函数 - - :return: None - """ - - def create_context_menu(self) -> 'QMenu': - """ - 创建上下文菜单。 - :return: - """ - menu = self.text_edit.createStandardContextMenu() - - # 遍历本身已有的菜单项做翻译处理 - # 前提是要加载了Qt自带的翻译文件 - for action in menu.actions(): - action.setText(QCoreApplication.translate('QTextControl', action.text())) - # 添加额外菜单 - menu.addSeparator() - menu.addAction(self._action_format) - menu.addAction(self._action_run_code) - menu.addAction(self._action_run_sel_code) - menu.addAction(self._action_save) - menu.addAction(self._action_find) - menu.addAction(self._action_replace) - menu.addAction(self._action_find_in_path) - menu.addAction(self._action_add_breakpoint) - menu.addAction(self._action_remove_breakpoint) - menu.addAction(self._action_view_breakpoints) - # menu.addAction(self) - return menu - - def slot_custom_context_menu_requested(self, pos: QPoint) -> None: - """ - 右键菜单修改 - - :param pos: - :type pos: QPoint - :return: None - """ - menu = self.create_context_menu() - # 根据条件决定菜单是否可用 - enabled = len(self.text().strip()) > 0 - self._action_format.setEnabled(enabled) - self._action_run_code.setEnabled(enabled) - # self._action_run_sel_code.setEnabled(self.textEdit.hasSelectedText()) - self._action_run_sel_code.setEnabled(enabled) - menu.exec_(self.text_edit.mapToGlobal(pos)) - del menu - - def slot_find_in_path(self): - sel = self.text_edit.get_selected_text() - self.signal_request_find_in_path.emit(sel) - - def slot_find(self): - self.find_dialog.show_replace_actions(replace_on=False) - self.find_dialog.show() - - def slot_replace(self): - self.find_dialog.show_replace_actions(replace_on=True) - self.find_dialog.show() - - def slot_goto_line(self): - self.goto_line_dialog.set_current_line(self.text_edit.textCursor().blockNumber()) - self.goto_line_dialog.set_max_row_count(self.text_edit.blockCount()) - ret = self.goto_line_dialog.exec_() - if ret: - self.goto_line(self.goto_line_dialog.get_line()) - - def set_indicators(self, msg, clear=True): - """ - qtextedit 的indicators ,但是目前还不支持。 - :return: - """ - pass - - def change_color_scheme(self, color_scheme_name: str): - if color_scheme_name == 'dark': - self.text_edit.load_color_scheme({'keyword': '#b7602f'}) - elif color_scheme_name == 'light': - self.text_edit.load_color_scheme({'keyword': '#101e96'}) - else: - raise ValueError('unrecognized input color scheme name %s' % color_scheme_name) diff --git a/packages/code_editor/widgets/dialogs/find_dialog.py b/packages/code_editor/widgets/dialogs/find_dialog.py index fca1238e..314bdd0e 100644 --- a/packages/code_editor/widgets/dialogs/find_dialog.py +++ b/packages/code_editor/widgets/dialogs/find_dialog.py @@ -5,7 +5,7 @@ from PySide2.QtWidgets import QDialog, QVBoxLayout, QPushButton, QHBoxLayout from pmgwidgets import PMGPanel if TYPE_CHECKING: - from packages.code_editor.codeeditor.qtpyeditor.codeeditor import PMGBaseEditor + from packages.code_editor.widgets.editors.base_editor import PMGBaseEditor from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 328ecc7c..0cc5e10d 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -28,20 +28,61 @@ Created on 2020/9/7 import logging import os -from typing import Dict, Any +import time +from typing import Dict, Any, List, Callable -from PySide2.QtWidgets import QWidget, QMessageBox +from PySide2.QtCore import SignalInstance, Signal, QTranslator, QLocale, QDir, QCoreApplication, QPoint, Qt +from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon +from PySide2.QtWidgets import QWidget, QMessageBox, QApplication, QLabel, QHBoxLayout, QVBoxLayout, QFileDialog, \ + QShortcut, QAction + +from packages.code_editor.codeeditor.qtpyeditor.Utilities import decode +from packages.code_editor.widgets.dialogs.find_dialog import FindDialog +from packages.code_editor.widgets.dialogs.goto_line_dialog import GotoLineDialog +from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit logger = logging.getLogger(__name__) class PMAbstractEditor(QWidget): - textEdit: 'PMBaseCodeEdit' + signal_focused_in: SignalInstance = None + signal_new_requested: SignalInstance = Signal(str, int) # 文件路径;文件的打开模式(目前都是0) + signal_save_requested: SignalInstance = Signal() + signal_request_find_in_path: SignalInstance = Signal(str) + text_edit: 'PMBaseCodeEdit' + find_dialog: 'FindDialog' + goto_line_dialog: 'GotoLineDialog' + + # 为PySide2内置函数添加代码提示 + layout: Callable[[], QVBoxLayout] def __init__(self, parent): + app = QApplication.instance() + trans_editor_tb = QTranslator() + app.trans_editor_tb = trans_editor_tb + trans_editor_tb.load( + os.path.join(os.path.dirname(__file__), 'translations', 'qt_%s.qm' % QLocale.system().name())) + app.installTranslator(trans_editor_tb) super().__init__(parent) self.last_save_time = 0 self.extension_lib = None + self.last_save_time = 0 + self._path = '' + self._modified = False + self._extension_names: List[str] = [] # 该编辑器支持的文件名 + self._indicator_dict: Dict[str, str] = {} + + self.line_number_area = QWidget() + self.line_number_area.setMaximumHeight(60) + self.line_number_area.setMinimumHeight(20) + self.status_label = QLabel() + line_number_area_layout = QHBoxLayout() + line_number_area_layout.addWidget(self.status_label) + line_number_area_layout.setContentsMargins(0, 0, 0, 0) + self.line_number_area.setLayout(line_number_area_layout) + self.modified_status_label = QLabel() + self.modified_status_label.setText('') + line_number_area_layout.addWidget(self.modified_status_label) def set_shortcut(self): pass @@ -49,6 +90,29 @@ class PMAbstractEditor(QWidget): def set_lib(self, extension_lib): pass + def set_edit(self, edit: 'PMBaseCodeEdit'): + self.text_edit = edit + 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.find_dialog = FindDialog(parent=self, text_editor=self) + self.goto_line_dialog = GotoLineDialog(parent=self) + layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + self.setLayout(layout) + self.layout().addWidget(self.text_edit) + self.layout().addWidget(self.status_label) + + def show_cursor_pos(self): + row = self.text_edit.textCursor().block().blockNumber() + col = self.text_edit.textCursor().columnNumber() + self.status_label.setText(f'行:{row + 1},列:{col + 1}') + + def slot_text_edit_focusedin(self, e): + pass + def update_settings(self, settings: Dict[str, Any]): pass @@ -60,7 +124,48 @@ class PMAbstractEditor(QWidget): 跳转到对应行列 :return: """ - pass + """跳转到对应行列""" + doc: QTextDocument = self.text_edit.document() + lines = doc.blockCount() + assert 1 <= line_no <= lines + pos = doc.findBlockByLineNumber(line_no - 1).position() + text_cursor: QTextCursor = self.text_edit.textCursor() + text_cursor.setPosition(pos) + self.text_edit.setTextCursor(text_cursor) + + def search_word(self, text_to_find: str, wrap: bool, regex: bool, case_sensitive: bool, whole_word: bool, + forward: bool, index=-1, line=-1, **kwargs) -> bool: + find_flags = 0 + # if wrap: + # find_flags = find_flags | QTextDocument.FindFlags + if case_sensitive: + find_flags = find_flags | QTextDocument.FindCaseSensitively + if whole_word: + find_flags = find_flags | QTextDocument.FindWholeWords + if not forward: + find_flags = find_flags | QTextDocument.FindBackward + if find_flags == 0: + find_flags = QTextDocument.FindFlags + # print(find_flags) + ret = self.text_edit.find(text_to_find, options=find_flags) + cursor_pos = self.text_edit.get_cursor_position() + if wrap and (not ret): + cursor = self.text_edit.textCursor() + cursor.clearSelection() + if forward: + cursor.movePosition(QTextCursor.Start) + print('cursor to start!') + + else: + cursor.movePosition(QTextCursor.End) + self.text_edit.setTextCursor(cursor) + ret = self.text_edit.find(text_to_find, options=find_flags) + # print(ret,cursor) + if not ret: + cursor = self.text_edit.textCursor() + cursor.setPosition(cursor_pos) + self.text_edit.setTextCursor(cursor) + return ret def _init_signals(self) -> None: """ @@ -68,7 +173,48 @@ class PMAbstractEditor(QWidget): :return: None """ - pass + + # 绑定获得焦点信号 + self.text_edit.signal_focused_in.connect(self.slot_text_edit_focusedin) + # 绑定光标变化信号 + self.text_edit.cursorPositionChanged.connect(self.slot_cursor_position_changed) + # 绑定内容改变信号 + self.text_edit.textChanged.connect(self.slot_text_changed) + # 绑定选中变化信号 + self.text_edit.selectionChanged.connect(self.slot_selection_changed) + # 绑定是否被修改信号 + # self.text_edit.signal_modification)_Changed.connect(self.slot_modification_changed) + # 绑定右键菜单信号 + self.text_edit.customContextMenuRequested.connect(self.slot_custom_context_menu_requested) + # 绑定快捷键信号 + self._action_format.triggered.connect(self.slot_code_format) + self._shortcut_format.activated.connect(self.slot_code_format) + self._action_run_code.triggered.connect(self.slot_code_run) + self._shortcut_run.activated.connect(self.slot_code_run) + self._action_run_sel_code.triggered.connect(self.slot_code_sel_run) + self._shortcut_run_sel.activated.connect(self.slot_code_sel_run) + + self._shortcut_save.activated.connect(self.slot_save) + self._action_save.triggered.connect(self.slot_save) + + self._action_find.triggered.connect(self.slot_find) + self._action_replace.triggered.connect(self.slot_replace) + + self._shortcut_find.activated.connect(self.slot_find) + self._shortcut_replace.activated.connect(self.slot_replace) + + self._action_find_in_path.triggered.connect(self.slot_find_in_path) + self._shortcut_find_in_path.activated.connect(self.slot_find_in_path) + + self._action_autocomp.triggered.connect(self.autocomp) + self._shortcut_autocomp.activated.connect(self.autocomp) + + self._shortcut_goto.activated.connect(self.slot_goto_line) + + # self._action_add_breakpoint.triggered.connect(self.slot_add_breakpoint_triggered) + # self._action_remove_breakpoint.triggered.connect(self.slot_remove_breakpoint_triggered) + + # self._action_view_breakpoints.triggered.connect(self.view_break_points) def _init_actions(self) -> None: """ @@ -76,7 +222,60 @@ class PMAbstractEditor(QWidget): :return: """ - pass + self.icon_path = os.path.dirname(os.path.dirname(__file__)) # 图标文件路径 + self._action_format = QAction(QIcon(os.path.join(self.icon_path, 'icons/format.svg')), + QCoreApplication.translate("PMGBaseEditor", 'Format Code'), + self.text_edit) + self._action_run_code = QAction(QIcon(os.path.join(self.icon_path, 'icons/run.svg')), + QCoreApplication.translate("PMGBaseEditor", 'Run Code'), + self.text_edit) + self._action_run_sel_code = QAction(QIcon(os.path.join(self.icon_path, 'icons/python.svg')), + QCoreApplication.translate("PMGBaseEditor", 'Run Selected Code'), + self.text_edit) + self._action_save = QAction(QIcon(os.path.join(self.icon_path, 'icons/save.svg')), + QCoreApplication.translate("PMGBaseEditor", 'Save'), + self.text_edit) + self._action_find = QAction(QCoreApplication.translate("PMGBaseEditor", 'Find'), self.text_edit) + self._action_replace = QAction(QCoreApplication.translate("PMGBaseEditor", 'Replace'), self.text_edit) + + self._action_find_in_path = QAction(QCoreApplication.translate('PMGBaseEditor', 'Find In Path'), self.text_edit) + self._action_autocomp = QAction(QCoreApplication.translate("PMGBaseEditor", 'AutoComp'), self.text_edit) + + # 设置快捷键 + self._shortcut_format = QShortcut(QKeySequence('Ctrl+Alt+F'), self.text_edit, context=Qt.WidgetShortcut) + self._action_format.setShortcut(QKeySequence('Ctrl+Alt+F')) + + self._shortcut_autocomp = QShortcut(QKeySequence('Ctrl+P'), self.text_edit, context=Qt.WidgetShortcut) + self._action_autocomp.setShortcut(QKeySequence("Ctrl+P")) + + self._shortcut_run = QShortcut(QKeySequence('Ctrl+R'), self.text_edit, context=Qt.WidgetShortcut) + self._action_run_code.setShortcut(QKeySequence('Ctrl+R')) + + self._shortcut_run_sel = QShortcut(QKeySequence('F9'), self.text_edit, context=Qt.WidgetShortcut) + self._action_run_sel_code.setShortcut(QKeySequence('F9')) + + self._action_save.setShortcut(QKeySequence('Ctrl+S')) + self._shortcut_save = QShortcut(QKeySequence('Ctrl+S'), self.text_edit, context=Qt.WidgetShortcut) + + self._action_find.setShortcut(QKeySequence('Ctrl+F')) + self._shortcut_find = QShortcut(QKeySequence('Ctrl+F'), self.text_edit, context=Qt.WidgetShortcut) + + self._action_replace.setShortcut(QKeySequence('Ctrl+H')) + self._shortcut_replace = QShortcut(QKeySequence('Ctrl+H'), self.text_edit, context=Qt.WidgetShortcut) + + self._action_find_in_path.setShortcut(QKeySequence('Ctrl+Shift+F')) + self._shortcut_find_in_path = QShortcut(QKeySequence('Ctrl+Shift+F'), self.text_edit, context=Qt.WidgetShortcut) + + self._shortcut_goto = QShortcut(QKeySequence('Ctrl+G'), self.text_edit, context=Qt.WidgetShortcut) + + self._action_add_breakpoint = QAction(QIcon(os.path.join(self.icon_path, 'icons/breakpoint.svg')), + QCoreApplication.translate("PMGBaseEditor", 'Add Breakpoint'), + self.text_edit) + self._action_remove_breakpoint = QAction(QCoreApplication.translate("PMGBaseEditor", 'Remove Breakpoint'), + self.text_edit) + + self._action_view_breakpoints = QAction(QCoreApplication.translate("PMGBaseEditor", 'View BreakPoints'), + self.text_edit) def autocomp(self): pass @@ -92,7 +291,7 @@ class PMAbstractEditor(QWidget): :param text: 文本内容 :return: None """ - pass + self.text_edit.setPlainText(text) def set_modified(self, modified: bool) -> None: """ @@ -102,7 +301,9 @@ class PMAbstractEditor(QWidget): :type: bool :return: None """ - pass + self._modified = modified + self.text_edit.modified = modified + self.slot_modification_changed(modified) def load_file(self, path: str) -> None: """ @@ -112,7 +313,25 @@ class PMAbstractEditor(QWidget): :type path: str :return: None """ - pass + self._path = '' + try: + # 读取文件内容并加载 + with open(path, 'rb') as fp: + text = fp.read() + text, coding = decode(text) + self.set_encoding(coding) + self.set_text(text) + self.set_modified(False) + self.text_edit.set_eol_status() + except Exception as e: + import traceback + traceback.print_exc() + logger.warning(str(e)) + + self._path = path + self.setWindowTitle(self.filename()) + self.last_save_time = time.time() + self.set_modified(False) def set_encoding(self, encoding: str): """ @@ -164,6 +383,8 @@ class PMAbstractEditor(QWidget): 保存时触发的事件。 :return: """ + self.save() + self.set_modified(False) def default_save_path(self) -> str: """ @@ -177,10 +398,38 @@ class PMAbstractEditor(QWidget): pass def save(self) -> bool: - """ - 保存文件时调用的方法 - :return: - """ + """保存文件时调用的方法 + + If the file is not saved yet, the qfiledialog will open save dialog at default_dir,generated by get_default_dir + method. + """ + QCoreApplication.translate = QCoreApplication.translate + path = self._path.replace_selection(os.sep, '/') + default_dir = self.default_save_path() + if path.startswith(QDir.tempPath().replace(os.sep, '/')): + assert os.path.exists(default_dir) or default_dir == '' + # 弹出对话框要求选择真实路径保存 + path, ext = QFileDialog.getSaveFileName(self, QCoreApplication.translate("PMGBaseEditor", 'Save file'), + default_dir, + filter='*.py') + + if not path: + return False + if not path.endswith('.py'): + path += '.py' + self._path = path + try: + with open(self._path, 'wb') as fp: + fp.write(self.text().encode('utf-8', errors='ignore')) + + self.setWindowTitle(os.path.basename(path)) + self.set_modified(False) + self.last_save_time = time.time() + return True + except Exception as e: + # 保存失败 + logger.warning(str(e)) + return False def modified(self) -> bool: """ @@ -189,7 +438,18 @@ class PMAbstractEditor(QWidget): :rtype: bool :return: 返回内容是否被修改 """ - return self.textEdit.is_modified() + return self._modified + + def is_temp_file(self) -> bool: + """ + 返回文件是否为临时文件 + :return: + """ + tmp_path = QDir.tempPath().replace('\\', '/') + if self._path.replace('\\', '/').startswith(tmp_path): + return True + else: + return False def filename(self) -> str: """ @@ -227,21 +487,29 @@ class PMAbstractEditor(QWidget): self.setWindowTitle(new_title) def text(self, selected: bool = False) -> str: - """ - 返回编辑器选中或者全部内容 + """返回编辑器选中或者全部内容。 + Args: - selected: + selected: True则返回选中的内容,False则返回全部内容 Returns: - + str, 选中的或全部的代码 """ - return '' + if selected: + return self.text_edit.get_selected_text() + else: + return self.text_edit.toPlainText() def slot_file_modified_externally(self): return def change_color_scheme(self, color_scheme_name: str): - pass + if color_scheme_name == 'dark': + self.text_edit.load_color_scheme({'keyword': '#b7602f'}) + elif color_scheme_name == 'light': + self.text_edit.load_color_scheme({'keyword': '#101e96'}) + else: + raise ValueError('unrecognized input color scheme name %s' % color_scheme_name) def slot_code_format(self): pass @@ -251,3 +519,83 @@ class PMAbstractEditor(QWidget): def slot_code_sel_run(self): pass + + def slot_cursor_position_changed(self): + pass + + def slot_selection_changed(self) -> None: + """ + 选中内容变化槽函数 + + :return: None + """ + + def create_context_menu(self) -> 'QMenu': + """ + 创建上下文菜单。 + :return: + """ + menu = self.text_edit.createStandardContextMenu() + + # 遍历本身已有的菜单项做翻译处理 + # 前提是要加载了Qt自带的翻译文件 + for action in menu.actions(): + action.setText(QCoreApplication.translate('QTextControl', action.text())) + # 添加额外菜单 + menu.addSeparator() + menu.addAction(self._action_format) + menu.addAction(self._action_run_code) + menu.addAction(self._action_run_sel_code) + menu.addAction(self._action_save) + menu.addAction(self._action_find) + menu.addAction(self._action_replace) + menu.addAction(self._action_find_in_path) + menu.addAction(self._action_add_breakpoint) + menu.addAction(self._action_remove_breakpoint) + menu.addAction(self._action_view_breakpoints) + # menu.addAction(self) + return menu + + def slot_custom_context_menu_requested(self, pos: QPoint) -> None: + """ + 右键菜单修改 + + :param pos: + :type pos: QPoint + :return: None + """ + menu = self.create_context_menu() + # 根据条件决定菜单是否可用 + enabled = len(self.text().strip()) > 0 + self._action_format.setEnabled(enabled) + self._action_run_code.setEnabled(enabled) + # self._action_run_sel_code.setEnabled(self.textEdit.hasSelectedText()) + self._action_run_sel_code.setEnabled(enabled) + menu.exec_(self.text_edit.mapToGlobal(pos)) + del menu + + def slot_find_in_path(self): + sel = self.text_edit.get_selected_text() + self.signal_request_find_in_path.emit(sel) + + def slot_find(self): + self.find_dialog.show_replace_actions(replace_on=False) + self.find_dialog.show() + + def slot_replace(self): + self.find_dialog.show_replace_actions(replace_on=True) + self.find_dialog.show() + + def slot_goto_line(self): + self.goto_line_dialog.set_current_line(self.text_edit.textCursor().blockNumber()) + self.goto_line_dialog.set_max_row_count(self.text_edit.blockCount()) + ret = self.goto_line_dialog.exec_() + if ret: + self.goto_line(self.goto_line_dialog.get_line()) + + def set_indicators(self, msg, clear=True): + """ + qtextedit 的indicators ,但是目前还不支持。 + :return: + """ + pass diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index 4e27d44b..ace3a3bb 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -42,16 +42,16 @@ from PySide2.QtWidgets import QAction, QShortcut, QMessageBox from yapf.yapflib import py3compat, yapf_api from packages.code_editor.codeeditor.qtpyeditor import PythonHighlighter -from packages.code_editor.codeeditor.qtpyeditor.codeeditor import PMGBaseEditor from packages.code_editor.widgets.text_edit.python_text_edit import PMPythonCodeEdit from pmgwidgets import in_unit_test, PMGOneShotThreadRunner, run_python_file_in_terminal, parse_simplified_pmgjson, \ PMGPanelDialog +from .base_editor import PMAbstractEditor logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) -class PMPythonEditor(PMGBaseEditor): +class PMPythonEditor(PMAbstractEditor): """ 自定义编辑器控件 """ -- Gitee From b33904d9704a65db314cc50f68e7a33a7af13285 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 01:26:52 +0800 Subject: [PATCH 028/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codeeditor/qtpyeditor/highlighters/python.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/code_editor/codeeditor/qtpyeditor/highlighters/python.py b/packages/code_editor/codeeditor/qtpyeditor/highlighters/python.py index 232e7035..72e8adb3 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/highlighters/python.py +++ b/packages/code_editor/codeeditor/qtpyeditor/highlighters/python.py @@ -7,18 +7,18 @@ import builtins import logging import sys import time -from typing import Dict, Tuple, List, Set +from typing import Dict, List, Set from PySide2.QtCore import QRegExp, Qt +from PySide2.QtGui import QSyntaxHighlighter, QTextCharFormat, QColor, QFont, QCursor, QBrush from PySide2.QtWidgets import QApplication -from PySide2.QtGui import QSyntaxHighlighter, QTextCharFormat, QColor, QFont, QCursor, QBrush, QTextBlock logger = logging.getLogger('code_editor.highlighters.python') color_scheme_intellij = {'keyword': '#101e96'} color_scheme_dark = {'keyword': '#b7602f'} -class FontConfig(): +class FontConfig: def __init__(self): """ color是16位标准的。 -- Gitee From e463a84b35dbd47a8f2957baa810e4e79062d4e9 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 01:27:59 +0800 Subject: [PATCH 029/108] =?UTF-8?q?=E5=88=A0=E9=99=A4code=5Feditor?= =?UTF-8?q?=E4=B8=AD=E6=97=A0=E7=94=A8=E7=9A=84=E7=BF=BB=E8=AF=91=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qtpyeditor/translations/qt_zh_CN.qm | Bin 1596 -> 0 bytes .../qtpyeditor/translations/qt_zh_CN.ts | 143 ------------------ 2 files changed, 143 deletions(-) delete mode 100644 packages/code_editor/codeeditor/qtpyeditor/translations/qt_zh_CN.qm delete mode 100644 packages/code_editor/codeeditor/qtpyeditor/translations/qt_zh_CN.ts diff --git a/packages/code_editor/codeeditor/qtpyeditor/translations/qt_zh_CN.qm b/packages/code_editor/codeeditor/qtpyeditor/translations/qt_zh_CN.qm deleted file mode 100644 index 765078f2772c13314b1273053fd9f747018fbb34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1596 zcma)+QAiVU9LN7||Fzq8I`nrM+3s((V*P%{Z^s6%3(lER z2mXJb^Yb8!^{<#iES_f{VBVMDzF^!{@iu{XakFshC_ewedf*VQFWBUrN0=XFXCE%$ zeb(78hu3gDk*@Z-+=ASnmMH6ckLDi)kg25JFz+(ciR`1bhvOHBz%6^87&V)i2v!R2d8{wfqnFV4A#MNsYX`Ox2e~r^;GSN+b z3Qfmen)dUmMUktCBcUs%E1E$j<~B8)uE9P}iHf1eQ^uIVb^gf+YL-oosqQv(zaC9R zMt{a~fl9#6+f$z_Q(|D1w) N(xEnymn%V`{{Se{d_DjG diff --git a/packages/code_editor/codeeditor/qtpyeditor/translations/qt_zh_CN.ts b/packages/code_editor/codeeditor/qtpyeditor/translations/qt_zh_CN.ts deleted file mode 100644 index 9204a031..00000000 --- a/packages/code_editor/codeeditor/qtpyeditor/translations/qt_zh_CN.ts +++ /dev/null @@ -1,143 +0,0 @@ - - - - - FindDialog - - - Text to Find - 要查找的文本 - - - - Text to Replace - 要替换的文本 - - - - Wrap - 循环查找 - - - - Regex - 查找正则表达式 - - - - Case Sensitive - 大小写敏感 - - - - Whole Word - 全字匹配 - - - - Up - 向上 - - - - Down - 向下 - - - - Replace - 替换 - - - - Replace All - 全部替换 - - - - FindInPathWidget - - - Case - 大小写敏感 - - - - Whole Word - 全字匹配 - - - - Find - 查找 - - - - Find In Path - 在路径中查找 - - - - PMGBaseEditor - - - Save - 保存 - - - - Save file "{0}"? - 是否保存文件"{0}"? - - - - Save file - 保存文件 - - - - Format Code - 格式化代码 - - - - Run Code - 运行代码 - - - - Run Selected Code - 运行选中代码 - - - - Find - 查找 - - - - Find In Path - 在路径中查找 - - - - AutoComp - 自动补全 - - - - Add Breakpoint - 添加断点 - - - - Remove Breakpoint - 移除断点 - - - - View BreakPoints - 查看所有断点 - - - -- Gitee From d0718a662fd6e2004b7188fdee49a8840ae5f1a7 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 01:29:30 +0800 Subject: [PATCH 030/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/codeeditor/tabwidget.py | 2 +- packages/code_editor/widgets/dialogs/goto_line_dialog.py | 2 +- .../{codeeditor/qtpyeditor => widgets}/ui/__init__.py | 0 .../{codeeditor/qtpyeditor => widgets}/ui/findinpath.py | 0 .../{codeeditor/qtpyeditor => widgets}/ui/formeditor.py | 0 .../{codeeditor/qtpyeditor => widgets}/ui/gotoline.py | 0 .../{codeeditor/qtpyeditor => widgets}/ui/ui_formeditor.py | 0 .../{codeeditor/qtpyeditor => widgets}/ui/ui_gotoline.py | 0 8 files changed, 2 insertions(+), 2 deletions(-) rename packages/code_editor/{codeeditor/qtpyeditor => widgets}/ui/__init__.py (100%) rename packages/code_editor/{codeeditor/qtpyeditor => widgets}/ui/findinpath.py (100%) rename packages/code_editor/{codeeditor/qtpyeditor => widgets}/ui/formeditor.py (100%) rename packages/code_editor/{codeeditor/qtpyeditor => widgets}/ui/gotoline.py (100%) rename packages/code_editor/{codeeditor/qtpyeditor => widgets}/ui/ui_formeditor.py (100%) rename packages/code_editor/{codeeditor/qtpyeditor => widgets}/ui/ui_gotoline.py (100%) diff --git a/packages/code_editor/codeeditor/tabwidget.py b/packages/code_editor/codeeditor/tabwidget.py index 4bdf970e..b929ad6e 100644 --- a/packages/code_editor/codeeditor/tabwidget.py +++ b/packages/code_editor/codeeditor/tabwidget.py @@ -921,7 +921,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): return self.tr('Editor') def slot_find_in_path(self, word: str): - from packages.code_editor.codeeditor.qtpyeditor.ui.findinpath import FindInPathWidget + from packages.code_editor.widgets.ui import FindInPathWidget path = self.extension_lib.Program.get_work_dir() if not self.extension_lib.UI.widget_exists('find_in_path'): w: FindInPathWidget = self.extension_lib.insert_widget( diff --git a/packages/code_editor/widgets/dialogs/goto_line_dialog.py b/packages/code_editor/widgets/dialogs/goto_line_dialog.py index 4c30b3fc..4e05091d 100644 --- a/packages/code_editor/widgets/dialogs/goto_line_dialog.py +++ b/packages/code_editor/widgets/dialogs/goto_line_dialog.py @@ -2,7 +2,7 @@ from typing import Callable from PySide2.QtWidgets import QDialog, QMessageBox -from packages.code_editor.codeeditor.qtpyeditor.ui.gotoline import Ui_DialogGoto +from packages.code_editor.widgets.ui.gotoline import Ui_DialogGoto class GotoLineDialog(QDialog, Ui_DialogGoto): diff --git a/packages/code_editor/codeeditor/qtpyeditor/ui/__init__.py b/packages/code_editor/widgets/ui/__init__.py similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/ui/__init__.py rename to packages/code_editor/widgets/ui/__init__.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/ui/findinpath.py b/packages/code_editor/widgets/ui/findinpath.py similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/ui/findinpath.py rename to packages/code_editor/widgets/ui/findinpath.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/ui/formeditor.py b/packages/code_editor/widgets/ui/formeditor.py similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/ui/formeditor.py rename to packages/code_editor/widgets/ui/formeditor.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/ui/gotoline.py b/packages/code_editor/widgets/ui/gotoline.py similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/ui/gotoline.py rename to packages/code_editor/widgets/ui/gotoline.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/ui/ui_formeditor.py b/packages/code_editor/widgets/ui/ui_formeditor.py similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/ui/ui_formeditor.py rename to packages/code_editor/widgets/ui/ui_formeditor.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/ui/ui_gotoline.py b/packages/code_editor/widgets/ui/ui_gotoline.py similarity index 100% rename from packages/code_editor/codeeditor/qtpyeditor/ui/ui_gotoline.py rename to packages/code_editor/widgets/ui/ui_gotoline.py -- Gitee From 2cf385ead304ecc4b0156b0432810eccd3a639d0 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 01:33:39 +0800 Subject: [PATCH 031/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codeeditor/qtpyeditor/Utilities/__init__.py | 2 +- packages/code_editor/utils/__init__.py | 0 .../code_editor/utils/auto_complete_thread/__init__.py | 0 .../auto_complete_thread/python_auto_complete.py} | 8 ++------ .../code_editor/widgets/text_edit/python_text_edit.py | 2 +- 5 files changed, 4 insertions(+), 8 deletions(-) create mode 100644 packages/code_editor/utils/__init__.py create mode 100644 packages/code_editor/utils/auto_complete_thread/__init__.py rename packages/code_editor/{codeeditor/qtpyeditor/Utilities/autocomp.py => utils/auto_complete_thread/python_auto_complete.py} (94%) diff --git a/packages/code_editor/codeeditor/qtpyeditor/Utilities/__init__.py b/packages/code_editor/codeeditor/qtpyeditor/Utilities/__init__.py index 9895a578..61130c1e 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/Utilities/__init__.py +++ b/packages/code_editor/codeeditor/qtpyeditor/Utilities/__init__.py @@ -9,7 +9,7 @@ Package implementing various functions/classes needed everywhere within eric6. import os import re from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF32 -from .autocomp import AutoCompThread + try: EXTSEP = os.extsep except AttributeError: diff --git a/packages/code_editor/utils/__init__.py b/packages/code_editor/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/code_editor/utils/auto_complete_thread/__init__.py b/packages/code_editor/utils/auto_complete_thread/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/code_editor/codeeditor/qtpyeditor/Utilities/autocomp.py b/packages/code_editor/utils/auto_complete_thread/python_auto_complete.py similarity index 94% rename from packages/code_editor/codeeditor/qtpyeditor/Utilities/autocomp.py rename to packages/code_editor/utils/auto_complete_thread/python_auto_complete.py index 76e41153..7d784aba 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/Utilities/autocomp.py +++ b/packages/code_editor/utils/auto_complete_thread/python_auto_complete.py @@ -1,11 +1,7 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/1/30 11:40 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: autocomp.py +import logging import re import time -import logging + from PySide2.QtCore import QThread, Signal logger = logging.getLogger(__name__) diff --git a/packages/code_editor/widgets/text_edit/python_text_edit.py b/packages/code_editor/widgets/text_edit/python_text_edit.py index b08c33d5..38dc7664 100644 --- a/packages/code_editor/widgets/text_edit/python_text_edit.py +++ b/packages/code_editor/widgets/text_edit/python_text_edit.py @@ -7,7 +7,7 @@ from PySide2.QtGui import QTextCursor, QMouseEvent from PySide2.QtWidgets import QLabel from packages.code_editor.codeeditor.qtpyeditor import PythonHighlighter -from packages.code_editor.codeeditor.qtpyeditor.Utilities import AutoCompThread +from packages.code_editor.utils.auto_complete_thread.python_auto_complete import AutoCompThread from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit logger = logging.getLogger(__name__) -- Gitee From a4b162535029d4121fe22405485f810d9be7e760 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 09:31:39 +0800 Subject: [PATCH 032/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codeeditor/qtpyeditor/linenumber.py | 147 ------------------ .../widgets/text_edit/base_text_edit.py | 100 +++++++++++- .../widgets/text_edit/line_number_area.py | 14 ++ 3 files changed, 109 insertions(+), 152 deletions(-) delete mode 100644 packages/code_editor/codeeditor/qtpyeditor/linenumber.py create mode 100644 packages/code_editor/widgets/text_edit/line_number_area.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/linenumber.py b/packages/code_editor/codeeditor/qtpyeditor/linenumber.py deleted file mode 100644 index d111e44f..00000000 --- a/packages/code_editor/codeeditor/qtpyeditor/linenumber.py +++ /dev/null @@ -1,147 +0,0 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/1/18 18:23 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: linenumber.py -# !/usr/bin/python3 -# QcodeEditor.py by acbetter. -# 来源:https://stackoverflow.com/questions/40386194/create-text-area-textedit-with-line-number-in-pyqt -# -*- coding: utf-8 -*- -import os - -from PySide2.QtCore import Qt, QRect, QSize -from PySide2.QtGui import QColor, QPainter, QTextFormat, QFont -from PySide2.QtGui import QFontDatabase -from PySide2.QtWidgets import QWidget, QPlainTextEdit, QTextEdit - -import utils - - -class QLineNumberArea(QWidget): - def __init__(self, editor): - super().__init__(editor) - self.codeEditor = editor - - def sizeHint(self): - return QSize(self.editor.lineNumberAreaWidth(), 0) - - def paintEvent(self, event): - self.codeEditor.lineNumberAreaPaintEvent(event) - - -class QCodeEditor(QPlainTextEdit): - def __init__(self, parent=None): - super().__init__(parent) - - fontId = QFontDatabase.addApplicationFont( - os.path.join(utils.get_root_dir(), 'resources', 'fonts', 'SourceCodePro-Regular.ttf')) - fontFamilies = QFontDatabase.applicationFontFamilies(fontId) - - self.font = QFont() - self.font.setPointSize(15) # 设置行号的字体大小 - # font.setFamily("Microsoft YaHei UI") # 设置行号的字体 - self.font.setFamily(fontFamilies[0]) # 设置行号的字体 - self.setFont(self.font) - - self.lineNumberArea = QLineNumberArea(self) - self.blockCountChanged.connect(self.updateLineNumberAreaWidth) - self.updateRequest.connect(self.updateLineNumberArea) - self.cursorPositionChanged.connect(self.highlightCurrentLine) - self.updateLineNumberAreaWidth(0) - - def lineNumberAreaWidth(self): - digits = 1 - max_value = max(1, self.blockCount()) - while max_value >= 10: - max_value /= 10 - digits += 1 - space = 30 + self.fontMetrics().width('9') * digits - return space - - def updateLineNumberAreaWidth(self, _): - self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) - - def updateLineNumberArea(self, rect, dy): - if dy: - self.lineNumberArea.scroll(0, dy) - else: - self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height()) - if rect.contains(self.viewport().rect()): - self.updateLineNumberAreaWidth(0) - - def resizeEvent(self, event): - super().resizeEvent(event) - cr = self.contentsRect() - self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) - - # def highlightCurrentLine(self): - # """ - # TODO:如何在不出现问题的情况下保证编辑器打开? - # :return: - # """ - # return - # extraSelections = [] - # if not self.isReadOnly(): - # selection = QTextEdit.ExtraSelection() - # lineColor = QColor(Qt.yellow).lighter(160) - # selection.format.setBackground(lineColor) - # selection.format.setProperty(QTextFormat.FullWidthSelection, True) - # selection.cursor = self.textCursor() - # selection.cursor.clearSelection() - # extraSelections.append(selection) - # self.setExtraSelections(extraSelections) - - def highlightCurrentLine(self): - extra_selections = [] - - if not self.isReadOnly(): - selection = QTextEdit.ExtraSelection() - - line_color = QColor(235, 252, 252) # 当前行背景色 - selection.format.setBackground(line_color) - - selection.format.setProperty(QTextFormat.FullWidthSelection, True) - - selection.cursor = self.textCursor() - selection.cursor.clearSelection() - - extra_selections.append(selection) - - self.setExtraSelections(extra_selections) - - def lineNumberAreaPaintEvent(self, event): - painter = QPainter(self.lineNumberArea) - - painter.fillRect(event.rect(), QColor(240, 240, 240)) - - block = self.firstVisibleBlock() - blockNumber = block.blockNumber() - top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top() - bottom = top + self.blockBoundingRect(block).height() - - # Just to make sure I use the right font - height = self.fontMetrics().height() - while block.isValid() and (top <= event.rect().bottom()): - if block.isVisible() and (bottom >= event.rect().top()): - number = str(blockNumber + 1) - painter.setPen(Qt.black) - - self.font.setPointSize(10) - painter.setFont(self.font) - - painter.drawText(0, top, self.lineNumberArea.width(), height, Qt.AlignCenter, number) - - block = block.next() - top = bottom - bottom = top + self.blockBoundingRect(block).height() - blockNumber += 1 - - -if __name__ == '__main__': - import sys - from PySide2.QtWidgets import QApplication - - app = QApplication(sys.argv) - codeEditor = QCodeEditor() - codeEditor.show() - sys.exit(app.exec_()) 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 947bc54c..0c150f98 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -1,27 +1,30 @@ import contextlib import logging +import os import re import time from itertools import groupby from queue import Queue from typing import Callable, Tuple, Dict, List -from PySide2.QtCore import SignalInstance, Signal, Qt, QTimer, QModelIndex, QUrl -from PySide2.QtGui import QFocusEvent, QTextCursor, QMouseEvent, QKeyEvent, QDragEnterEvent, QDropEvent -from PySide2.QtWidgets import QPlainTextEdit, QWidget, QApplication +from PySide2.QtCore import SignalInstance, Signal, Qt, QTimer, QModelIndex, QUrl, QRect +from PySide2.QtGui import QFocusEvent, QTextCursor, QMouseEvent, QKeyEvent, QDragEnterEvent, QDropEvent, QPainter, \ + QColor, QTextFormat, QFontDatabase, QFont +from PySide2.QtWidgets import QPlainTextEdit, QWidget, QApplication, QTextEdit +import utils from packages.code_editor.codeeditor.grammar_analyzer import GrammarAnalyzer from packages.code_editor.codeeditor.qtpyeditor import PythonHighlighter -from packages.code_editor.codeeditor.qtpyeditor.linenumber import QCodeEditor from packages.code_editor.codeeditor.qtpyeditor.syntaxana import getIndent from packages.code_editor.widgets.auto_complete_dropdown.base_auto_complete_dropdown import \ BaseAutoCompleteDropdownWidget +from packages.code_editor.widgets.text_edit.line_number_area import QLineNumberArea logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) -class PMBaseCodeEdit(QCodeEditor): +class PMBaseCodeEdit(QPlainTextEdit): """ 与语言无关的编辑器相关操作应该定义在这里。 """ @@ -43,6 +46,23 @@ class PMBaseCodeEdit(QCodeEditor): def __init__(self, parent=None): super(PMBaseCodeEdit, self).__init__(parent) + + fontId = QFontDatabase.addApplicationFont( + os.path.join(utils.get_root_dir(), 'resources', 'fonts', 'SourceCodePro-Regular.ttf')) + fontFamilies = QFontDatabase.applicationFontFamilies(fontId) + + self.font = QFont() + self.font.setPointSize(15) # 设置行号的字体大小 + # font.setFamily("Microsoft YaHei UI") # 设置行号的字体 + self.font.setFamily(fontFamilies[0]) # 设置行号的字体 + self.setFont(self.font) + + self.lineNumberArea = QLineNumberArea(self) + self.blockCountChanged.connect(self.updateLineNumberAreaWidth) + self.updateRequest.connect(self.updateLineNumberArea) + self.cursorPositionChanged.connect(self.highlightCurrentLine) + self.updateLineNumberAreaWidth(0) + self._last_operation: float = 0.0 # 记录上次操作的时间 self.update_request_queue = Queue() @@ -68,6 +88,76 @@ class PMBaseCodeEdit(QCodeEditor): self.textChanged.connect(self.update_last_operation_time) + def lineNumberAreaWidth(self): + digits = 1 + max_value = max(1, self.blockCount()) + while max_value >= 10: + max_value /= 10 + digits += 1 + space = 30 + self.fontMetrics().width('9') * digits + return space + + def updateLineNumberAreaWidth(self, _): + self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) + + def updateLineNumberArea(self, rect, dy): + if dy: + self.lineNumberArea.scroll(0, dy) + else: + self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height()) + if rect.contains(self.viewport().rect()): + self.updateLineNumberAreaWidth(0) + + def resizeEvent(self, event): + super().resizeEvent(event) + cr = self.contentsRect() + self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) + + def highlightCurrentLine(self): + extra_selections = [] + + if not self.isReadOnly(): + selection = QTextEdit.ExtraSelection() + + line_color = QColor(235, 252, 252) # 当前行背景色 + selection.format.setBackground(line_color) + + selection.format.setProperty(QTextFormat.FullWidthSelection, True) + + selection.cursor = self.textCursor() + selection.cursor.clearSelection() + + extra_selections.append(selection) + + self.setExtraSelections(extra_selections) + + def lineNumberAreaPaintEvent(self, event): + painter = QPainter(self.lineNumberArea) + + painter.fillRect(event.rect(), QColor(240, 240, 240)) + + block = self.firstVisibleBlock() + blockNumber = block.blockNumber() + top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top() + bottom = top + self.blockBoundingRect(block).height() + + # Just to make sure I use the right font + height = self.fontMetrics().height() + while block.isValid() and (top <= event.rect().bottom()): + if block.isVisible() and (bottom >= event.rect().top()): + number = str(blockNumber + 1) + painter.setPen(Qt.black) + + self.font.setPointSize(10) + painter.setFont(self.font) + + painter.drawText(0, top, self.lineNumberArea.width(), height, Qt.AlignCenter, number) + + block = block.next() + top = bottom + bottom = top + self.blockBoundingRect(block).height() + blockNumber += 1 + def update_last_operation_time(self): """更新上一次操作的时间""" self._last_operation = time.time() diff --git a/packages/code_editor/widgets/text_edit/line_number_area.py b/packages/code_editor/widgets/text_edit/line_number_area.py new file mode 100644 index 00000000..4f250e60 --- /dev/null +++ b/packages/code_editor/widgets/text_edit/line_number_area.py @@ -0,0 +1,14 @@ +from PySide2.QtCore import QSize +from PySide2.QtWidgets import QWidget + + +class QLineNumberArea(QWidget): + def __init__(self, editor): + super().__init__(editor) + self.codeEditor = editor + + def sizeHint(self): + return QSize(self.editor.lineNumberAreaWidth(), 0) + + def paintEvent(self, event): + self.codeEditor.lineNumberAreaPaintEvent(event) \ No newline at end of file -- Gitee From 92d1ba7b47f69609c6b73847832744f6772555a8 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 09:33:45 +0800 Subject: [PATCH 033/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/io/postgresql/psycopg2/_json.py | 4 +++- core/io/postgresql/psycopg2/_range.py | 6 ++++-- core/io/postgresql/psycopg2/extras.py | 4 +++- features/openprocess.py | 6 ++++-- features/ui/common/openprocess.py | 4 +++- features/util/openprocess.py | 6 ++++-- .../qtpyeditor/Utilities/__init__.py => utils/utils.py} | 9 +-------- packages/code_editor/widgets/editors/base_editor.py | 2 +- pmgwidgets/utilities/platform/openprocess.py | 5 ++++- utils/debug/debuggerprocess.py | 4 +++- utils/io/pmserial/pmqtreadserial.py | 4 +++- utils/io/pmserial/readserial.py | 4 +++- 12 files changed, 36 insertions(+), 22 deletions(-) rename packages/code_editor/{codeeditor/qtpyeditor/Utilities/__init__.py => utils/utils.py} (95%) diff --git a/core/io/postgresql/psycopg2/_json.py b/core/io/postgresql/psycopg2/_json.py index eac37972..660a7c48 100644 --- a/core/io/postgresql/psycopg2/_json.py +++ b/core/io/postgresql/psycopg2/_json.py @@ -36,6 +36,8 @@ from psycopg2.compat import PY2 # oids from PostgreSQL 9.2 +import packages.code_editor.utils.utils + JSON_OID = 114 JSONARRAY_OID = 199 @@ -88,7 +90,7 @@ class Json(object): else: def __str__(self): # getquoted is binary in Py3 - return self.getquoted().decode('ascii', 'replace') + return packages.code_editor.utils.utils.decode('ascii', 'replace') def register_json(conn_or_curs=None, globally=False, loads=None, diff --git a/core/io/postgresql/psycopg2/_range.py b/core/io/postgresql/psycopg2/_range.py index b668fb63..e0c76ca8 100644 --- a/core/io/postgresql/psycopg2/_range.py +++ b/core/io/postgresql/psycopg2/_range.py @@ -32,6 +32,8 @@ from psycopg2.extensions import ISQLQuote, adapt, register_adapter from psycopg2.extensions import new_type, new_array_type, register_type from psycopg2.compat import string_types +import packages.code_editor.utils.utils + class Range(object): """Python representation for a PostgreSQL |range|_ type. @@ -495,12 +497,12 @@ class NumberRangeAdapter(RangeAdapter): # quoted (they are numbers). Also, I'm lazy and not preparing the # adapter because I assume encoding doesn't matter for these # objects. - lower = adapt(r.lower).getquoted().decode('ascii') + lower = packages.code_editor.utils.utils.decode('ascii') else: lower = '' if not r.upper_inf: - upper = adapt(r.upper).getquoted().decode('ascii') + upper = packages.code_editor.utils.utils.decode('ascii') else: upper = '' diff --git a/core/io/postgresql/psycopg2/extras.py b/core/io/postgresql/psycopg2/extras.py index 135a3fb7..ad73e788 100644 --- a/core/io/postgresql/psycopg2/extras.py +++ b/core/io/postgresql/psycopg2/extras.py @@ -35,6 +35,8 @@ import logging as _logging import psycopg2 from psycopg2 import extensions as _ext + +import packages.code_editor.utils.utils from .extensions import cursor as _cursor from .extensions import connection as _connection from .extensions import adapt as _A, quote_ident @@ -906,7 +908,7 @@ class HstoreAdapter(object): if s is None: return None - s = s.decode(_ext.encodings[cur.connection.encoding]) + s = packages.code_editor.utils.utils.decode(_ext.encodings[cur.connection.encoding]) return self.parse(s, cur) @classmethod diff --git a/features/openprocess.py b/features/openprocess.py index d6054765..1d4cc8df 100644 --- a/features/openprocess.py +++ b/features/openprocess.py @@ -7,6 +7,8 @@ import time import chardet from typing import List +import packages.code_editor.utils.utils + class PMProcess(): def __init__(self, args: List[str]): @@ -50,7 +52,7 @@ class PMProcess(): encoding = 'utf-8' else: encoding = chardet.detect(line)['encoding'] - queue.put(str(type) + line.decode(encoding)) + queue.put(str(type) + packages.code_editor.utils.utils.decode(encoding)) print(line) stream.close() @@ -72,7 +74,7 @@ class PMProcess(): encoding = 'utf-8' else: encoding = chardet.detect(line)['encoding'] - queue.put(str(type) + line.decode(encoding)) + queue.put(str(type) + packages.code_editor.utils.utils.decode(encoding)) stream.close() def consoleLoop(self): # 封装后的内容。 diff --git a/features/ui/common/openprocess.py b/features/ui/common/openprocess.py index ddbc2f52..00872fda 100644 --- a/features/ui/common/openprocess.py +++ b/features/ui/common/openprocess.py @@ -7,6 +7,8 @@ import time import chardet from typing import List +import packages.code_editor.utils.utils + class PMProcess(): def __init__(self, args: List[str]): @@ -35,7 +37,7 @@ class PMProcess(): for line in iter(stream.readline, b''): if self.terminate: break encoding = chardet.detect(line)['encoding'] - queue.put(str(type) + line.decode(encoding)) + queue.put(str(type) + packages.code_editor.utils.utils.decode(encoding)) stream.close() def consoleLoop(self): # 封装后的内容。 diff --git a/features/util/openprocess.py b/features/util/openprocess.py index d6054765..1d4cc8df 100644 --- a/features/util/openprocess.py +++ b/features/util/openprocess.py @@ -7,6 +7,8 @@ import time import chardet from typing import List +import packages.code_editor.utils.utils + class PMProcess(): def __init__(self, args: List[str]): @@ -50,7 +52,7 @@ class PMProcess(): encoding = 'utf-8' else: encoding = chardet.detect(line)['encoding'] - queue.put(str(type) + line.decode(encoding)) + queue.put(str(type) + packages.code_editor.utils.utils.decode(encoding)) print(line) stream.close() @@ -72,7 +74,7 @@ class PMProcess(): encoding = 'utf-8' else: encoding = chardet.detect(line)['encoding'] - queue.put(str(type) + line.decode(encoding)) + queue.put(str(type) + packages.code_editor.utils.utils.decode(encoding)) stream.close() def consoleLoop(self): # 封装后的内容。 diff --git a/packages/code_editor/codeeditor/qtpyeditor/Utilities/__init__.py b/packages/code_editor/utils/utils.py similarity index 95% rename from packages/code_editor/codeeditor/qtpyeditor/Utilities/__init__.py rename to packages/code_editor/utils/utils.py index 61130c1e..af4e5942 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/Utilities/__init__.py +++ b/packages/code_editor/utils/utils.py @@ -1,11 +1,3 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2003 - 2020 Detlev Offenbach -# - -""" -Package implementing various functions/classes needed everywhere within eric6. -""" import os import re from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF32 @@ -14,6 +6,7 @@ try: EXTSEP = os.extsep except AttributeError: EXTSEP = "." + codingBytes_regexps = [ (5, re.compile(br"""coding[:=]\s*([-\w_.]+)""")), (1, re.compile(br"""<\?xml.*\bencoding\s*=\s*['"]([-\w_.]+)['"]\?>""")), diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 0cc5e10d..44f21eb6 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -36,7 +36,7 @@ from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon from PySide2.QtWidgets import QWidget, QMessageBox, QApplication, QLabel, QHBoxLayout, QVBoxLayout, QFileDialog, \ QShortcut, QAction -from packages.code_editor.codeeditor.qtpyeditor.Utilities import decode +from packages.code_editor.utils.utils import decode from packages.code_editor.widgets.dialogs.find_dialog import FindDialog from packages.code_editor.widgets.dialogs.goto_line_dialog import GotoLineDialog from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit diff --git a/pmgwidgets/utilities/platform/openprocess.py b/pmgwidgets/utilities/platform/openprocess.py index fc073355..abb634e9 100644 --- a/pmgwidgets/utilities/platform/openprocess.py +++ b/pmgwidgets/utilities/platform/openprocess.py @@ -5,6 +5,9 @@ import time import chardet from typing import List +import packages.code_editor.utils.utils + + class PMGProcess(): def __init__(self, args: List[str]): self.terminate = False @@ -34,7 +37,7 @@ class PMGProcess(): if self.terminate: break encoding = chardet.detect(line)['encoding'] - queue.put(str(type) + line.decode(encoding)) + queue.put(str(type) + packages.code_editor.utils.utils.decode(encoding)) stream.close() def console_loop(self): # 封装后的内容。 diff --git a/utils/debug/debuggerprocess.py b/utils/debug/debuggerprocess.py index 44b987bd..8a8761f9 100644 --- a/utils/debug/debuggerprocess.py +++ b/utils/debug/debuggerprocess.py @@ -6,6 +6,8 @@ import time import chardet from typing import List +import packages.code_editor.utils.utils + class PMProcess(): def __init__(self, args: List[str]): @@ -35,7 +37,7 @@ class PMProcess(): for line in iter(stream.readline, b''): if self.terminate: break encoding = chardet.detect(line)['encoding'] - queue.put(str(type) + line.decode(encoding)) + queue.put(str(type) + packages.code_editor.utils.utils.decode(encoding)) stream.close() def consoleLoop(self): # 封装后的内容。 diff --git a/utils/io/pmserial/pmqtreadserial.py b/utils/io/pmserial/pmqtreadserial.py index 642c7f68..a7a8ad59 100644 --- a/utils/io/pmserial/pmqtreadserial.py +++ b/utils/io/pmserial/pmqtreadserial.py @@ -6,6 +6,8 @@ import sys import time from PySide2.QtWidgets import QApplication, QMainWindow, QTextEdit from PySide2.QtCore import QObject, QThread, Signal + +import packages.code_editor.utils.utils from pmgwidgets import PMQThreadObject, QTextCursor from utils.io.pmserial.readserial import get_all_serial_names @@ -47,7 +49,7 @@ class PMGSerialWorker(QObject): chars = com.readline(com.inWaiting()) print(chars) try: - chars = chars.decode(self.coding) + chars = packages.code_editor.utils.utils.decode(self.coding) except UnicodeDecodeError: continue if self.splitter != '': diff --git a/utils/io/pmserial/readserial.py b/utils/io/pmserial/readserial.py index e3469a2c..8fc717fc 100644 --- a/utils/io/pmserial/readserial.py +++ b/utils/io/pmserial/readserial.py @@ -4,6 +4,8 @@ import time from collections import deque import threading +import packages.code_editor.utils.utils + def get_all_serial_names(): import serial.tools.list_ports @@ -20,7 +22,7 @@ if __name__ == '__main__': recv_queue = deque() while True: end_time = time.time() - s += com.readline(com.inWaiting()).decode('ascii') + s += packages.code_editor.utils.utils.decode('ascii') l = s.split('\n') if len(l) > 1: recv_queue.extend(l[:len(l) - 1]) -- Gitee From 1065b042dba0518c33a7d377efba34966d581519 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 09:35:33 +0800 Subject: [PATCH 034/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qtpyeditor/highlighters/python.py | 52 ++----------------- packages/code_editor/utils/font_config.py | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+), 49 deletions(-) create mode 100644 packages/code_editor/utils/font_config.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/highlighters/python.py b/packages/code_editor/codeeditor/qtpyeditor/highlighters/python.py index 72e8adb3..4b672bc2 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/highlighters/python.py +++ b/packages/code_editor/codeeditor/qtpyeditor/highlighters/python.py @@ -10,62 +10,16 @@ import time from typing import Dict, List, Set from PySide2.QtCore import QRegExp, Qt -from PySide2.QtGui import QSyntaxHighlighter, QTextCharFormat, QColor, QFont, QCursor, QBrush +from PySide2.QtGui import QSyntaxHighlighter, QTextCharFormat, QColor, QCursor, QBrush from PySide2.QtWidgets import QApplication +from packages.code_editor.utils.font_config import FontConfig + logger = logging.getLogger('code_editor.highlighters.python') color_scheme_intellij = {'keyword': '#101e96'} color_scheme_dark = {'keyword': '#b7602f'} -class FontConfig: - def __init__(self): - """ - color是16位标准的。 - bold有以下几种选择。 - QFont::Thin 0 0 - QFont::ExtraLight 12 12 - QFont::Light 25 25 - QFont::Normal 50 50 - QFont::Medium 57 57 - QFont::DemiBold 63 63 - QFont::Bold 75 75 - QFont::ExtraBold 81 81 - QFont::Black - """ - self.font_size = 15 - self.settings = {'normal': {'color': Qt.black, 'bold': QFont.Normal}, - 'keyword': {'color': Qt.darkBlue, 'bold': QFont.ExtraBold}, - 'builtin': {'color': Qt.darkRed, 'bold': QFont.Normal}, - 'constant': {'color': Qt.darkGreen, 'bold': QFont.Normal}, - 'decorator': {'color': Qt.darkBlue, 'bold': QFont.Normal}, - 'comment': {'color': Qt.darkGreen, 'bold': QFont.Normal}, - 'string': {'color': Qt.darkYellow, 'bold': QFont.Normal}, - 'number': {'color': Qt.darkMagenta, 'bold': QFont.Normal}, - 'error': {'color': Qt.darkRed, 'bold': QFont.Normal}, - 'pyqt': {'color': Qt.darkCyan, 'bold': QFont.Normal} - } - # self.load_color_scheme(color_scheme_intellij) - - def load_color_scheme(self, scheme: Dict[str, str]): - for name in scheme: - assert name in self.settings.keys() - self.set_font_color(name, scheme[name]) - - def set_font_color(self, font_name: str, font_color: str): - assert font_name in self.settings.keys() - self.settings[font_name]['color'] = font_color - - def get_font_color(self, font_name: str): - return self.settings[font_name]['color'] - - def get_font_bold(self, font_name: str): - return self.settings[font_name]['bold'] - - def get_font_size(self) -> int: - return self.font_size - - class BaseHighlighter(QSyntaxHighlighter): ERROR = 1 WARNING = 2 diff --git a/packages/code_editor/utils/font_config.py b/packages/code_editor/utils/font_config.py new file mode 100644 index 00000000..9e5d649b --- /dev/null +++ b/packages/code_editor/utils/font_config.py @@ -0,0 +1,52 @@ +from typing import Dict + +from PySide2.QtCore import Qt +from PySide2.QtGui import QFont + + +class FontConfig: + def __init__(self): + """ + color是16位标准的。 + bold有以下几种选择。 + QFont::Thin 0 0 + QFont::ExtraLight 12 12 + QFont::Light 25 25 + QFont::Normal 50 50 + QFont::Medium 57 57 + QFont::DemiBold 63 63 + QFont::Bold 75 75 + QFont::ExtraBold 81 81 + QFont::Black + """ + self.font_size = 15 + self.settings = {'normal': {'color': Qt.black, 'bold': QFont.Normal}, + 'keyword': {'color': Qt.darkBlue, 'bold': QFont.ExtraBold}, + 'builtin': {'color': Qt.darkRed, 'bold': QFont.Normal}, + 'constant': {'color': Qt.darkGreen, 'bold': QFont.Normal}, + 'decorator': {'color': Qt.darkBlue, 'bold': QFont.Normal}, + 'comment': {'color': Qt.darkGreen, 'bold': QFont.Normal}, + 'string': {'color': Qt.darkYellow, 'bold': QFont.Normal}, + 'number': {'color': Qt.darkMagenta, 'bold': QFont.Normal}, + 'error': {'color': Qt.darkRed, 'bold': QFont.Normal}, + 'pyqt': {'color': Qt.darkCyan, 'bold': QFont.Normal} + } + # self.load_color_scheme(color_scheme_intellij) + + def load_color_scheme(self, scheme: Dict[str, str]): + for name in scheme: + assert name in self.settings.keys() + self.set_font_color(name, scheme[name]) + + def set_font_color(self, font_name: str, font_color: str): + assert font_name in self.settings.keys() + self.settings[font_name]['color'] = font_color + + def get_font_color(self, font_name: str): + return self.settings[font_name]['color'] + + def get_font_bold(self, font_name: str): + return self.settings[font_name]['bold'] + + def get_font_size(self) -> int: + return self.font_size -- Gitee From 4265fdff61c7aec7c7e958f59aaed6e6d515ebfe Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 09:38:52 +0800 Subject: [PATCH 035/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codeeditor/qtpyeditor/__init__.py | 2 +- .../qtpyeditor/highlighters/__init__.py | 6 ----- packages/code_editor/codeeditor/tabwidget.py | 2 +- .../code_editor/utils/highlighter/__init__.py | 0 .../utils/highlighter/base_highlighter.py | 11 ++++++++++ .../highlighter/python_highlighter.py} | 22 +++---------------- .../widgets/editors/python_editor.py | 2 +- .../widgets/text_edit/base_text_edit.py | 2 +- .../widgets/text_edit/python_text_edit.py | 2 +- 9 files changed, 19 insertions(+), 30 deletions(-) delete mode 100644 packages/code_editor/codeeditor/qtpyeditor/highlighters/__init__.py create mode 100644 packages/code_editor/utils/highlighter/__init__.py create mode 100644 packages/code_editor/utils/highlighter/base_highlighter.py rename packages/code_editor/{codeeditor/qtpyeditor/highlighters/python.py => utils/highlighter/python_highlighter.py} (93%) diff --git a/packages/code_editor/codeeditor/qtpyeditor/__init__.py b/packages/code_editor/codeeditor/qtpyeditor/__init__.py index 10c0349b..dbecfca4 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/__init__.py +++ b/packages/code_editor/codeeditor/qtpyeditor/__init__.py @@ -4,4 +4,4 @@ # @Email: 1295752786@qq.com # @File: __init__.py -from .highlighters import PythonHighlighter +from ...utils.highlighter.python_highlighter import PythonHighlighter diff --git a/packages/code_editor/codeeditor/qtpyeditor/highlighters/__init__.py b/packages/code_editor/codeeditor/qtpyeditor/highlighters/__init__.py deleted file mode 100644 index 84f16438..00000000 --- a/packages/code_editor/codeeditor/qtpyeditor/highlighters/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/1/18 8:53 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: __init__.py.py -from .python import PythonHighlighter diff --git a/packages/code_editor/codeeditor/tabwidget.py b/packages/code_editor/codeeditor/tabwidget.py index b929ad6e..3495e027 100644 --- a/packages/code_editor/codeeditor/tabwidget.py +++ b/packages/code_editor/codeeditor/tabwidget.py @@ -40,7 +40,7 @@ from PySide2.QtWidgets import QTabWidget, QFileDialog, QMessageBox, QApplication from flake8.main.application import Application import pmgwidgets -from packages.code_editor.codeeditor.qtpyeditor import PythonHighlighter +from packages.code_editor.utils.highlighter.python_highlighter import PythonHighlighter from pmgwidgets import PMDockObject, in_unit_test, PMGFileSystemWatchdog, UndoManager if TYPE_CHECKING or in_unit_test(): diff --git a/packages/code_editor/utils/highlighter/__init__.py b/packages/code_editor/utils/highlighter/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/code_editor/utils/highlighter/base_highlighter.py b/packages/code_editor/utils/highlighter/base_highlighter.py new file mode 100644 index 00000000..636229fc --- /dev/null +++ b/packages/code_editor/utils/highlighter/base_highlighter.py @@ -0,0 +1,11 @@ +from PySide2.QtGui import QSyntaxHighlighter, QColor + + +class BaseHighlighter(QSyntaxHighlighter): + ERROR = 1 + WARNING = 2 + HINT = 3 + DEHIGHLIGHT = 4 + + HIGHLIGHT_COLOR = {ERROR: QColor(255, 65, 65, 200), WARNING: QColor(255, 255, 65, 100), + HINT: QColor(155, 155, 155, 100), DEHIGHLIGHT: QColor(155, 155, 155, 100)} diff --git a/packages/code_editor/codeeditor/qtpyeditor/highlighters/python.py b/packages/code_editor/utils/highlighter/python_highlighter.py similarity index 93% rename from packages/code_editor/codeeditor/qtpyeditor/highlighters/python.py rename to packages/code_editor/utils/highlighter/python_highlighter.py index 4b672bc2..f5021971 100644 --- a/packages/code_editor/codeeditor/qtpyeditor/highlighters/python.py +++ b/packages/code_editor/utils/highlighter/python_highlighter.py @@ -1,8 +1,3 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/1/18 8:53 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: python.py import builtins import logging import sys @@ -10,24 +5,13 @@ import time from typing import Dict, List, Set from PySide2.QtCore import QRegExp, Qt -from PySide2.QtGui import QSyntaxHighlighter, QTextCharFormat, QColor, QCursor, QBrush +from PySide2.QtGui import QTextCharFormat, QBrush, QColor, QCursor, QSyntaxHighlighter from PySide2.QtWidgets import QApplication from packages.code_editor.utils.font_config import FontConfig +from packages.code_editor.utils.highlighter.base_highlighter import BaseHighlighter -logger = logging.getLogger('code_editor.highlighters.python') -color_scheme_intellij = {'keyword': '#101e96'} -color_scheme_dark = {'keyword': '#b7602f'} - - -class BaseHighlighter(QSyntaxHighlighter): - ERROR = 1 - WARNING = 2 - HINT = 3 - DEHIGHLIGHT = 4 - - HIGHLIGHT_COLOR = {ERROR: QColor(255, 65, 65, 200), WARNING: QColor(255, 255, 65, 100), - HINT: QColor(155, 155, 155, 100), DEHIGHLIGHT: QColor(155, 155, 155, 100)} +logger = logging.getLogger(__name__) class PythonHighlighter(BaseHighlighter): diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index ace3a3bb..b4631391 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -41,7 +41,7 @@ from PySide2.QtGui import QKeySequence, QCloseEvent from PySide2.QtWidgets import QAction, QShortcut, QMessageBox from yapf.yapflib import py3compat, yapf_api -from packages.code_editor.codeeditor.qtpyeditor import PythonHighlighter +from ...utils.highlighter.python_highlighter import PythonHighlighter from packages.code_editor.widgets.text_edit.python_text_edit import PMPythonCodeEdit from pmgwidgets import in_unit_test, PMGOneShotThreadRunner, run_python_file_in_terminal, parse_simplified_pmgjson, \ PMGPanelDialog 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 0c150f98..8678d8b0 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -14,7 +14,7 @@ from PySide2.QtWidgets import QPlainTextEdit, QWidget, QApplication, QTextEdit import utils from packages.code_editor.codeeditor.grammar_analyzer import GrammarAnalyzer -from packages.code_editor.codeeditor.qtpyeditor import PythonHighlighter +from packages.code_editor.utils.highlighter.python_highlighter import PythonHighlighter from packages.code_editor.codeeditor.qtpyeditor.syntaxana import getIndent from packages.code_editor.widgets.auto_complete_dropdown.base_auto_complete_dropdown import \ BaseAutoCompleteDropdownWidget diff --git a/packages/code_editor/widgets/text_edit/python_text_edit.py b/packages/code_editor/widgets/text_edit/python_text_edit.py index 38dc7664..eced4c3d 100644 --- a/packages/code_editor/widgets/text_edit/python_text_edit.py +++ b/packages/code_editor/widgets/text_edit/python_text_edit.py @@ -6,7 +6,7 @@ from PySide2.QtCore import QPoint, QModelIndex from PySide2.QtGui import QTextCursor, QMouseEvent from PySide2.QtWidgets import QLabel -from packages.code_editor.codeeditor.qtpyeditor import PythonHighlighter +from packages.code_editor.utils.highlighter.python_highlighter import PythonHighlighter from packages.code_editor.utils.auto_complete_thread.python_auto_complete import AutoCompThread from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit -- Gitee From 268906ab2e3f61c9c93498d40e13acb3ed2a3fb1 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 09:40:58 +0800 Subject: [PATCH 036/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codeeditor/qtpyeditor/__init__.py | 7 - .../codeeditor/qtpyeditor/syntaxana.py | 267 ------------------ .../utils/grammar_analyzer/__init__.py | 0 .../utils/grammar_analyzer/get_indent.py | 13 + .../widgets/text_edit/base_text_edit.py | 8 +- 5 files changed, 17 insertions(+), 278 deletions(-) delete mode 100644 packages/code_editor/codeeditor/qtpyeditor/__init__.py delete mode 100644 packages/code_editor/codeeditor/qtpyeditor/syntaxana.py create mode 100644 packages/code_editor/utils/grammar_analyzer/__init__.py create mode 100644 packages/code_editor/utils/grammar_analyzer/get_indent.py diff --git a/packages/code_editor/codeeditor/qtpyeditor/__init__.py b/packages/code_editor/codeeditor/qtpyeditor/__init__.py deleted file mode 100644 index dbecfca4..00000000 --- a/packages/code_editor/codeeditor/qtpyeditor/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/1/18 8:02 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: __init__.py - -from ...utils.highlighter.python_highlighter import PythonHighlighter diff --git a/packages/code_editor/codeeditor/qtpyeditor/syntaxana.py b/packages/code_editor/codeeditor/qtpyeditor/syntaxana.py deleted file mode 100644 index 1fb4b922..00000000 --- a/packages/code_editor/codeeditor/qtpyeditor/syntaxana.py +++ /dev/null @@ -1,267 +0,0 @@ -# -*- coding:utf-8 -*- -# @Time: 2021/1/18 8:49 -# @Author: Zhanyi Hou -# @Email: 1295752786@qq.com -# @File: syntaxana.py -# -*- coding: utf-8 -*- -""" -powered by NovalIDE -来自NovalIDE的词法分析模块 -作者:侯展意 -词法分析模块的重要组成单元、 -依靠各种正则表达式进行特征的提取。 - -""" -import re -from typing import List, Tuple, Dict - - -def getReplacingDic() -> Dict: - dic = {} - dic[','] = ',' - dic['。'] = '.' - dic[';'] = ';' - dic[':'] = ':' - dic['‘'] = "'" - dic['’'] = "'" - dic['“'] = '"' - dic['”'] = '"' - dic['【'] = '[' - dic['】'] = ']' - dic['('] = '(' - dic[')'] = ')' - return dic - - -def getIndent(s: str) -> Tuple[str, int]: - s = s.replace('\t', ' ') # tab替换成四个空格 - s = s.rstrip() - if len(s) > 0: - for i, ch in enumerate(s): - if ch != ' ': - return s[i:], i - return "", i + 1 - else: - return "", 0 - - -def removeComment(s: str) -> str: - pos = s.find('#') - if pos != -1: - return s[:pos] - else: - return s - - -def getStringContent(row): - pass - - -def removeStringContent(row: str) -> str: - row = row.replace('\"', '\'') - if row.count('\'') >= 2: - s = getAllFromRegex(regex=r'[\'](.*?)[\']', st=row) - for item in s: - row = row.replace('\'%s\'' % item, '\'\'') # 带着分号一起换掉。 - return row - else: - return row - - -def parseVarType(row: str): - getInfoFromRegex(r'[\'](.*?)[\']', row) - - -def getAllFromRegex(regex: str, st: str) -> List[str]: - foundList = re.findall(re.compile(regex, re.S), st) - - return foundList - - -def getInfoFromRegex(regex: str, st: str) -> str: # 从正则表达式中获取信息的函数。如果没有任何结果则返回0。 - foundList = re.findall(re.compile(regex, re.S), st) - item = '' - if foundList != []: - item = foundList[0] - return item - - -def getWordsFromString(s: str) -> list: - if s != '': - syms = s.split(',') # 用逗号分隔开。 - for i in range(len(syms)): - syms[i] = syms[i].strip() - return syms - else: - return [] - - -def countPar(row: str) -> Tuple[int, int, int]: # 检测三类括号的数量。 - lparNum = row.count('(') - rparNum = row.count(')') - lbraceNum = row.count('{') - rbraceNum = row.count('}') - lbracketNum = row.count('[') - rbracketNum = row.count(']') - - return lparNum - rparNum, lbraceNum - rbraceNum, lbracketNum - rbracketNum # 返回左括号数量减去右括号数量。 - - -def checkPar(row: str) -> int: - a, b, c = countPar(row) - if (a == 0) & (b == 0) & (c == 0): - return 1 - else: - if (a < 0) | (b < 0) | (c < 0): - return -1 - else: - return 0 - - -def getBracketedContent(row: str) -> Tuple[str, str, str]: # 获取任何类型括号最外层内部的东西。(不是小括号!!!) - # 返回值:一个表示括号类型的量,以及一个有关括号中内容的字符串,以及括号前的内容。 - lst = [-1, -1, -1] - symList = ['(', '[', '{'] - symListCouple = [')', ']', '}'] - length = len(row) - for i in range(len(lst)): - lst[i] = row.find(symList[i]) - if lst[i] == -1: - lst[i] = length - minVal = min(lst) - if minVal == length: # 说明根本没括号 - return '', '', row[:minVal] # 所以返回值不仅没有括号,还没有括号中的内容(废话),只是返回括号前面的东西。 - else: - pos = lst.index(minVal) # 获取最小值的索引 - regex = r'[%s](.*)[%s]' % (symList[pos], symListCouple[pos]) - return symList[pos], getInfoFromRegex(regex=regex, st=row), row[:minVal] - - -def getFuncArgs(row: str) -> List[str]: # 获取函数的输入参数。 - - s = getInfoFromRegex(regex=r'[(](.*)[)]', st=row) - - li = getWordsFromString(s) - - if len(li) > 0: - if li[0] == 'self': # 不允许函数的第一个参数名字叫self。 - li.pop(0) - for i in range(len(li)): - eqSymPos = li[i].find('=') - - if eqSymPos != -1: # 如果eqSymPos中有一个等号 - li[i] = li[i][:eqSymPos] # 那么将等号去除 - colonSymPos = li[i].find(':') - if colonSymPos != -1: - li[i] = li[i][:colonSymPos] - - return li - - -def getFuncName(row: str) -> str: # 获取函数的名称。 - return getInfoFromRegex(regex=r'def\s(.*?)[(]', st=row) # 注意,需要匹配函数名,其中还有个空格。 - - -def getLocalVarNames(row: str) -> List[str]: # 获取局部变量的名称。 - li = getInfoFromRegex(regex=r'(.*?)[=]', st=row) # 注意,需要匹配局部变量的名称,其中还有个空格。 - - words = getWordsFromString(li) - result = [] - for w in words: # 如果是函数的方法,则不可。 - if w.find('.') == -1: - result.append(w) - return result - - -def is_number(str_number: str) -> bool: - if (str_number.split(".")[0]).isdigit() or str_number.isdigit() or (str_number.split('-')[-1]).split(".")[ - -1].isdigit(): - return True - else: - return False - - -def getForVariables(row: str) -> List[int]: - """ - 获取for循环中定义的变量。 - """ - s = getInfoFromRegex(r'for(.*?)in', row) - s = s.strip() - return getWordsFromString(s) - - -def getVarType(row: str) -> str: - """ - 获取变量的类型,比如集合,数字等等。 - """ - bracket, content, outer = getBracketedContent(row) - li = outer.split('=') - if len(li) >= 1: - - if li[1].strip() == '': # 这种情况下为直接赋值的语句, - if bracket == '(': - return ':tuple' - elif bracket == '[': - return ':list' - else: - st = li[1].split(',')[0] - if is_number(st): - return ':number' - - return '' - - -class Row: - def __init__(self, pos: int, text: str, indent: int) -> None: - self.pos = pos - self.text = text - self.indent = indent - - def __repr__(self) -> str: - return 'row:' + repr(self.pos) + "\t indent:" + repr(self.indent) + "\t text:" + self.text + '\n' - - -def regularize(rawText: List[str]) -> List[Row]: - global kwdTuple, indexList, charStr - - f = rawText # 获取打开的文件数组,每个元素是一行。 - regularifiedText = '' - rowList = [] - currentRow = Row(0, '', 0) # 创建一个没有含义的对象,这样方便类型检查。 - inStaticFunction = False - inFunctionDefinition = False - skipLine = False - currentFuncIndent = 0 - currentIndent = 0 - funcIndent = 0 - - for i, l in enumerate(f): - l = removeStringContent(l) - l = removeComment(l) - - if skipLine == False: - row, currentIndent = getIndent(l) # 获取当前的行名和缩进,同时修剪掉行首的空格 - currentRow = Row(i, row, currentIndent) - rowList.append(currentRow) - - else: - currentRow.text += l.strip() # 如果判断出这一行还没有结束,就不用获取当前的缩进,直接缀连即可。 - rowList.append(Row(i, '', 0)) # 这一行相应的没有任何内容 - - cp = checkPar(currentRow.text) - - if cp == 0: # 如果括号不匹配,那么就再继续进行,直至寻找到符合要求的行为止。 - skipLine = True - if len(currentRow.text) >= 200: # 长度超出,强行退出。 - skipLine = False - continue - elif cp == -1: # 如果右边括号反倒更多,就跳出这种情况。 - skipLine = False - continue - else: - skipLine = False - return rowList - - -if __name__ == '__main__': - regularize(['', '', '']) diff --git a/packages/code_editor/utils/grammar_analyzer/__init__.py b/packages/code_editor/utils/grammar_analyzer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/code_editor/utils/grammar_analyzer/get_indent.py b/packages/code_editor/utils/grammar_analyzer/get_indent.py new file mode 100644 index 00000000..db0fa577 --- /dev/null +++ b/packages/code_editor/utils/grammar_analyzer/get_indent.py @@ -0,0 +1,13 @@ +from typing import Tuple + + +def get_indent(s: str) -> Tuple[str, int]: + s = s.replace('\t', ' ') # tab替换成四个空格 + s = s.rstrip() + if len(s) > 0: + for i, ch in enumerate(s): + if ch != ' ': + return s[i:], i + return "", i + 1 + else: + return "", 0 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 8678d8b0..b5d62bc7 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -15,7 +15,7 @@ from PySide2.QtWidgets import QPlainTextEdit, QWidget, QApplication, QTextEdit import utils from packages.code_editor.codeeditor.grammar_analyzer import GrammarAnalyzer from packages.code_editor.utils.highlighter.python_highlighter import PythonHighlighter -from packages.code_editor.codeeditor.qtpyeditor.syntaxana import getIndent +from packages.code_editor.utils.grammar_analyzer.get_indent import get_indent from packages.code_editor.widgets.auto_complete_dropdown.base_auto_complete_dropdown import \ BaseAutoCompleteDropdownWidget from packages.code_editor.widgets.text_edit.line_number_area import QLineNumberArea @@ -333,7 +333,7 @@ class PMBaseCodeEdit(QPlainTextEdit): """ with self.editing_block_cursor() as cursor: text = cursor.block().text() - text, indent = getIndent(text) + text, indent = get_indent(text) if text.endswith(':'): cursor.insertText('\n' + ' ' * (indent + 4)) else: @@ -363,7 +363,7 @@ class PMBaseCodeEdit(QPlainTextEdit): current_line = cursor.blockNumber() last_line = current_line while current_line <= end_line: - line_text, indent = getIndent(cursor.block().text()) + line_text, indent = get_indent(cursor.block().text()) if line_text.startswith('#'): cursor.movePosition( QTextCursor.NextCharacter, QTextCursor.MoveAnchor, indent) @@ -380,7 +380,7 @@ class PMBaseCodeEdit(QPlainTextEdit): cursor.movePosition(QTextCursor.StartOfLine) else: cursor.movePosition(QTextCursor.StartOfLine) - line_text, indent = getIndent(cursor.block().text()) + line_text, indent = get_indent(cursor.block().text()) if line_text.startswith('#'): cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.MoveAnchor, indent) -- Gitee From 9f4820a51bf00ba83681e912d5ff6ac481a1c0d4 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 09:42:56 +0800 Subject: [PATCH 037/108] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E4=BA=86=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E7=9A=84=E6=B2=A1=E6=9C=89=E7=94=A8=E5=88=B0?= =?UTF-8?q?=E7=9A=84=E6=A0=B7=E5=BC=8F=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codeeditor/themes/Material-Dark.xml | 781 ------------------ .../codeeditor/themes/Obsidian PyCs.xml | 758 ----------------- .../codeeditor/themes/tomorrow.xml | 740 ----------------- .../codeeditor/themes/tomorrow_night.xml | 763 ----------------- .../themes/tomorrow_night_bright.xml | 763 ----------------- 5 files changed, 3805 deletions(-) delete mode 100644 packages/code_editor/codeeditor/themes/Material-Dark.xml delete mode 100644 packages/code_editor/codeeditor/themes/Obsidian PyCs.xml delete mode 100644 packages/code_editor/codeeditor/themes/tomorrow.xml delete mode 100644 packages/code_editor/codeeditor/themes/tomorrow_night.xml delete mode 100644 packages/code_editor/codeeditor/themes/tomorrow_night_bright.xml diff --git a/packages/code_editor/codeeditor/themes/Material-Dark.xml b/packages/code_editor/codeeditor/themes/Material-Dark.xml deleted file mode 100644 index 2a2db6e5..00000000 --- a/packages/code_editor/codeeditor/themes/Material-Dark.xml +++ /dev/null @@ -1,781 +0,0 @@ - - - - - - - - @Override @SuppressWarnings @SafeVarargs @FunctionalInterface @Retention @Documented @Target @Inherited @Repeatable @Deprecated - toto titi - - - - - - - - - - - - - - - - - alignas alignof noexcept nullptr static_assert thread_local final override - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - var - - - if else for while - bool long int char - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - toto titi - - - - - - - - - - - - - - - synthesize - - - - - NSString NSObject NSView NSWindow NSArray NSNumber NSUserDefaults NSNotification NSNotificationCenter CALayer CGColorRef NSEvent NSPoint NSSize NSRect CGPoint CGSize CGRect CGFloat unichar NSSet NSDictionary NSMutableString - - - - - - - - - - - - - - - nvarchar uniqueidentifier - - ooooo - - - - - - - - - - - - - - - - - - - - - - - - - the_ID the_post have_posts wp_link_pages the_content - - - - $_POST $_GET $_SESSION - - - - - - - - - ooooo - alert appendChild arguments array blur checked childNodes className confirm dialogArguments event focus getElementById getElementsByTagName innerHTML keyCode length location null number parentNode push RegExp replace selectNodes selectSingleNode setAttribute split src srcElement test undefined value window - XmlUtil loadXmlString TopologyXmlTree NotificationArea loadXmlFile debug - bool long int char - bool long int char - - - - - - - - - - - - import - import - - - - - - - - - - import - import - - - - - - - - - - - - - - - - - if else for while - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool long int char - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - param @projectDescription projectDescription @param - - - - - - - - - - - - - - - - endfunction endif - - - - - - - - - - - - - - - - - - - ContentScroller - - - endfunction endif - - - - - - - - - - - - - - - - ContentScroller - - - onMotionChanged onMotionFinished Tween ImagesStrip ContentScroller mx transitions easing Sprite Point MouseEvent Event BitmapData Timer TimerEvent addEventListener event x y height width - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ContentScroller - - - onMotionChanged onMotionFinished Tween ImagesStrip ContentScroller mx transitions easing Sprite Point MouseEvent Event BitmapData Timer TimerEvent addEventListener event x y height width - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - raise - - - - - - - - - - - - - - - - - raise - - - - - - - - - - - - - - - - - - - - - - - True False - - - - - - - - - - - - - - - - - - True False - - - - - - - - - - - - - - - - - - if else for while - True False - bool long int char - - - - - - - - - - - - - - - - - if else for while - bool long int char - bool long int char - - - - - - - - - - - - - - - - if else for while - bool long int char - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if else for while - bool long int char - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if else for while - if else for while - bool long int char - - - - - - - - - - - - - - - if else for while - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/code_editor/codeeditor/themes/Obsidian PyCs.xml b/packages/code_editor/codeeditor/themes/Obsidian PyCs.xml deleted file mode 100644 index 2d26f13c..00000000 --- a/packages/code_editor/codeeditor/themes/Obsidian PyCs.xml +++ /dev/null @@ -1,758 +0,0 @@ - - - - - - - - @Override @SuppressWarnings @SafeVarargs @FunctionalInterface @Retention @Documented @Target @Inherited @Repeatable @Deprecated - toto titi - - - - - - - - - - - - - - - - - - alignas alignof noexcept nullptr static_assert thread_local final override - - - - - - - - - - - - - - - - - - - - - - - - - var - - - if else for while - bool long int char - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - toto titi - - - - - - - - - - - - - synthesize - - - - - NSString NSObject NSView NSWindow NSArray NSNumber NSUserDefaults NSNotification NSNotificationCenter CALayer CGColorRef NSEvent NSPoint NSSize NSRect CGPoint CGSize CGRect CGFloat unichar NSSet NSDictionary NSMutableString - - - - - nvarchar uniqueidentifier - - ooooo - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static - - - - $_POST $_GET $_SESSION - - - - - - - - - - - - - - ooooo - alert appendChild arguments array blur checked childNodes className confirm dialogArguments event focus getElementById getElementsByTagName innerHTML keyCode length location null number parentNode push RegExp replace selectNodes selectSingleNode setAttribute split src srcElement test undefined value window - XmlUtil loadXmlString TopologyXmlTree NotificationArea loadXmlFile debug - bool long int char - bool long int char - - - - - - - param @projectDescription projectDescription @param - param @projectDescription projectDescription @param - - - - - - import - import - - - - - - - - - - - - - - - - import - import - - - - - - - - - - - - - - - if else for while - bool long int char - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool long int char - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - param @projectDescription projectDescription @param - - - - - - - - - - - - - - - - - endfunction endif - - - - - - - - ContentScroller - - - endfunction endif - - - - - - - - - - - - - - - - - - ContentScroller - - - onMotionChanged onMotionFinished Tween ImagesStrip ContentScroller mx transitions easing Sprite Point MouseEvent Event BitmapData Timer TimerEvent addEventListener event x y height width - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ContentScroller - - - onMotionChanged onMotionFinished Tween ImagesStrip ContentScroller mx transitions easing Sprite Point MouseEvent Event BitmapData Timer TimerEvent addEventListener event x y height width - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - raise - - - - - - - - - - - - - - - - - - - - - - - - - True False - - - - - - - - - - - - - - - - True False - - - - - - - - - - - - - - - - - if else for while - if else for while - bool long int char - - - - - - - - - - - - - - - - - - - - - if else for while - bool long int char - bool long int char - - - - - - - - - - - - if else for while - bool long int char - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if else for while - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if else for while - if else for while - bool long int char - - - - - - - - - - - - - - if else for while - if else for while - bool long int char - - - - - - - - - - if else for while - bool long int char - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/code_editor/codeeditor/themes/tomorrow.xml b/packages/code_editor/codeeditor/themes/tomorrow.xml deleted file mode 100644 index b9ecf44e..00000000 --- a/packages/code_editor/codeeditor/themes/tomorrow.xml +++ /dev/null @@ -1,740 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - synthesize - - - - - NSString NSObject NSView NSWindow NSArray NSNumber NSUserDefaults NSNotification NSNotificationCenter CALayer CGColorRef NSEvent NSPoint NSSize NSRect CGPoint CGSize CGRect CGFloat unichar NSSet NSDictionary NSMutableString - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if else for while - bool long int char - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/code_editor/codeeditor/themes/tomorrow_night.xml b/packages/code_editor/codeeditor/themes/tomorrow_night.xml deleted file mode 100644 index a05c9229..00000000 --- a/packages/code_editor/codeeditor/themes/tomorrow_night.xml +++ /dev/null @@ -1,763 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - synthesize - - - - - NSString NSObject NSView NSWindow NSArray NSNumber NSUserDefaults NSNotification NSNotificationCenter CALayer CGColorRef NSEvent NSPoint NSSize NSRect CGPoint CGSize CGRect CGFloat unichar NSSet NSDictionary NSMutableString - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if else for while - if else for while - if else for while - bool long int char - - - - - - - - - - - if else for while - bool long int char - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/code_editor/codeeditor/themes/tomorrow_night_bright.xml b/packages/code_editor/codeeditor/themes/tomorrow_night_bright.xml deleted file mode 100644 index 56fcdfe8..00000000 --- a/packages/code_editor/codeeditor/themes/tomorrow_night_bright.xml +++ /dev/null @@ -1,763 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - synthesize - - - - - NSString NSObject NSView NSWindow NSArray NSNumber NSUserDefaults NSNotification NSNotificationCenter CALayer CGColorRef NSEvent NSPoint NSSize NSRect CGPoint CGSize CGRect CGFloat unichar NSSet NSDictionary NSMutableString - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if else for while - if else for while - if else for while - bool long int char - - - - - - - - - - - if else for while - bool long int char - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file -- Gitee From 6646a0b78289de650b1ce148fb48676c69f4d9f3 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 09:43:24 +0800 Subject: [PATCH 038/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{codeeditor => utils/grammar_analyzer}/grammar_analyzer.py | 0 packages/code_editor/widgets/text_edit/base_text_edit.py | 2 +- tests/test_code_editor/test_grammer/test_python/test_bracket.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/code_editor/{codeeditor => utils/grammar_analyzer}/grammar_analyzer.py (100%) diff --git a/packages/code_editor/codeeditor/grammar_analyzer.py b/packages/code_editor/utils/grammar_analyzer/grammar_analyzer.py similarity index 100% rename from packages/code_editor/codeeditor/grammar_analyzer.py rename to packages/code_editor/utils/grammar_analyzer/grammar_analyzer.py 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 b5d62bc7..f1eeda19 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -13,7 +13,7 @@ from PySide2.QtGui import QFocusEvent, QTextCursor, QMouseEvent, QKeyEvent, QDra from PySide2.QtWidgets import QPlainTextEdit, QWidget, QApplication, QTextEdit import utils -from packages.code_editor.codeeditor.grammar_analyzer import GrammarAnalyzer +from packages.code_editor.utils.grammar_analyzer.grammar_analyzer import GrammarAnalyzer from packages.code_editor.utils.highlighter.python_highlighter import PythonHighlighter from packages.code_editor.utils.grammar_analyzer.get_indent import get_indent from packages.code_editor.widgets.auto_complete_dropdown.base_auto_complete_dropdown import \ diff --git a/tests/test_code_editor/test_grammer/test_python/test_bracket.py b/tests/test_code_editor/test_grammer/test_python/test_bracket.py index 2d5af053..1230e7b0 100644 --- a/tests/test_code_editor/test_grammer/test_python/test_bracket.py +++ b/tests/test_code_editor/test_grammer/test_python/test_bracket.py @@ -1,6 +1,6 @@ from unittest import TestCase -from packages.code_editor.codeeditor.grammar_analyzer import GrammarAnalyzer +from packages.code_editor.utils.grammar_analyzer.grammar_analyzer import GrammarAnalyzer func_def_1 = """ def add(x, y): -- Gitee From 94799e8c9c56bb7c8d781aaffbb8fd644662d853 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 09:44:37 +0800 Subject: [PATCH 039/108] =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=99=A8=E4=B8=AD=E7=9A=84=E4=B8=80=E4=BA=9B=E8=BF=87=E6=97=B6?= =?UTF-8?q?=E7=9A=84=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codeeditor/tests/get_flake8_output.py | 34 ------------------ .../codeeditor/tests/get_yapf_output.py | 33 ----------------- .../code_editor/codeeditor/tests/test_file.py | 29 --------------- .../codeeditor/tests/theme_xml_json.py | 36 ------------------- 4 files changed, 132 deletions(-) delete mode 100644 packages/code_editor/codeeditor/tests/get_flake8_output.py delete mode 100644 packages/code_editor/codeeditor/tests/get_yapf_output.py delete mode 100644 packages/code_editor/codeeditor/tests/test_file.py delete mode 100644 packages/code_editor/codeeditor/tests/theme_xml_json.py diff --git a/packages/code_editor/codeeditor/tests/get_flake8_output.py b/packages/code_editor/codeeditor/tests/get_flake8_output.py deleted file mode 100644 index 13b30bc2..00000000 --- a/packages/code_editor/codeeditor/tests/get_flake8_output.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2020/9/10 -@author: Irony -@site: https://pyqt.site https://github.com/PyQt5 -@email: 892768447@qq.com -@file: get_flake8_output -@description: get flake8 lint output -""" - -__Author__ = 'Irony' -__Copyright__ = 'Copyright (c) 2020' -__Version__ = 'Version 1.0' - -import io -import os -from contextlib import redirect_stdout - -from flake8.main import application - -with io.StringIO() as out, redirect_stdout(out): - app = application.Application() - app.initialize( - ['flake8', '--exit-zero', '--config', os.path.abspath('../.flake8')]) - app.run_checks([os.path.abspath('../syntaxana.py')]) - app.report() - ret = out.getvalue() -print(ret) -# report = Report(app) -# print(report.get_statistics('E')) -# print(report.get_statistics('W')) -# print(report.get_statistics('F')) diff --git a/packages/code_editor/codeeditor/tests/get_yapf_output.py b/packages/code_editor/codeeditor/tests/get_yapf_output.py deleted file mode 100644 index b5b7ba40..00000000 --- a/packages/code_editor/codeeditor/tests/get_yapf_output.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2020/9/10 -@author: Irony -@email: 892768447@qq.com -@file: get_yapf_output -@description: test yapf and get output -""" - -__Author__ = 'Irony' -__Copyright__ = 'Copyright (c) 2020' -__Version__ = 'Version 1.0' - -import os - -from yapf.yapflib import py3compat -from yapf.yapflib import yapf_api - -source = open(os.path.abspath('../pythoneditor.py'), 'rb').read().decode() -source = py3compat.removeBOM(source) - -try: - reformatted_source, _ = yapf_api.FormatCode( - source, - filename='pythoneditor.py', - print_diff=True, - style_config=os.path.abspath('../.style.yapf') - ) - print(reformatted_source) -except Exception as e: - raise e diff --git a/packages/code_editor/codeeditor/tests/test_file.py b/packages/code_editor/codeeditor/tests/test_file.py deleted file mode 100644 index 55eec7df..00000000 --- a/packages/code_editor/codeeditor/tests/test_file.py +++ /dev/null @@ -1,29 +0,0 @@ -import numpy as np -from numpy import * - -a = array([1, 2, 3, 4]) - - -def a(a, b, c): - if 1 in [1, 2, 3]: - a += 1 - pass - O = 0 - i = 123 - h = 123 - h = 456 - o = 0 - f = [o for o in range(5)] - print(o) - print(O, i) - pass - - -while (1): - if a > 0: - break - continue - - -def O1(): - pass diff --git a/packages/code_editor/codeeditor/tests/theme_xml_json.py b/packages/code_editor/codeeditor/tests/theme_xml_json.py deleted file mode 100644 index 6eb53b60..00000000 --- a/packages/code_editor/codeeditor/tests/theme_xml_json.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2020/10/4 -@author: Irony -@site: https://pyqt5.com , https://github.com/892768447 -@email: 892768447@qq.com -@file: theme_xml_json -@description: -""" - -# import json -# import xmltodict -# -# xml = open('../themes/Material-Dark.xml', 'rb').read().decode() -# style = xmltodict.parse(xml, encoding='utf-8') -# print(json.dumps(style, indent=4)) -# -# # 全局样式 -# for s in style['NotepadPlus']['GlobalStyles']['WidgetStyle']: -# name = s['@name'] -# fgColor = s['@fgColor'] -# bgColor = s['@bgColor'] -# print(name, fgColor, bgColor) - -from lxml import etree - -style = etree.parse('../themes/Material-Dark.xml') -# 全局样式 -for c in style.xpath('/NotepadPlus/GlobalStyles/WidgetStyle'): - print(c.get('name'), c.get('fgColor'), c.get('bgColor')) - -# 关键词高亮 -for w in style.xpath('/NotepadPlus/LexerStyles/LexerType[@name="python"]/WordsStyle'): - print(w.get('name'), w.get('fgColor'), w.get('bgColor')) -- Gitee From 5fe8e8a61d5d7d673f0c73754028c639bd691510 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 10:01:48 +0800 Subject: [PATCH 040/108] =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=B8=89=E4=B8=AA?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E7=94=A8=E5=88=B0=E7=9A=84=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/codeeditor/autocomplete.py | 127 ------------------ packages/code_editor/codeeditor/infer.py | 18 --- packages/code_editor/codeeditor/syntaxana.py | 71 ---------- 3 files changed, 216 deletions(-) delete mode 100644 packages/code_editor/codeeditor/autocomplete.py delete mode 100644 packages/code_editor/codeeditor/infer.py delete mode 100644 packages/code_editor/codeeditor/syntaxana.py diff --git a/packages/code_editor/codeeditor/autocomplete.py b/packages/code_editor/codeeditor/autocomplete.py deleted file mode 100644 index e9079c51..00000000 --- a/packages/code_editor/codeeditor/autocomplete.py +++ /dev/null @@ -1,127 +0,0 @@ -# encoding=utf-8 -import sys -import time -import logging -from PySide2.QtWidgets import QApplication, QMainWindow -from PySide2.QtCore import QObject, Signal, QThread, SignalInstance -from pmgwidgets import PMQThreadObject - -logger = logging.getLogger(__name__) - - -def jedi_init(): - """ - 初始化jedi,预加载相关模块 - :return: - """ - t0 = time.time() - import jedi - code = """ -from PyQt5.QtWidgets import * -from PyQt5.QtCore import * -from PyQt5.QtGui import * -from numpy import * -a - """ - script = jedi.Script(code) - script.complete(6, 1) - t1 = time.time() - logger.info('time elapsed for preload autocompletion modules/s:' + str(t1 - t0)) - - -class PMAutoCompWorker(QObject): - signal_autocomp_parsed: SignalInstance = Signal(list, int, int) - - def __init__(self): - super(PMAutoCompWorker, self).__init__() - self.quit = False - self.code: str = '' - self.line: int = 0 - self.col: int = 0 - self.last_code = '' - self.last_line = '' - self.last_col = '' - self.path: str = '' - - def set_scan_task(self, code: str, line: int, col: int, path: str = ''): - self.code = code - self.line = line - self.col = col - self.path = path - - def is_changed(self) -> bool: - return not (self.code == self.last_code and self.line == self.last_line and self.col == self.last_col) - - def work(self): - """ - 工作函数. - - :return: - """ - import jedi - try: - jedi_init() - except: - pass - while 1: - if self.quit: - break - if not self.is_changed(): - QThread.msleep(50) - continue - else: - script = jedi.Script(code=self.code, path=self.path) - - try: - line, col = self.line, self.col - completions = script.complete(line + 1, col + 1, fuzzy=True) - names = [c.name for c in completions] - self.signal_autocomp_parsed.emit(names, line, col) - - except ValueError as e: - pass - except Exception: - pass - self.last_col = self.col - self.last_line = self.line - self.last_code = self.code - - def on_exit(self): - self.quit = True - - -class PMPythonAutocompleter(PMQThreadObject): - def __init__(self): - autocomp_worker = PMAutoCompWorker() - super(PMPythonAutocompleter, self).__init__(parent=None, worker=autocomp_worker) - self.signal_autocomp_parsed = self.worker.signal_autocomp_parsed - - def terminate(self): - logger.warning('client quit') - self.worker.on_exit() - - if self.thread.isRunning(): - self.thread.quit() - self.thread.wait(500) - logger.warning('Autocompleter quit!!') - - -if __name__ == '__main__': - logger.setLevel(logging.INFO) - - - class W(QMainWindow): - - def closeEvent(self, a0) -> None: - super().closeEvent(a0) - m.terminate() - - - app = QApplication(sys.argv) - # w = W() - # w.show() - logger.warning("Start print log") - m = PMPythonAutocompleter() - m.worker.set_scan_task('i', 0, 0, '') - m.signal_autocomp_parsed.connect(lambda x: print(x)) - sys.exit(app.exec_()) diff --git a/packages/code_editor/codeeditor/infer.py b/packages/code_editor/codeeditor/infer.py deleted file mode 100644 index e343ca38..00000000 --- a/packages/code_editor/codeeditor/infer.py +++ /dev/null @@ -1,18 +0,0 @@ -from jedi import Script -source = ''' -import keyword - -class C: - pass - -class D: - pass - -x = D() - -def f(): - pass - -for variable in [keyword, f, C, x]: - variable -''' diff --git a/packages/code_editor/codeeditor/syntaxana.py b/packages/code_editor/codeeditor/syntaxana.py deleted file mode 100644 index 1d35e1a5..00000000 --- a/packages/code_editor/codeeditor/syntaxana.py +++ /dev/null @@ -1,71 +0,0 @@ -import re - -camelCaseRegex = re.compile(r'[\w|\d]([A-Z])') # 识别驼峰命名的正则表达式 -underLineCaseRegex = re.compile(r'_(\w)') # 识别下划线命名方法的正则表达式 - - -def find_camelcase_hint(word): - sub = re.findall(camelCaseRegex, word) - s = word[0] + ''.join(sub) - return s - - -def find_underline_hint(word): - sub = re.findall(underLineCaseRegex, word) - s = word[0] + ''.join(sub) - return s - - -def ifMatchCamelCase(word='', hint=''): - """ - 匹配形如‘isValidStudentNumber’这类的驼峰命名法。 - """ - global camelCaseRegex - if len(word) >= 1: - sub = re.findall(camelCaseRegex, word) - - s = word[0] + ''.join(sub) - if s.lower().startswith(hint): - return True - return False - - -def ifMatchUnderlineCase(word='', hint=''): - """ - 匹配形如‘is_valid_student_number’这类的下划线命名法。 - """ - global underLineCaseRegex - if len(word) >= 1: - sub = re.findall(underLineCaseRegex, word) - s = word[0] + ''.join(sub) - if s.lower().startswith(hint): - return True - return False - - -def filter_words(word_list, hint): - """ - 提取下划线或者驼峰命名的字符。 - """ - result_list = [] - for word in word_list: - if word.lower().startswith(hint): - result_list.append(word) - elif ifMatchCamelCase(word, hint): - result_list.append(word) - result_list.append(hint) - elif ifMatchUnderlineCase(word, hint): - result_list.append(word) - result_list.append(hint) - else: - continue - result_list = list(set(result_list)) - for i in range(len(result_list)): - result_list[i] = result_list[i].strip() + ' ' - return result_list - - -if __name__ == '__main__': - from pmgwidgets import pmgprint - sub = re.findall(camelCaseRegex, 'CcAsDs') - pmgprint(sub) -- Gitee From fc88c1f3aa6f06ca92a37d1b0b39af18058320a2 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 10:13:44 +0800 Subject: [PATCH 041/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E5=87=BA=E7=8E=B0=E6=96=B0=E7=9A=84bug=EF=BC=8C?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A3=80=E6=9F=A5=E5=8A=9F=E8=83=BD=E6=B6=88?= =?UTF-8?q?=E5=A4=B1=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{codeeditor/config => assets}/.flake8 | 0 .../{codeeditor/config => assets}/.style.yapf | 0 .../config => assets}/.style.yapf.zh | 0 packages/code_editor/codeeditor/tabwidget.py | 95 +----------------- .../{widgets => utils}/base_object.py | 2 +- .../utils/code_checker/__init__.py | 0 .../utils/code_checker/base_code_checker.py | 96 +++++++++++++++++++ .../base_auto_complete_dropdown.py | 2 +- .../widgets/editors/base_editor.py | 3 +- .../widgets/editors/python_editor.py | 2 +- 10 files changed, 103 insertions(+), 97 deletions(-) rename packages/code_editor/{codeeditor/config => assets}/.flake8 (100%) rename packages/code_editor/{codeeditor/config => assets}/.style.yapf (100%) rename packages/code_editor/{codeeditor/config => assets}/.style.yapf.zh (100%) rename packages/code_editor/{widgets => utils}/base_object.py (52%) create mode 100644 packages/code_editor/utils/code_checker/__init__.py create mode 100644 packages/code_editor/utils/code_checker/base_code_checker.py diff --git a/packages/code_editor/codeeditor/config/.flake8 b/packages/code_editor/assets/.flake8 similarity index 100% rename from packages/code_editor/codeeditor/config/.flake8 rename to packages/code_editor/assets/.flake8 diff --git a/packages/code_editor/codeeditor/config/.style.yapf b/packages/code_editor/assets/.style.yapf similarity index 100% rename from packages/code_editor/codeeditor/config/.style.yapf rename to packages/code_editor/assets/.style.yapf diff --git a/packages/code_editor/codeeditor/config/.style.yapf.zh b/packages/code_editor/assets/.style.yapf.zh similarity index 100% rename from packages/code_editor/codeeditor/config/.style.yapf.zh rename to packages/code_editor/assets/.style.yapf.zh diff --git a/packages/code_editor/codeeditor/tabwidget.py b/packages/code_editor/codeeditor/tabwidget.py index 3495e027..a125dafe 100644 --- a/packages/code_editor/codeeditor/tabwidget.py +++ b/packages/code_editor/codeeditor/tabwidget.py @@ -24,22 +24,17 @@ __version__ = '0.1' import cgitb import logging import os -import re import sys import time -from contextlib import redirect_stdout -from io import StringIO -from queue import Queue -from typing import List from typing import TYPE_CHECKING, Dict, Union, Tuple, Optional, Any -from PySide2.QtCore import QDir, QObject, Signal, QThread, QTemporaryFile, QTimer +from PySide2.QtCore import QDir, QThread, QTimer from PySide2.QtGui import QCloseEvent from PySide2.QtWidgets import QTabWidget, QFileDialog, QMessageBox, QApplication, QSizePolicy, QWidget, QComboBox # TODO to remove (use extensionlib) -from flake8.main.application import Application import pmgwidgets +from packages.code_editor.utils.code_checker.base_code_checker import CodeCheckWorker from packages.code_editor.utils.highlighter.python_highlighter import PythonHighlighter from pmgwidgets import PMDockObject, in_unit_test, PMGFileSystemWatchdog, UndoManager @@ -63,92 +58,6 @@ EDITOR_TYPE = Optional[Union['PMBaseEditor', 'PMCythonEditor', 'PMPythonEditor', logger = logging.getLogger(__name__) -class CodeCheckWorker(QObject): - """ - 代码检查 - """ - checked = Signal(object, list) - - def __init__(self, *args, **kwargs): - super(CodeCheckWorker, self).__init__(*args, **kwargs) - self._queue = Queue() - self._running = True - self.background_checking = True - - def add(self, widget: 'QsciScintilla', code: str): - """ - 添加需要检测的对象 - Args: - widget: 目标编辑器 - code: 目标编辑器代码 - - Returns: - - """ - self._queue.put_nowait((widget, code)) - while self._queue.qsize() > 3: - self._queue.get(False, 0) - - def stop(self): - """ - 停止线程标志 - """ - self._running = False - - def run(self): - """ - 代码检测工作函数 - Returns: - - """ - while 1: - if not self._running: - logger.info('code checker quit') - break - if not self.background_checking: - QThread.msleep(500) - continue - if self._queue.qsize() == 0: - QThread.msleep(500) - continue - try: - widget, code = self._queue.get(False, 0.5) - # 创建临时文件 - file = QTemporaryFile(self) - file.setAutoRemove(True) - if file.open(): - with open(file.fileName(), 'wb') as fp: - fp.write(code.encode()) - file.close() - # 使用flake8检测代码 - results = [] - with StringIO() as out, redirect_stdout(out): - app = Application() - app.initialize( - ['flake8', '--exit-zero', '--config', - os.path.join(os.path.dirname(__file__), 'config', '.flake8')]) - app.run_checks([file.fileName()]) - app.report() - results = out.getvalue().split('\n') - print(results) - new_results: List[Tuple[int, int, str, str]] = [] - for ret in results: - if re.search(r'\d+:\d+:[EFW]\d+:.*?', ret): - split_list = ret.split(':') - line_no = int(split_list[0]) - column = int(split_list[1]) - error_code = split_list[2] - error_type = split_list[3] - new_results.append((line_no, column, error_code, error_type)) - # results = [ret for ret in results if re.search(r'\d+:\d+:[EFW]\d+:.*?', ret)] - - self.checked.emit(widget, new_results) # 如果为空,也应该这样做。将空列表传入可以清空所有的标记。 - file.deleteLater() - del file - except Exception as e: - logger.warning(str(e)) - - class PMCodeEditTabWidget(QTabWidget, PMDockObject): """ 多标签页编辑器控件 diff --git a/packages/code_editor/widgets/base_object.py b/packages/code_editor/utils/base_object.py similarity index 52% rename from packages/code_editor/widgets/base_object.py rename to packages/code_editor/utils/base_object.py index 3cdfe901..bb9c1146 100644 --- a/packages/code_editor/widgets/base_object.py +++ b/packages/code_editor/utils/base_object.py @@ -1,4 +1,4 @@ -from ..settings import Settings +from packages.code_editor.settings import Settings class CodeEditorBaseObject: diff --git a/packages/code_editor/utils/code_checker/__init__.py b/packages/code_editor/utils/code_checker/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/packages/code_editor/utils/code_checker/base_code_checker.py b/packages/code_editor/utils/code_checker/base_code_checker.py new file mode 100644 index 00000000..0b63940b --- /dev/null +++ b/packages/code_editor/utils/code_checker/base_code_checker.py @@ -0,0 +1,96 @@ +import logging +import os +import re +from contextlib import redirect_stdout +from io import StringIO +from queue import Queue +from typing import List, Tuple + +from PySide2.QtCore import QObject, Signal, QThread, QTemporaryFile +from flake8.main.application import Application + +from ..base_object import CodeEditorBaseObject + +logger = logging.getLogger(__name__) + + +class CodeCheckWorker(CodeEditorBaseObject, QObject): + """ + 代码检查 + """ + checked = Signal(object, list) + + def __init__(self, *args, **kwargs): + super(CodeCheckWorker, self).__init__(*args, **kwargs) + self._queue = Queue() + self._running = True + self.background_checking = True + + def add(self, widget: 'QsciScintilla', code: str): + """ + 添加需要检测的对象 + Args: + widget: 目标编辑器 + code: 目标编辑器代码 + + Returns: + + """ + self._queue.put_nowait((widget, code)) + while self._queue.qsize() > 3: + self._queue.get(False, 0) + + def stop(self): + """ + 停止线程标志 + """ + self._running = False + + def run(self): + """ + 代码检测工作函数 + Returns: + + """ + while 1: + if not self._running: + logger.info('code checker quit') + break + if not self.background_checking: + QThread.msleep(500) + continue + if self._queue.qsize() == 0: + QThread.msleep(500) + continue + try: + widget, code = self._queue.get(False, 0.5) + # 创建临时文件 + file = QTemporaryFile(self) + file.setAutoRemove(True) + if file.open(): + with open(file.fileName(), 'wb') as fp: + fp.write(code.encode()) + file.close() + # 使用flake8检测代码 + with StringIO() as out, redirect_stdout(out): + app = Application() + app.initialize(['flake8', '--exit-zero', '--config', self.settings.assets_dir / '.flake8']) + app.run_checks([file.fileName()]) + app.report() + results = out.getvalue().split('\n') + new_results: List[Tuple[int, int, str, str]] = [] + for ret in results: + if re.search(r'\d+:\d+:[EFW]\d+:.*?', ret): + split_list = ret.split(':') + line_no = int(split_list[0]) + column = int(split_list[1]) + error_code = split_list[2] + error_type = split_list[3] + new_results.append((line_no, column, error_code, error_type)) + # results = [ret for ret in results if re.search(r'\d+:\d+:[EFW]\d+:.*?', ret)] + + self.checked.emit(widget, new_results) # 如果为空,也应该这样做。将空列表传入可以清空所有的标记。 + file.deleteLater() + del file + except Exception as e: + logger.warning(str(e)) diff --git a/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py b/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py index 399489d9..cfc28343 100644 --- a/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py +++ b/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py @@ -8,7 +8,7 @@ from PySide2.QtCore import Qt from PySide2.QtGui import QIcon, QPixmap, QKeyEvent from PySide2.QtWidgets import QTableWidget, QHeaderView, QTableWidgetItem -from ..base_object import CodeEditorBaseObject +from packages.code_editor.utils.base_object import CodeEditorBaseObject if TYPE_CHECKING: from jedi.api.classes import Completion as CompletionResult diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 44f21eb6..16724d89 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -36,6 +36,7 @@ from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon from PySide2.QtWidgets import QWidget, QMessageBox, QApplication, QLabel, QHBoxLayout, QVBoxLayout, QFileDialog, \ QShortcut, QAction +from packages.code_editor.utils.base_object import CodeEditorBaseObject from packages.code_editor.utils.utils import decode from packages.code_editor.widgets.dialogs.find_dialog import FindDialog from packages.code_editor.widgets.dialogs.goto_line_dialog import GotoLineDialog @@ -44,7 +45,7 @@ from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit logger = logging.getLogger(__name__) -class PMAbstractEditor(QWidget): +class PMAbstractEditor(CodeEditorBaseObject, QWidget): signal_focused_in: SignalInstance = None signal_new_requested: SignalInstance = Signal(str, int) # 文件路径;文件的打开模式(目前都是0) signal_save_requested: SignalInstance = Signal() diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index b4631391..3e8315a1 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -399,7 +399,7 @@ class PMPythonEditor(PMAbstractEditor): text, filename=self.filename(), # print_diff=True, - style_config=os.path.join(os.path.dirname(__file__), 'config', '.style.yapf') + style_config=str(self.settings.assets_dir / '.style.yapf') ) self.set_text(reformatted_source) # TODO 重构代码后需要保证光标的位置不动,可以考虑使用parso实现,这里目前的bug有些多 -- Gitee From 7e9b2f0595edea5efddb5f39f54e257c6e8e0664 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 10:17:11 +0800 Subject: [PATCH 042/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- features/ui/common/debug_process_with_pyqt.py | 2 +- packages/code_editor/codeeditor/__init__.py | 12 --- packages/code_editor/main.py | 2 +- .../tabwidget.py => widgets/tab_widget.py} | 75 ++----------------- 4 files changed, 8 insertions(+), 83 deletions(-) delete mode 100644 packages/code_editor/codeeditor/__init__.py rename packages/code_editor/{codeeditor/tabwidget.py => widgets/tab_widget.py} (92%) diff --git a/features/ui/common/debug_process_with_pyqt.py b/features/ui/common/debug_process_with_pyqt.py index 7ae052ea..a2e54a5c 100644 --- a/features/ui/common/debug_process_with_pyqt.py +++ b/features/ui/common/debug_process_with_pyqt.py @@ -18,7 +18,7 @@ from pmgwidgets import PMGJsonTree logger = logging.getLogger(__name__) if TYPE_CHECKING: from features.openprocess import PMProcess - from packages.code_editor.codeeditor.tabwidget import PMCodeEditTabWidget + from packages.code_editor.widgets.tab_widget import PMCodeEditTabWidget else: from features.openprocess import PMProcess diff --git a/packages/code_editor/codeeditor/__init__.py b/packages/code_editor/codeeditor/__init__.py deleted file mode 100644 index 0110c601..00000000 --- a/packages/code_editor/codeeditor/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2020/9/12 -@author: Irony -@site: https://pyqt5.com , https://github.com/892768447 -@email: 892768447@qq.com -@file: __init__.py -@description: -""" - diff --git a/packages/code_editor/main.py b/packages/code_editor/main.py index 5346fd4c..fbe2d1d9 100644 --- a/packages/code_editor/main.py +++ b/packages/code_editor/main.py @@ -15,7 +15,7 @@ trans_editor_tb.load(os.path.join(os.path.dirname(__file__), 'translations', f'q app.installTranslator(trans_editor_tb) from features.extensions.extensionlib import BaseInterface, BaseExtension -from .codeeditor.tabwidget import PMCodeEditTabWidget +from .widgets.tab_widget import PMCodeEditTabWidget from .debugger import PMDebugConsoleTabWidget from .toolbar import PMEditorToolbar from pmgwidgets import PMGPanel, load_json, dump_json diff --git a/packages/code_editor/codeeditor/tabwidget.py b/packages/code_editor/widgets/tab_widget.py similarity index 92% rename from packages/code_editor/codeeditor/tabwidget.py rename to packages/code_editor/widgets/tab_widget.py index a125dafe..e7e97e56 100644 --- a/packages/code_editor/codeeditor/tabwidget.py +++ b/packages/code_editor/widgets/tab_widget.py @@ -1,58 +1,19 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Created on 2020/9/7 -@author: Irony -@email: 892768447@qq.com -@file: widget -@description: Code Editor TabWidget - -协作者&维护者(Co-Author and supporter): -Zhanyi Hou -1295752786@qq.com - -注释为中文。若需要英文版翻译可将注释复制到翻译软件中。我们已经确认所有的注释都可以被翻译软件正确译为英文。 -All Comments are in Simplified Chinese.If you need English version, you can get the translation -via translation websites such as youdao or google. We had made sure that all comments can be correctly -translated into English by those websites. - -""" - -__version__ = '0.1' - -import cgitb import logging import os import sys import time -from typing import TYPE_CHECKING, Dict, Union, Tuple, Optional, Any +from typing import Dict, Optional, Tuple, Any, Union -from PySide2.QtCore import QDir, QThread, QTimer +from PySide2.QtCore import QTimer, QThread, QDir from PySide2.QtGui import QCloseEvent -from PySide2.QtWidgets import QTabWidget, QFileDialog, QMessageBox, QApplication, QSizePolicy, QWidget, QComboBox -# TODO to remove (use extensionlib) +from PySide2.QtWidgets import QTabWidget, QSizePolicy, QMessageBox, QFileDialog, QComboBox, QWidget import pmgwidgets from packages.code_editor.utils.code_checker.base_code_checker import CodeCheckWorker from packages.code_editor.utils.highlighter.python_highlighter import PythonHighlighter -from pmgwidgets import PMDockObject, in_unit_test, PMGFileSystemWatchdog, UndoManager - -if TYPE_CHECKING or in_unit_test(): - from ..widgets.editors.python_editor import PMPythonEditor - # from packages.code_editor.codeeditor.baseeditor import PMBaseEditor - # from packages.code_editor.codeeditor.cppeditor import PMCPPEditor - # from packages.code_editor.codeeditor.cythoneditor import PMCythonEditor - from packages.code_editor.widgets.editors.markdown_editor import PMMarkdownEditor - - from packages.code_editor.debugger import PMDebugConsoleTabWidget - # from packages.code_editor.codeeditor.ui.findinpath import FindInPathWidget -else: - # from codeeditor.baseeditor import PMBaseEditor - from ..widgets.editors.python_editor import PMPythonEditor - # from codeeditor.cppeditor import PMCPPEditor - # from codeeditor.cythoneditor import PMCythonEditor - from ..widgets.editors.markdown_editor import PMMarkdownEditor +from packages.code_editor.widgets.editors.markdown_editor import PMMarkdownEditor +from packages.code_editor.widgets.editors.python_editor import PMPythonEditor +from pmgwidgets import PMDockObject, UndoManager, PMGFileSystemWatchdog, in_unit_test EDITOR_TYPE = Optional[Union['PMBaseEditor', 'PMCythonEditor', 'PMPythonEditor', 'PMCPPEditor', 'PMMarkdownEditor']] logger = logging.getLogger(__name__) @@ -862,27 +823,3 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): # for editor_index in range(self.count()): # self.widget(editor_index).change_color_scheme(scheme) - - -if __name__ == '__main__': - cgitb.enable(format='text') - app = QApplication(sys.argv) - # app.setStyleSheet(""" - # PMBaseEditor { - # qproperty-theme: "Material-Dark"; - # } - # """) - - w = PMCodeEditTabWidget() - w.show() - w.set_color_scheme('dark') - w.setMinimumWidth(800) - w.setMinimumHeight(600) - w.setup_ui() - code_editor_root_directory = os.path.dirname(__file__) - w.slot_new_script(r'C:/Users/12957/documents/developing/Python/pyminer_workdir/app_designer.py') - w.currentWidget().goto_line(5) - # w.on_work_dir_changed(r'') - # w.slot_new_script(r'') - # w.on_work_dir_changed('') - sys.exit(app.exec_()) -- Gitee From b4b9476e8434015c1852e81c5262101639022c77 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 10:22:45 +0800 Subject: [PATCH 043/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{source => assets/icons}/lightening.png | Bin packages/code_editor/toolbar.py | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) rename packages/code_editor/{source => assets/icons}/lightening.png (100%) diff --git a/packages/code_editor/source/lightening.png b/packages/code_editor/assets/icons/lightening.png similarity index 100% rename from packages/code_editor/source/lightening.png rename to packages/code_editor/assets/icons/lightening.png diff --git a/packages/code_editor/toolbar.py b/packages/code_editor/toolbar.py index 9bd7196e..a4da52c8 100644 --- a/packages/code_editor/toolbar.py +++ b/packages/code_editor/toolbar.py @@ -1,9 +1,10 @@ import os +from packages.code_editor.utils.base_object import CodeEditorBaseObject from pmgwidgets import create_icon, PMGToolBar, QComboBox -class PMEditorToolbar(PMGToolBar): +class PMEditorToolbar(CodeEditorBaseObject, PMGToolBar): def __init__(self): super(PMEditorToolbar, self).__init__() @@ -45,7 +46,7 @@ class PMEditorToolbar(PMGToolBar): self.add_tool_button('button_instant_boot', self.tr('Instant Boot'), self.tr('Run script with common module preloaded to shorten interpterter startup-time.'), - create_icon(os.path.join(os.path.dirname(__file__), 'source', 'lightening.png'))) + create_icon(str(self.settings.icons_dir / 'lightening.png'))) # self.add_tool_button('button_debug', self.tr('Debug'), # create_icon(':/color/theme/default/icons/debug.svg')) interpreter_sel_widget = self.add_widget('combobox_interpreter', QComboBox()) -- Gitee From f1ba40bc9e5c58a5ae3e11713239e3231dda648c Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 10:45:55 +0800 Subject: [PATCH 044/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/main.py | 4 ++-- packages/code_editor/{ => widgets}/debugger.py | 0 packages/code_editor/{ => widgets}/toolbar.py | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/code_editor/{ => widgets}/debugger.py (100%) rename packages/code_editor/{ => widgets}/toolbar.py (100%) diff --git a/packages/code_editor/main.py b/packages/code_editor/main.py index fbe2d1d9..d10341ee 100644 --- a/packages/code_editor/main.py +++ b/packages/code_editor/main.py @@ -16,8 +16,8 @@ app.installTranslator(trans_editor_tb) from features.extensions.extensionlib import BaseInterface, BaseExtension from .widgets.tab_widget import PMCodeEditTabWidget -from .debugger import PMDebugConsoleTabWidget -from .toolbar import PMEditorToolbar +from packages.code_editor.widgets.debugger import PMDebugConsoleTabWidget +from packages.code_editor.widgets.toolbar import PMEditorToolbar from pmgwidgets import PMGPanel, load_json, dump_json __prevent_from_ide_optimization = PMEditorToolbar # 这一行的目的是防止导入被编辑器自动优化。 diff --git a/packages/code_editor/debugger.py b/packages/code_editor/widgets/debugger.py similarity index 100% rename from packages/code_editor/debugger.py rename to packages/code_editor/widgets/debugger.py diff --git a/packages/code_editor/toolbar.py b/packages/code_editor/widgets/toolbar.py similarity index 100% rename from packages/code_editor/toolbar.py rename to packages/code_editor/widgets/toolbar.py -- Gitee From f487787a108bba029cdd1f0135797a30e1ec71b5 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 10:50:45 +0800 Subject: [PATCH 045/108] =?UTF-8?q?=E5=AF=B9code=5Feditor=E5=86=85?= =?UTF-8?q?=E9=83=A8=E7=9A=84=E4=BB=A3=E7=A0=81=E4=BD=BF=E7=94=A8=E7=9B=B8?= =?UTF-8?q?=E5=AF=B9=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/main.py | 4 ++-- packages/code_editor/utils/base_object.py | 2 +- .../utils/highlighter/python_highlighter.py | 4 ++-- .../base_auto_complete_dropdown.py | 2 +- packages/code_editor/widgets/dialogs/find_dialog.py | 4 ++-- .../code_editor/widgets/dialogs/goto_line_dialog.py | 2 +- packages/code_editor/widgets/editors/base_editor.py | 10 +++++----- .../code_editor/widgets/editors/markdown_editor.py | 5 +++-- .../code_editor/widgets/editors/python_editor.py | 4 ++-- packages/code_editor/widgets/tab_widget.py | 12 ++++++------ .../code_editor/widgets/text_edit/base_text_edit.py | 11 +++++------ .../widgets/text_edit/python_text_edit.py | 6 +++--- packages/code_editor/widgets/toolbar.py | 4 +--- 13 files changed, 34 insertions(+), 36 deletions(-) diff --git a/packages/code_editor/main.py b/packages/code_editor/main.py index d10341ee..231000b4 100644 --- a/packages/code_editor/main.py +++ b/packages/code_editor/main.py @@ -16,8 +16,8 @@ app.installTranslator(trans_editor_tb) from features.extensions.extensionlib import BaseInterface, BaseExtension from .widgets.tab_widget import PMCodeEditTabWidget -from packages.code_editor.widgets.debugger import PMDebugConsoleTabWidget -from packages.code_editor.widgets.toolbar import PMEditorToolbar +from .widgets.debugger import PMDebugConsoleTabWidget +from .widgets.toolbar import PMEditorToolbar from pmgwidgets import PMGPanel, load_json, dump_json __prevent_from_ide_optimization = PMEditorToolbar # 这一行的目的是防止导入被编辑器自动优化。 diff --git a/packages/code_editor/utils/base_object.py b/packages/code_editor/utils/base_object.py index bb9c1146..3cdfe901 100644 --- a/packages/code_editor/utils/base_object.py +++ b/packages/code_editor/utils/base_object.py @@ -1,4 +1,4 @@ -from packages.code_editor.settings import Settings +from ..settings import Settings class CodeEditorBaseObject: diff --git a/packages/code_editor/utils/highlighter/python_highlighter.py b/packages/code_editor/utils/highlighter/python_highlighter.py index f5021971..9bd5e512 100644 --- a/packages/code_editor/utils/highlighter/python_highlighter.py +++ b/packages/code_editor/utils/highlighter/python_highlighter.py @@ -8,8 +8,8 @@ from PySide2.QtCore import QRegExp, Qt from PySide2.QtGui import QTextCharFormat, QBrush, QColor, QCursor, QSyntaxHighlighter from PySide2.QtWidgets import QApplication -from packages.code_editor.utils.font_config import FontConfig -from packages.code_editor.utils.highlighter.base_highlighter import BaseHighlighter +from ...utils.font_config import FontConfig +from ...utils.highlighter.base_highlighter import BaseHighlighter logger = logging.getLogger(__name__) diff --git a/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py b/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py index cfc28343..d3f0e9fe 100644 --- a/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py +++ b/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py @@ -8,7 +8,7 @@ from PySide2.QtCore import Qt from PySide2.QtGui import QIcon, QPixmap, QKeyEvent from PySide2.QtWidgets import QTableWidget, QHeaderView, QTableWidgetItem -from packages.code_editor.utils.base_object import CodeEditorBaseObject +from ...utils.base_object import CodeEditorBaseObject if TYPE_CHECKING: from jedi.api.classes import Completion as CompletionResult diff --git a/packages/code_editor/widgets/dialogs/find_dialog.py b/packages/code_editor/widgets/dialogs/find_dialog.py index 314bdd0e..47a20e33 100644 --- a/packages/code_editor/widgets/dialogs/find_dialog.py +++ b/packages/code_editor/widgets/dialogs/find_dialog.py @@ -5,8 +5,8 @@ from PySide2.QtWidgets import QDialog, QVBoxLayout, QPushButton, QHBoxLayout from pmgwidgets import PMGPanel if TYPE_CHECKING: - from packages.code_editor.widgets.editors.base_editor import PMGBaseEditor - from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit + from ...widgets.editors.base_editor import PMGBaseEditor + from ...widgets.text_edit.base_text_edit import PMBaseCodeEdit class FindDialog(QDialog): diff --git a/packages/code_editor/widgets/dialogs/goto_line_dialog.py b/packages/code_editor/widgets/dialogs/goto_line_dialog.py index 4e05091d..18eca009 100644 --- a/packages/code_editor/widgets/dialogs/goto_line_dialog.py +++ b/packages/code_editor/widgets/dialogs/goto_line_dialog.py @@ -2,7 +2,7 @@ from typing import Callable from PySide2.QtWidgets import QDialog, QMessageBox -from packages.code_editor.widgets.ui.gotoline import Ui_DialogGoto +from ..ui.gotoline import Ui_DialogGoto class GotoLineDialog(QDialog, Ui_DialogGoto): diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 16724d89..fdd9cbf7 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -36,11 +36,11 @@ from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon from PySide2.QtWidgets import QWidget, QMessageBox, QApplication, QLabel, QHBoxLayout, QVBoxLayout, QFileDialog, \ QShortcut, QAction -from packages.code_editor.utils.base_object import CodeEditorBaseObject -from packages.code_editor.utils.utils import decode -from packages.code_editor.widgets.dialogs.find_dialog import FindDialog -from packages.code_editor.widgets.dialogs.goto_line_dialog import GotoLineDialog -from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit +from ...utils.base_object import CodeEditorBaseObject +from ...utils.utils import decode +from ..dialogs.find_dialog import FindDialog +from ..dialogs.goto_line_dialog import GotoLineDialog +from ..text_edit.base_text_edit import PMBaseCodeEdit logger = logging.getLogger(__name__) diff --git a/packages/code_editor/widgets/editors/markdown_editor.py b/packages/code_editor/widgets/editors/markdown_editor.py index 798d1e6a..70bf938f 100644 --- a/packages/code_editor/widgets/editors/markdown_editor.py +++ b/packages/code_editor/widgets/editors/markdown_editor.py @@ -6,13 +6,14 @@ from PySide2.QtCore import Qt from PySide2.QtGui import QKeyEvent from PySide2.QtWidgets import QHBoxLayout, QMessageBox -from packages.code_editor.widgets.editors.base_editor import PMAbstractEditor from packages.qt_vditor.client import Window +from .base_editor import PMAbstractEditor class PMMarkdownEditor(PMAbstractEditor): def __init__(self, parent=None): super(PMMarkdownEditor, self).__init__(parent=parent) + # TODO 不应该直接引用qt_vditor,而是应该走interface self.textEdit = Window(url='http://127.0.0.1:5000/qt_vditor') self._path = '' self.setLayout(QHBoxLayout()) @@ -90,4 +91,4 @@ class PMMarkdownEditor(PMAbstractEditor): QMessageBox.warning(self, '未集成功能', '暂不支持运行含有两段及以上代码的Markdown文件!') return '' else: - return code_blocks[0] \ No newline at end of file + return code_blocks[0] diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index 3e8315a1..498c85b9 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -41,11 +41,11 @@ from PySide2.QtGui import QKeySequence, QCloseEvent from PySide2.QtWidgets import QAction, QShortcut, QMessageBox from yapf.yapflib import py3compat, yapf_api -from ...utils.highlighter.python_highlighter import PythonHighlighter -from packages.code_editor.widgets.text_edit.python_text_edit import PMPythonCodeEdit from pmgwidgets import in_unit_test, PMGOneShotThreadRunner, run_python_file_in_terminal, parse_simplified_pmgjson, \ PMGPanelDialog from .base_editor import PMAbstractEditor +from ..text_edit.python_text_edit import PMPythonCodeEdit +from ...utils.highlighter.python_highlighter import PythonHighlighter logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) diff --git a/packages/code_editor/widgets/tab_widget.py b/packages/code_editor/widgets/tab_widget.py index e7e97e56..7178b834 100644 --- a/packages/code_editor/widgets/tab_widget.py +++ b/packages/code_editor/widgets/tab_widget.py @@ -9,11 +9,12 @@ from PySide2.QtGui import QCloseEvent from PySide2.QtWidgets import QTabWidget, QSizePolicy, QMessageBox, QFileDialog, QComboBox, QWidget import pmgwidgets -from packages.code_editor.utils.code_checker.base_code_checker import CodeCheckWorker -from packages.code_editor.utils.highlighter.python_highlighter import PythonHighlighter -from packages.code_editor.widgets.editors.markdown_editor import PMMarkdownEditor -from packages.code_editor.widgets.editors.python_editor import PMPythonEditor from pmgwidgets import PMDockObject, UndoManager, PMGFileSystemWatchdog, in_unit_test +from .editors.markdown_editor import PMMarkdownEditor +from .editors.python_editor import PMPythonEditor +from .ui.findinpath import FindInPathWidget +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']] logger = logging.getLogger(__name__) @@ -762,7 +763,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): if language == 'python': breakpoints_str = '' for i in range(self.count()): - editor: PMBaseEditor = self.widget(i) + editor = self.widget(i) if editor.path().endswith('.py'): path = editor.path() break_points = editor.get_all_breakpoints() @@ -791,7 +792,6 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): return self.tr('Editor') def slot_find_in_path(self, word: str): - from packages.code_editor.widgets.ui import FindInPathWidget path = self.extension_lib.Program.get_work_dir() if not self.extension_lib.UI.widget_exists('find_in_path'): w: FindInPathWidget = self.extension_lib.insert_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 f1eeda19..abb3307d 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -13,12 +13,11 @@ from PySide2.QtGui import QFocusEvent, QTextCursor, QMouseEvent, QKeyEvent, QDra from PySide2.QtWidgets import QPlainTextEdit, QWidget, QApplication, QTextEdit import utils -from packages.code_editor.utils.grammar_analyzer.grammar_analyzer import GrammarAnalyzer -from packages.code_editor.utils.highlighter.python_highlighter import PythonHighlighter -from packages.code_editor.utils.grammar_analyzer.get_indent import get_indent -from packages.code_editor.widgets.auto_complete_dropdown.base_auto_complete_dropdown import \ - BaseAutoCompleteDropdownWidget -from packages.code_editor.widgets.text_edit.line_number_area import QLineNumberArea +from .line_number_area import QLineNumberArea +from ..auto_complete_dropdown.base_auto_complete_dropdown import BaseAutoCompleteDropdownWidget +from ...utils.grammar_analyzer.get_indent import get_indent +from ...utils.grammar_analyzer.grammar_analyzer import GrammarAnalyzer +from ...utils.highlighter.python_highlighter import PythonHighlighter logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) diff --git a/packages/code_editor/widgets/text_edit/python_text_edit.py b/packages/code_editor/widgets/text_edit/python_text_edit.py index eced4c3d..d336090b 100644 --- a/packages/code_editor/widgets/text_edit/python_text_edit.py +++ b/packages/code_editor/widgets/text_edit/python_text_edit.py @@ -6,9 +6,9 @@ from PySide2.QtCore import QPoint, QModelIndex from PySide2.QtGui import QTextCursor, QMouseEvent from PySide2.QtWidgets import QLabel -from packages.code_editor.utils.highlighter.python_highlighter import PythonHighlighter -from packages.code_editor.utils.auto_complete_thread.python_auto_complete import AutoCompThread -from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit +from .base_text_edit import PMBaseCodeEdit +from ...utils.auto_complete_thread.python_auto_complete import AutoCompThread +from ...utils.highlighter.python_highlighter import PythonHighlighter logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) diff --git a/packages/code_editor/widgets/toolbar.py b/packages/code_editor/widgets/toolbar.py index a4da52c8..dca76900 100644 --- a/packages/code_editor/widgets/toolbar.py +++ b/packages/code_editor/widgets/toolbar.py @@ -1,7 +1,5 @@ -import os - -from packages.code_editor.utils.base_object import CodeEditorBaseObject from pmgwidgets import create_icon, PMGToolBar, QComboBox +from ..utils.base_object import CodeEditorBaseObject class PMEditorToolbar(CodeEditorBaseObject, PMGToolBar): -- Gitee From 1c95c9af78fd281b1843d0e31ea5d0bd32b679b5 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 11:14:24 +0800 Subject: [PATCH 046/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../base_auto_complete_dropdown.py | 2 +- .../widgets/text_edit/base_text_edit.py | 19 +++++++------------ .../widgets/text_edit/python_text_edit.py | 5 +++-- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py b/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py index d3f0e9fe..a20c7fb2 100644 --- a/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py +++ b/packages/code_editor/widgets/auto_complete_dropdown/base_auto_complete_dropdown.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: from jedi.api.classes import Completion as CompletionResult from ..text_edit.base_text_edit import PMBaseCodeEdit -logger = logging.getLogger('code_editor.base_auto_comp_dropdown') +logger = logging.getLogger('code_editor.auto_complete_dropdown.base') logger.setLevel(logging.DEBUG) 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 abb3307d..4cd6fa87 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -5,7 +5,7 @@ import re import time from itertools import groupby from queue import Queue -from typing import Callable, Tuple, Dict, List +from typing import Callable, Tuple, Dict, List, TYPE_CHECKING from PySide2.QtCore import SignalInstance, Signal, Qt, QTimer, QModelIndex, QUrl, QRect from PySide2.QtGui import QFocusEvent, QTextCursor, QMouseEvent, QKeyEvent, QDragEnterEvent, QDropEvent, QPainter, \ @@ -19,6 +19,9 @@ from ...utils.grammar_analyzer.get_indent import get_indent from ...utils.grammar_analyzer.grammar_analyzer import GrammarAnalyzer from ...utils.highlighter.python_highlighter import PythonHighlighter +if TYPE_CHECKING: + from ..editors.python_editor import PMPythonEditor + logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -38,9 +41,8 @@ class PMBaseCodeEdit(QPlainTextEdit): textChanged: SignalInstance UPDATE_CODE_HIGHLIGHT = 1 - textCursor: Callable[[], QTextCursor] - doc_tab_widget: 'PMGPythonEditor' + doc_tab_widget: 'PMPythonEditor' highlighter: 'PythonHighlighter' def __init__(self, parent=None): @@ -194,15 +196,11 @@ class PMBaseCodeEdit(QPlainTextEdit): def on_text_changed(self): """文字发生改变时的方法""" - if self.modified: - pass - else: + if not self.modified: if self.toPlainText() != self._last_text: self.modified = True if self.text_modified_signal_allowed: self.signal_text_modified.emit() - else: - pass self._last_text = self.toPlainText() def _insert_autocomp(self, event: QModelIndex = None): @@ -613,10 +611,7 @@ class PMBaseCodeEdit(QPlainTextEdit): self.highlighter.registerHighlight(line, start, length, marker, hint) def clear_highlight(self): - """ - 清除高亮 - :return: - """ + """清除高亮""" self.highlighter.highlight_marks = {} def rehighlight(self): diff --git a/packages/code_editor/widgets/text_edit/python_text_edit.py b/packages/code_editor/widgets/text_edit/python_text_edit.py index d336090b..222dac53 100644 --- a/packages/code_editor/widgets/text_edit/python_text_edit.py +++ b/packages/code_editor/widgets/text_edit/python_text_edit.py @@ -5,6 +5,7 @@ from typing import List from PySide2.QtCore import QPoint, QModelIndex from PySide2.QtGui import QTextCursor, QMouseEvent from PySide2.QtWidgets import QLabel +from jedi.api.classes import Completion as CompletionResult from .base_text_edit import PMBaseCodeEdit from ...utils.auto_complete_thread.python_auto_complete import AutoCompThread @@ -37,7 +38,7 @@ class PMPythonCodeEdit(PMBaseCodeEdit): self.hint_widget.setVisible(False) self.hint_widget.setStyleSheet("background-color:#d8d8d8;padding:4px") - def on_autocomp_signal_received(self, text_cursor_content: tuple, completions: List['CompletionResult']): + def on_autocomp_signal_received(self, text_cursor_content: tuple, completions: List[CompletionResult]): """ 当收到自动补全提示信号时,执行的函数。 :param text_cursor_content: (row,col,hint_when_completion_triggered) @@ -46,7 +47,7 @@ class PMPythonCodeEdit(PMBaseCodeEdit): """ hint = self._get_hint() - logger.debug('hint_when_completion_triggered:{0},current_hint:{1}'.format(text_cursor_content[2], hint)) + logger.debug(f'hint_when_completion_triggered:{text_cursor_content[2]},current_hint:{hint}') if hint.startswith(text_cursor_content[2]): if len(completions) == 1: if completions[0].name == self._get_hint(): -- Gitee From 5c05749d20c2950a49ebf5906e3332e74970f59d Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 11:21:12 +0800 Subject: [PATCH 047/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../utils/code_checker/base_code_checker.py | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/packages/code_editor/utils/code_checker/base_code_checker.py b/packages/code_editor/utils/code_checker/base_code_checker.py index 0b63940b..f6f37f5f 100644 --- a/packages/code_editor/utils/code_checker/base_code_checker.py +++ b/packages/code_editor/utils/code_checker/base_code_checker.py @@ -1,12 +1,11 @@ import logging -import os import re from contextlib import redirect_stdout from io import StringIO from queue import Queue from typing import List, Tuple -from PySide2.QtCore import QObject, Signal, QThread, QTemporaryFile +from PySide2.QtCore import QObject, Signal, QThread, QTemporaryFile, SignalInstance from flake8.main.application import Application from ..base_object import CodeEditorBaseObject @@ -15,10 +14,8 @@ logger = logging.getLogger(__name__) class CodeCheckWorker(CodeEditorBaseObject, QObject): - """ - 代码检查 - """ - checked = Signal(object, list) + """代码检查""" + checked: SignalInstance = Signal(object, list) def __init__(self, *args, **kwargs): super(CodeCheckWorker, self).__init__(*args, **kwargs) @@ -27,31 +24,22 @@ class CodeCheckWorker(CodeEditorBaseObject, QObject): self.background_checking = True def add(self, widget: 'QsciScintilla', code: str): - """ - 添加需要检测的对象 + """添加需要检测的对象 + Args: widget: 目标编辑器 code: 目标编辑器代码 - - Returns: - """ self._queue.put_nowait((widget, code)) while self._queue.qsize() > 3: self._queue.get(False, 0) def stop(self): - """ - 停止线程标志 - """ + """通知线程需要退出,等待线程自行退出""" self._running = False def run(self): - """ - 代码检测工作函数 - Returns: - - """ + """代码检测工作函数""" while 1: if not self._running: logger.info('code checker quit') -- Gitee From 476e52901d7b21c2f7cfdd8709aef9a2f0f6e9bc Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 14:46:07 +0800 Subject: [PATCH 048/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/text_edit/base_text_edit.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) 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 4cd6fa87..76a90fbd 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -29,16 +29,17 @@ logger.setLevel(logging.DEBUG) class PMBaseCodeEdit(QPlainTextEdit): """ 与语言无关的编辑器相关操作应该定义在这里。 + 所有与TextEdit相关的操作都应该定义在这里,而对代码相关的操作应当定义在Editor中。 + 应当尽可能地避免子控件对父控件的调用,而是通过信号与槽的方式进行解耦。 """ # cursorPositionChanged = Signal() - signal_save: SignalInstance = Signal() - # Signal Focused in . But as it was too often triggered, I use click event instead. - signal_focused_in: SignalInstance = Signal(QFocusEvent) - signal_idle: SignalInstance = Signal() - signal_text_modified: SignalInstance = Signal() # If status changed from unmodified to modified, this signal emits. - signal_file_dropped: SignalInstance = Signal(str) - textChanged: SignalInstance + signal_save: SignalInstance = Signal() # 触发保存的事件,具体的保存操作交由给editor控件进行操作 + signal_focused_in: SignalInstance = Signal(QFocusEvent) # 使用click代替focus,因为focus in信号触发过于频繁 + signal_idle: SignalInstance = Signal() # 编辑器闲置,目前没有触发,也没有调用 + signal_text_modified: SignalInstance = Signal() # 当编辑器内的文本发生改变时,触发这个信号 + signal_file_dropped: SignalInstance = Signal(str) # 当一个文件被拖进这里时触发 + textChanged: SignalInstance # 暂不清楚为何定义了这个事件 UPDATE_CODE_HIGHLIGHT = 1 textCursor: Callable[[], QTextCursor] -- Gitee From f8d01e421a22fe09655f5e85b46237340473edf8 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 19:12:20 +0800 Subject: [PATCH 049/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/text_edit/base_text_edit.py | 74 ++++++++++++------- 1 file changed, 47 insertions(+), 27 deletions(-) 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 76a90fbd..003abf44 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -39,12 +39,21 @@ class PMBaseCodeEdit(QPlainTextEdit): signal_idle: SignalInstance = Signal() # 编辑器闲置,目前没有触发,也没有调用 signal_text_modified: SignalInstance = Signal() # 当编辑器内的文本发生改变时,触发这个信号 signal_file_dropped: SignalInstance = Signal(str) # 当一个文件被拖进这里时触发 - textChanged: SignalInstance # 暂不清楚为何定义了这个事件 + if TYPE_CHECKING: + # PySide2的内置事件 + textChanged: SignalInstance # 文本发生改变 + blockCountChanged: SignalInstance # 行号发生改变 + + # 获取光标 + textCursor: Callable[[], QTextCursor] + + # 其他类型提示 + doc_tab_widget: 'PMPythonEditor' + highlighter: 'PythonHighlighter' + + # 定义一系列的更新事件,不过只定义了这一个,提供了接口,其他的可以照例添加 UPDATE_CODE_HIGHLIGHT = 1 - textCursor: Callable[[], QTextCursor] - doc_tab_widget: 'PMPythonEditor' - highlighter: 'PythonHighlighter' def __init__(self, parent=None): super(PMBaseCodeEdit, self).__init__(parent) @@ -59,8 +68,7 @@ class PMBaseCodeEdit(QPlainTextEdit): self.font.setFamily(fontFamilies[0]) # 设置行号的字体 self.setFont(self.font) - self.lineNumberArea = QLineNumberArea(self) - self.blockCountChanged.connect(self.updateLineNumberAreaWidth) + self.line_number_area = QLineNumberArea(self) self.updateRequest.connect(self.updateLineNumberArea) self.cursorPositionChanged.connect(self.highlightCurrentLine) self.updateLineNumberAreaWidth(0) @@ -77,18 +85,30 @@ class PMBaseCodeEdit(QPlainTextEdit): self.text_modified_signal_allowed = True self.setTabChangesFocus(False) - self.textChanged.connect(self.on_text_changed) - + # 设置代码提示的弹出框 self.popup_hint_widget = BaseAutoCompleteDropdownWidget(self) - self.popup_hint_widget.doubleClicked.connect(self._insert_autocomp) self.popup_hint_widget.hide() self.setContextMenuPolicy(Qt.CustomContextMenu) + + # 用于更新界面的定时器,start的参数为毫秒 self.ui_update_timer = QTimer() self.ui_update_timer.start(300) - # noinspection PyUnresolvedReferences - self.ui_update_timer.timeout.connect(self.update_ui) + # 绑定各个信号 + self._bind_signals() + + # noinspection PyUnresolvedReferences + def _bind_signals(self): + # 定时触发的事件 + self.ui_update_timer.timeout.connect(self.update_ui) + # 文本更新后触发的事件 + self.textChanged.connect(self.on_text_changed) + # 文本发生改变后,保存当前时间 self.textChanged.connect(self.update_last_operation_time) + # 行数发生变化后,更新行号区域的宽度 + self.blockCountChanged.connect(self.updateLineNumberAreaWidth) + # 在代码提示框里面双击后,将自动补全的内容添加至代码 + self.popup_hint_widget.doubleClicked.connect(self._insert_autocomp) def lineNumberAreaWidth(self): digits = 1 @@ -104,16 +124,16 @@ class PMBaseCodeEdit(QPlainTextEdit): def updateLineNumberArea(self, rect, dy): if dy: - self.lineNumberArea.scroll(0, dy) + self.line_number_area.scroll(0, dy) else: - self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height()) + self.line_number_area.update(0, rect.y(), self.line_number_area.width(), rect.height()) if rect.contains(self.viewport().rect()): self.updateLineNumberAreaWidth(0) def resizeEvent(self, event): super().resizeEvent(event) cr = self.contentsRect() - self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) + self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) def highlightCurrentLine(self): extra_selections = [] @@ -134,7 +154,7 @@ class PMBaseCodeEdit(QPlainTextEdit): self.setExtraSelections(extra_selections) def lineNumberAreaPaintEvent(self, event): - painter = QPainter(self.lineNumberArea) + painter = QPainter(self.line_number_area) painter.fillRect(event.rect(), QColor(240, 240, 240)) @@ -153,7 +173,7 @@ class PMBaseCodeEdit(QPlainTextEdit): self.font.setPointSize(10) painter.setFont(self.font) - painter.drawText(0, top, self.lineNumberArea.width(), height, Qt.AlignCenter, number) + painter.drawText(0, top, self.line_number_area.width(), height, Qt.AlignCenter, number) block = block.next() top = bottom @@ -167,17 +187,17 @@ class PMBaseCodeEdit(QPlainTextEdit): def update_ui(self): if not self.isVisible(): return - if time.time() - self._last_operation > 0.5: - if self.update_request_queue.qsize() > 0: - - action: int = self.update_request_queue.get() - if action == self.UPDATE_CODE_HIGHLIGHT: - self.text_modified_signal_allowed = False - focus_widget: QWidget = QApplication.focusWidget() - self.highlighter.rehighlight() - self.text_modified_signal_allowed = True - if focus_widget is not None: - focus_widget.setFocus() + if self._last_operation <= 0.5: + return + if self.update_request_queue.empty(): + return + action: int = self.update_request_queue.get() + if action == self.UPDATE_CODE_HIGHLIGHT: + self.text_modified_signal_allowed = False + focus_widget: QWidget = QApplication.focusWidget() + self.highlighter.rehighlight() + self.text_modified_signal_allowed = True + focus_widget is not None and focus_widget.setFocus() def on_autocomp_signal_received(self, text_cursor_pos: tuple, completions: 'List[CompletionResult]'): """当收到自动补全提示信号时,执行的函数。""" -- Gitee From 3ae132bf0064accd1c70b708deeccbecbf6e66b5 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 19:35:45 +0800 Subject: [PATCH 050/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/text_edit/base_text_edit.py | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) 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 003abf44..f44e7590 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -44,6 +44,7 @@ class PMBaseCodeEdit(QPlainTextEdit): # PySide2的内置事件 textChanged: SignalInstance # 文本发生改变 blockCountChanged: SignalInstance # 行号发生改变 + updateRequest: SignalInstance # 当文本文档需要更新指定的矩形时触发 # 获取光标 textCursor: Callable[[], QTextCursor] @@ -69,9 +70,8 @@ class PMBaseCodeEdit(QPlainTextEdit): self.setFont(self.font) self.line_number_area = QLineNumberArea(self) - self.updateRequest.connect(self.updateLineNumberArea) self.cursorPositionChanged.connect(self.highlightCurrentLine) - self.updateLineNumberAreaWidth(0) + self.update_line_number_area_width(0) self._last_operation: float = 0.0 # 记录上次操作的时间 self.update_request_queue = Queue() @@ -101,57 +101,42 @@ class PMBaseCodeEdit(QPlainTextEdit): def _bind_signals(self): # 定时触发的事件 self.ui_update_timer.timeout.connect(self.update_ui) + # 文本滚动后,同步更新行号 + self.updateRequest.connect(self.update_line_number_area) # 文本更新后触发的事件 self.textChanged.connect(self.on_text_changed) # 文本发生改变后,保存当前时间 self.textChanged.connect(self.update_last_operation_time) # 行数发生变化后,更新行号区域的宽度 - self.blockCountChanged.connect(self.updateLineNumberAreaWidth) + self.blockCountChanged.connect(self.update_line_number_area_width) # 在代码提示框里面双击后,将自动补全的内容添加至代码 self.popup_hint_widget.doubleClicked.connect(self._insert_autocomp) def lineNumberAreaWidth(self): - digits = 1 - max_value = max(1, self.blockCount()) - while max_value >= 10: - max_value /= 10 - digits += 1 - space = 30 + self.fontMetrics().width('9') * digits - return space - - def updateLineNumberAreaWidth(self, _): - self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) - - def updateLineNumberArea(self, rect, dy): + return self.line_number_area_width + + @property + def line_number_area_width(self): + return 30 + self.fontMetrics().width('9') * len(str(max(1, self.blockCount()))) + + def update_line_number_area_width(self, *_): + self.setViewportMargins(self.line_number_area_width, 0, 0, 0) + + def update_line_number_area(self, rect, dy): if dy: self.line_number_area.scroll(0, dy) else: self.line_number_area.update(0, rect.y(), self.line_number_area.width(), rect.height()) if rect.contains(self.viewport().rect()): - self.updateLineNumberAreaWidth(0) - - def resizeEvent(self, event): - super().resizeEvent(event) - cr = self.contentsRect() - self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) - - def highlightCurrentLine(self): - extra_selections = [] + self.update_line_number_area_width(0) - if not self.isReadOnly(): - selection = QTextEdit.ExtraSelection() - - line_color = QColor(235, 252, 252) # 当前行背景色 - selection.format.setBackground(line_color) - - selection.format.setProperty(QTextFormat.FullWidthSelection, True) - - selection.cursor = self.textCursor() - selection.cursor.clearSelection() - - extra_selections.append(selection) + @property + def first_visible_line_number(self) -> int: + return self.firstVisibleBlock().blockNumber() - self.setExtraSelections(extra_selections) + @property + def current_line_number(self) -> int: + return self.textCursor().blockNumber() def lineNumberAreaPaintEvent(self, event): painter = QPainter(self.line_number_area) @@ -180,6 +165,29 @@ class PMBaseCodeEdit(QPlainTextEdit): bottom = top + self.blockBoundingRect(block).height() blockNumber += 1 + def resizeEvent(self, event): + super().resizeEvent(event) + cr = self.contentsRect() + self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) + + def highlightCurrentLine(self): + extra_selections = [] + + if not self.isReadOnly(): + selection = QTextEdit.ExtraSelection() + + line_color = QColor(235, 252, 252) # 当前行背景色 + selection.format.setBackground(line_color) + + selection.format.setProperty(QTextFormat.FullWidthSelection, True) + + selection.cursor = self.textCursor() + selection.cursor.clearSelection() + + extra_selections.append(selection) + + self.setExtraSelections(extra_selections) + def update_last_operation_time(self): """更新上一次操作的时间""" self._last_operation = time.time() @@ -514,14 +522,6 @@ class PMBaseCodeEdit(QPlainTextEdit): def is_modified_prop(self): return self.modified - @property - def first_visible_line_number(self) -> int: - return self.firstVisibleBlock().blockNumber() - - @property - def current_line_number(self) -> int: - return self.textCursor().blockNumber() - def go_to_line(self, line: int): tc = self.textCursor() pos = self.document().findBlockByNumber(line - 1).position() -- Gitee From e173a56dffce12da2af38f2274b125fa5a0a40be Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 19:42:39 +0800 Subject: [PATCH 051/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/text_edit/base_text_edit.py | 19 ++++++++----------- .../widgets/text_edit/line_number_area.py | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 13 deletions(-) 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 f44e7590..9767ac05 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -101,14 +101,19 @@ class PMBaseCodeEdit(QPlainTextEdit): def _bind_signals(self): # 定时触发的事件 self.ui_update_timer.timeout.connect(self.update_ui) + + # 行号相关的信号 # 文本滚动后,同步更新行号 - self.updateRequest.connect(self.update_line_number_area) + self.updateRequest.connect(self.line_number_area.slot_update) + self.updateRequest.connect(self.update_line_number_area_width) + # 行数发生变化后,更新行号区域的宽度 + # TODO 行号区域应当通过设置Layout来自动控制,而不是像这样手动控制 + self.blockCountChanged.connect(self.update_line_number_area_width) + # 文本更新后触发的事件 self.textChanged.connect(self.on_text_changed) # 文本发生改变后,保存当前时间 self.textChanged.connect(self.update_last_operation_time) - # 行数发生变化后,更新行号区域的宽度 - self.blockCountChanged.connect(self.update_line_number_area_width) # 在代码提示框里面双击后,将自动补全的内容添加至代码 self.popup_hint_widget.doubleClicked.connect(self._insert_autocomp) @@ -122,14 +127,6 @@ class PMBaseCodeEdit(QPlainTextEdit): def update_line_number_area_width(self, *_): self.setViewportMargins(self.line_number_area_width, 0, 0, 0) - def update_line_number_area(self, rect, dy): - if dy: - self.line_number_area.scroll(0, dy) - else: - self.line_number_area.update(0, rect.y(), self.line_number_area.width(), rect.height()) - if rect.contains(self.viewport().rect()): - self.update_line_number_area_width(0) - @property def first_visible_line_number(self) -> int: return self.firstVisibleBlock().blockNumber() diff --git a/packages/code_editor/widgets/text_edit/line_number_area.py b/packages/code_editor/widgets/text_edit/line_number_area.py index 4f250e60..10150fd0 100644 --- a/packages/code_editor/widgets/text_edit/line_number_area.py +++ b/packages/code_editor/widgets/text_edit/line_number_area.py @@ -1,8 +1,17 @@ +from typing import TYPE_CHECKING + from PySide2.QtCore import QSize +from PySide2.QtGui import QPaintEvent from PySide2.QtWidgets import QWidget +if TYPE_CHECKING: + from packages.code_editor.widgets.text_edit.base_text_edit import PMBaseCodeEdit + class QLineNumberArea(QWidget): + if TYPE_CHECKING: + codeEditor: PMBaseCodeEdit + def __init__(self, editor): super().__init__(editor) self.codeEditor = editor @@ -10,5 +19,11 @@ class QLineNumberArea(QWidget): def sizeHint(self): return QSize(self.editor.lineNumberAreaWidth(), 0) - def paintEvent(self, event): - self.codeEditor.lineNumberAreaPaintEvent(event) \ No newline at end of file + def paintEvent(self, event: QPaintEvent): + self.codeEditor.lineNumberAreaPaintEvent(event) + + def slot_update(self, rect, dy): + if dy: + self.scroll(0, dy) + else: + self.update(0, rect.y(), self.width(), rect.height()) -- Gitee From c93da289dc7cdf15e7ba250f9bf6fd46e84eee77 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 19:58:34 +0800 Subject: [PATCH 052/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/text_edit/base_text_edit.py | 5 +---- packages/code_editor/widgets/text_edit/line_number_area.py | 7 +++---- 2 files changed, 4 insertions(+), 8 deletions(-) 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 9767ac05..05f56ae9 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -117,9 +117,6 @@ class PMBaseCodeEdit(QPlainTextEdit): # 在代码提示框里面双击后,将自动补全的内容添加至代码 self.popup_hint_widget.doubleClicked.connect(self._insert_autocomp) - def lineNumberAreaWidth(self): - return self.line_number_area_width - @property def line_number_area_width(self): return 30 + self.fontMetrics().width('9') * len(str(max(1, self.blockCount()))) @@ -165,7 +162,7 @@ class PMBaseCodeEdit(QPlainTextEdit): def resizeEvent(self, event): super().resizeEvent(event) cr = self.contentsRect() - self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height())) + self.line_number_area.setGeometry(QRect(cr.left(), cr.top(), self.line_number_area_width, cr.height())) def highlightCurrentLine(self): extra_selections = [] diff --git a/packages/code_editor/widgets/text_edit/line_number_area.py b/packages/code_editor/widgets/text_edit/line_number_area.py index 10150fd0..ca1997ab 100644 --- a/packages/code_editor/widgets/text_edit/line_number_area.py +++ b/packages/code_editor/widgets/text_edit/line_number_area.py @@ -1,6 +1,5 @@ from typing import TYPE_CHECKING -from PySide2.QtCore import QSize from PySide2.QtGui import QPaintEvent from PySide2.QtWidgets import QWidget @@ -9,6 +8,9 @@ if TYPE_CHECKING: class QLineNumberArea(QWidget): + """ + 处理编辑器的行号区域相关的一些内容 + """ if TYPE_CHECKING: codeEditor: PMBaseCodeEdit @@ -16,9 +18,6 @@ class QLineNumberArea(QWidget): super().__init__(editor) self.codeEditor = editor - def sizeHint(self): - return QSize(self.editor.lineNumberAreaWidth(), 0) - def paintEvent(self, event: QPaintEvent): self.codeEditor.lineNumberAreaPaintEvent(event) -- Gitee From 4d9a63f6b0019785bd2c7ccc3495cadc7bc8093a Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 20:19:41 +0800 Subject: [PATCH 053/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/text_edit/base_text_edit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 05f56ae9..0c2f09d8 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -107,7 +107,7 @@ class PMBaseCodeEdit(QPlainTextEdit): self.updateRequest.connect(self.line_number_area.slot_update) self.updateRequest.connect(self.update_line_number_area_width) # 行数发生变化后,更新行号区域的宽度 - # TODO 行号区域应当通过设置Layout来自动控制,而不是像这样手动控制 + # TODO 行号区域应当通过设置Layout来自动控制,而现在是手动绘制在Editor里面的,需要进行调整 self.blockCountChanged.connect(self.update_line_number_area_width) # 文本更新后触发的事件 -- Gitee From 164207248b64ea397ffc156218ba1f8b7a9152ad Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 20:34:16 +0800 Subject: [PATCH 054/108] =?UTF-8?q?=E6=98=8E=E7=A1=AEBaseTextEdit=E4=B8=8E?= =?UTF-8?q?PythonTextEdit=E7=9A=84=E8=81=8C=E8=B4=A3=EF=BC=8C=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../base_auto_complete.py | 68 +++++++++++++++++++ .../python_auto_complete.py | 8 ++- .../widgets/editors/python_editor.py | 2 +- .../widgets/text_edit/base_text_edit.py | 31 +++++++-- .../widgets/text_edit/python_text_edit.py | 25 +------ 5 files changed, 104 insertions(+), 30 deletions(-) create mode 100644 packages/code_editor/utils/auto_complete_thread/base_auto_complete.py diff --git a/packages/code_editor/utils/auto_complete_thread/base_auto_complete.py b/packages/code_editor/utils/auto_complete_thread/base_auto_complete.py new file mode 100644 index 00000000..f14a2cc4 --- /dev/null +++ b/packages/code_editor/utils/auto_complete_thread/base_auto_complete.py @@ -0,0 +1,68 @@ +import logging +import re +import time + +from PySide2.QtCore import QThread, Signal + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +class BaseAutoCompleteThread(QThread): + """ + 当一段时间没有补全需求之后,后台自动补全线程会进入休眠模式。下一次补全请求则会唤醒该后台线程。 + """ + trigger = Signal(tuple, list) + + def __init__(self): + super(BaseAutoCompleteThread, self).__init__() + self.text = '' + self.text_cursor_pos = (0, 1) + self.activated = True + self.stop_flag = False + + def run(self): + text = '' + last_complete_time = time.time() + try: + import jedi + except ImportError: + print('Jedi not installed.install jedi for better auto-completion!') + return + while 1: + if self.stop_flag: + return + + if self.text == text: + if time.time() - last_complete_time >= 30: + self.activated = False + time.sleep(0.02 if self.activated else 0.1) + continue + + try: + row_text = self.text.splitlines()[self.text_cursor_pos[0] - 1] + hint = re.split( + '[.:;,?!\s \+ \- = \* \\ \/ \( \)\[\]\{\} ]', row_text)[-1] + content = ( + self.text_cursor_pos[0], self.text_cursor_pos[1], hint + ) + logger.debug('Text of current row:%s' % content[2]) + script = jedi.Script(self.text) + l = script.complete(*self.text_cursor_pos) + + except: + import traceback + traceback.print_exc() + l = [] + self.trigger.emit(content, l) + last_complete_time = time.time() + + self.activated = True + text = self.text + + def on_exit(self): + self.stop_flag = True + self.exit(0) + if self.isRunning(): + self.quit() + self.wait(500) diff --git a/packages/code_editor/utils/auto_complete_thread/python_auto_complete.py b/packages/code_editor/utils/auto_complete_thread/python_auto_complete.py index 7d784aba..d5e899b5 100644 --- a/packages/code_editor/utils/auto_complete_thread/python_auto_complete.py +++ b/packages/code_editor/utils/auto_complete_thread/python_auto_complete.py @@ -2,20 +2,22 @@ import logging import re import time -from PySide2.QtCore import QThread, Signal +from PySide2.QtCore import Signal + +from packages.code_editor.utils.auto_complete_thread.base_auto_complete import BaseAutoCompleteThread logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) -class AutoCompThread(QThread): +class PythonAutoCompleteThread(BaseAutoCompleteThread): """ 当一段时间没有补全需求之后,后台自动补全线程会进入休眠模式。下一次补全请求则会唤醒该后台线程。 """ trigger = Signal(tuple, list) def __init__(self): - super(AutoCompThread, self).__init__() + super(PythonAutoCompleteThread, self).__init__() self.text = '' self.text_cursor_pos = (0, 1) self.activated = True diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index 498c85b9..3ba2acab 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -92,7 +92,7 @@ class PMPythonEditor(PMAbstractEditor): def autocomp_stop(self): logger.info('autocomp stopped') - self.text_edit.autocomp_thread.on_exit() + self.text_edit.auto_complete_thread.on_exit() @cached_property def flake8_translations(self): 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 0c2f09d8..2f735512 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -5,12 +5,12 @@ import re import time from itertools import groupby from queue import Queue -from typing import Callable, Tuple, Dict, List, TYPE_CHECKING +from typing import Callable, Tuple, Dict, List, TYPE_CHECKING, Type from PySide2.QtCore import SignalInstance, Signal, Qt, QTimer, QModelIndex, QUrl, QRect from PySide2.QtGui import QFocusEvent, QTextCursor, QMouseEvent, QKeyEvent, QDragEnterEvent, QDropEvent, QPainter, \ QColor, QTextFormat, QFontDatabase, QFont -from PySide2.QtWidgets import QPlainTextEdit, QWidget, QApplication, QTextEdit +from PySide2.QtWidgets import QPlainTextEdit, QWidget, QApplication, QTextEdit, QLabel import utils from .line_number_area import QLineNumberArea @@ -20,7 +20,9 @@ from ...utils.grammar_analyzer.grammar_analyzer import GrammarAnalyzer from ...utils.highlighter.python_highlighter import PythonHighlighter 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 logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -32,6 +34,12 @@ class PMBaseCodeEdit(QPlainTextEdit): 所有与TextEdit相关的操作都应该定义在这里,而对代码相关的操作应当定义在Editor中。 应当尽可能地避免子控件对父控件的调用,而是通过信号与槽的方式进行解耦。 """ + # 各个子类的配置项 + highlighter_class: 'Type[BaseHighlighter]' = None # 语法高亮类 + auto_complete_thread_class: 'Type[BaseAutoCompleteThread]' = None # 自动补全类 + + highlighter: 'BaseHighlighter' = None + auto_complete_thread: 'BaseAutoCompleteThread' = None # cursorPositionChanged = Signal() signal_save: SignalInstance = Signal() # 触发保存的事件,具体的保存操作交由给editor控件进行操作 @@ -58,6 +66,21 @@ class PMBaseCodeEdit(QPlainTextEdit): def __init__(self, parent=None): super(PMBaseCodeEdit, self).__init__(parent) + if self.highlighter_class is not None: + self.highlighter = self.highlighter_class(self.document()) + if self.auto_complete_thread_class is not None: + self.auto_complete_thread = self.auto_complete_thread_class() + self.auto_complete_thread.trigger.connect(self.on_autocomp_signal_received) + self.auto_complete_thread.start() + + self.setTabChangesFocus(False) # 不允许Tab切换焦点,因Tab有更重要的切换缩进的作用 + self.setMouseTracking(True) # 启用鼠标跟踪,这允许在鼠标滑过该控件时捕捉到事件 + self.last_mouse_moved: float = time.time() # 最后一次鼠标事件时间 + + # 代码提示控件,使用一个Label作为代码提示 + self.hint_widget = QLabel('', parent=self) + self.hint_widget.setVisible(False) + self.hint_widget.setStyleSheet("background-color:#d8d8d8;padding:4px") fontId = QFontDatabase.addApplicationFont( os.path.join(utils.get_root_dir(), 'resources', 'fonts', 'SourceCodePro-Regular.ttf')) @@ -251,8 +274,8 @@ class PMBaseCodeEdit(QPlainTextEdit): if hint == '' and not nearby_text.endswith(('.', '\\\\', '/')): self.popup_hint_widget.hide_autocomp() return - self.autocomp_thread.text_cursor_pos = (position[0] + 1, position[1]) - self.autocomp_thread.text = self.toPlainText() + self.auto_complete_thread.text_cursor_pos = (position[0] + 1, position[1]) + self.auto_complete_thread.text = self.toPlainText() def autocomp_show(self, completions: list): raise NotImplementedError diff --git a/packages/code_editor/widgets/text_edit/python_text_edit.py b/packages/code_editor/widgets/text_edit/python_text_edit.py index 222dac53..f5a3a534 100644 --- a/packages/code_editor/widgets/text_edit/python_text_edit.py +++ b/packages/code_editor/widgets/text_edit/python_text_edit.py @@ -1,14 +1,12 @@ import logging -import time from typing import List from PySide2.QtCore import QPoint, QModelIndex from PySide2.QtGui import QTextCursor, QMouseEvent -from PySide2.QtWidgets import QLabel from jedi.api.classes import Completion as CompletionResult from .base_text_edit import PMBaseCodeEdit -from ...utils.auto_complete_thread.python_auto_complete import AutoCompThread +from ...utils.auto_complete_thread.python_auto_complete import PythonAutoCompleteThread from ...utils.highlighter.python_highlighter import PythonHighlighter logger = logging.getLogger(__name__) @@ -18,25 +16,8 @@ logger.setLevel(logging.DEBUG) class PMPythonCodeEdit(PMBaseCodeEdit): last_mouse_position: QPoint - def __init__(self, parent=None): - super(PMPythonCodeEdit, self).__init__(parent) - # self.setLineWrapMode(QPlainTextEdit.NoWrap) - # self.doc_tab_widget: 'PMGPythonEditor' = parent - # self.filename = '*' - # self.path = '' - # self.modified = True - self.highlighter = PythonHighlighter(self.document()) - self.setTabChangesFocus(False) - self.autocomp_thread = AutoCompThread() - self.autocomp_thread.trigger.connect(self.on_autocomp_signal_received) - self.autocomp_thread.start() - - self.setMouseTracking(True) - self.last_mouse_moved = time.time() - - self.hint_widget = QLabel('', parent=self) # 提示框标签。 - self.hint_widget.setVisible(False) - self.hint_widget.setStyleSheet("background-color:#d8d8d8;padding:4px") + auto_complete_thread_class = PythonAutoCompleteThread + highlighter_class = PythonHighlighter def on_autocomp_signal_received(self, text_cursor_content: tuple, completions: List[CompletionResult]): """ -- Gitee From c816b5df8c4b48aaad4af2eb2fd93fb370b41fa7 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 21:02:13 +0800 Subject: [PATCH 055/108] =?UTF-8?q?=E6=98=8E=E7=A1=AEBaseTextEdit=E4=B8=8E?= =?UTF-8?q?PythonTextEdit=E7=9A=84=E8=81=8C=E8=B4=A3=EF=BC=8C=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/text_edit/base_text_edit.py | 80 +++++++++++++++++-- .../widgets/text_edit/python_text_edit.py | 73 +---------------- 2 files changed, 75 insertions(+), 78 deletions(-) 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 2f735512..505987af 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -11,6 +11,7 @@ from PySide2.QtCore import SignalInstance, Signal, Qt, QTimer, QModelIndex, QUrl from PySide2.QtGui import QFocusEvent, QTextCursor, QMouseEvent, QKeyEvent, QDragEnterEvent, QDropEvent, QPainter, \ QColor, QTextFormat, QFontDatabase, QFont from PySide2.QtWidgets import QPlainTextEdit, QWidget, QApplication, QTextEdit, QLabel +from jedi.api.classes import Completion as CompletionResult import utils from .line_number_area import QLineNumberArea @@ -225,14 +226,19 @@ class PMBaseCodeEdit(QPlainTextEdit): focus_widget is not None and focus_widget.setFocus() def on_autocomp_signal_received(self, text_cursor_pos: tuple, completions: 'List[CompletionResult]'): - """当收到自动补全提示信号时,执行的函数。""" + """当收到自动补全提示信号时,执行的函数。 + + 为了避免自动补全对性能的影响,采用一个独立的线程进行自动补全。自动补全的过程如下: + 1. 提交自动补全请求; + 2. 自动补全线程进行补全; + 3. 本控件接收自动补全的结果,判断是否仍在之前的位置,如仍在,则补全。 + """ position = self.cursor_position if position[0] + 1 == text_cursor_pos[0] and position[1] == text_cursor_pos[1]: if len(completions) == 1: if completions[0].name == self._get_hint(): self.hide_autocomp() return - self.autocomp_show(completions) else: self.hide_autocomp() @@ -249,8 +255,31 @@ class PMBaseCodeEdit(QPlainTextEdit): self.signal_text_modified.emit() self._last_text = self.toPlainText() + # 代码提示 + cursor_pos = self.cursorRect() + self.popup_hint_widget.setGeometry( + cursor_pos.x() + 5, cursor_pos.y() + 20, + self.popup_hint_widget.sizeHint().width(), + self.popup_hint_widget.sizeHint().height()) + self._request_autocomp() + def _insert_autocomp(self, event: QModelIndex = None): - raise NotImplementedError + row = self.popup_hint_widget.currentRow() + if 0 <= row < self.popup_hint_widget.count(): + complete, word_type = self.popup_hint_widget.get_complete(row) + word = self.popup_hint_widget.get_text(row) + if not word.startswith(self._get_hint()): + return + comp = word[len(self._get_hint()):] + self.insertPlainText(comp) + if word_type == 'function': + self.insertPlainText('()') + tc = self.textCursor() + tc.movePosition(QTextCursor.PreviousCharacter) + self.setTextCursor(tc) + elif word_type == 'keyword': + self.insertPlainText(' ') + self.popup_hint_widget.hide() def _get_nearby_text(self): block_text = self.textCursor().block().text() @@ -277,9 +306,6 @@ class PMBaseCodeEdit(QPlainTextEdit): self.auto_complete_thread.text_cursor_pos = (position[0] + 1, position[1]) self.auto_complete_thread.text = self.toPlainText() - def autocomp_show(self, completions: list): - raise NotImplementedError - @property def cursor_position(self) -> Tuple[int, int]: return self.textCursor().blockNumber(), self.textCursor().columnNumber() @@ -667,3 +693,45 @@ class PMBaseCodeEdit(QPlainTextEdit): self.signal_file_dropped.emit(file) except Exception as exception: logger.exception(exception) + + def autocomp_show(self, completions: List['CompletionResult']): + result = [] + if len(completions) != 0: + self.popup_hint_widget.set_completions(completions) + else: + self.popup_hint_widget.hide() + self.popup_hint_widget.autocomp_list = result + + def mouseMoveEvent(self, event: QMouseEvent): + """ + 鼠标移动事件 + 移动到marker上的时候,便弹出提示框。 + 编辑器的提示位置。 + """ + super().mouseMoveEvent(event) + cursor: QTextCursor = self.cursorForPosition(event.pos()) + + # 如果代码量过大,则跳过 + if not len(self.toPlainText()) < 10000 * 120: + return + line, col = cursor.blockNumber(), cursor.positionInBlock() + flag = False + text = '' + if line in self.highlighter.highlight_marks: + marker_propertys = self.highlighter.highlight_marks.get(line) + for marker_property in marker_propertys: + start = marker_property[0] + if marker_property[1] == -1: + end = len(cursor.block().text()) + else: + end = start + marker_property[1] + if start <= col < end: + flag = True + text += marker_property[3] + '\n' + break + self.hint_widget.setGeometry(event.x(), event.y() + 20, + self.hint_widget.sizeHint().width(), self.hint_widget.sizeHint().height()) + + self.hint_widget.setText(text.strip()) + self.hint_widget.setVisible(flag) + event.ignore() diff --git a/packages/code_editor/widgets/text_edit/python_text_edit.py b/packages/code_editor/widgets/text_edit/python_text_edit.py index f5a3a534..fed28e12 100644 --- a/packages/code_editor/widgets/text_edit/python_text_edit.py +++ b/packages/code_editor/widgets/text_edit/python_text_edit.py @@ -1,8 +1,7 @@ import logging from typing import List -from PySide2.QtCore import QPoint, QModelIndex -from PySide2.QtGui import QTextCursor, QMouseEvent +from PySide2.QtCore import QPoint from jedi.api.classes import Completion as CompletionResult from .base_text_edit import PMBaseCodeEdit @@ -28,7 +27,6 @@ class PMPythonCodeEdit(PMBaseCodeEdit): """ hint = self._get_hint() - logger.debug(f'hint_when_completion_triggered:{text_cursor_content[2]},current_hint:{hint}') if hint.startswith(text_cursor_content[2]): if len(completions) == 1: if completions[0].name == self._get_hint(): @@ -37,72 +35,3 @@ class PMPythonCodeEdit(PMBaseCodeEdit): self.autocomp_show(completions) else: self.hide_autocomp() - - def on_text_changed(self): - super(PMPythonCodeEdit, self).on_text_changed() - cursor_pos = self.cursorRect() - self.popup_hint_widget.setGeometry( - cursor_pos.x() + 5, cursor_pos.y() + 20, - self.popup_hint_widget.sizeHint().width(), - self.popup_hint_widget.sizeHint().height()) - self._request_autocomp() - - def _insert_autocomp(self, model_index: QModelIndex = None): - row = self.popup_hint_widget.currentRow() - if 0 <= row < self.popup_hint_widget.count(): - complete, word_type = self.popup_hint_widget.get_complete(row) - word = self.popup_hint_widget.get_text(row) - if not word.startswith(self._get_hint()): - return - comp = word[len(self._get_hint()):] - self.insertPlainText(comp) - if word_type == 'function': - self.insertPlainText('()') - tc = self.textCursor() - tc.movePosition(QTextCursor.PreviousCharacter) - self.setTextCursor(tc) - elif word_type == 'keyword': - self.insertPlainText(' ') - self.popup_hint_widget.hide() - - def autocomp_show(self, completions: List['CompletionResult']): - result = [] - if len(completions) != 0: - self.popup_hint_widget.set_completions(completions) - else: - self.popup_hint_widget.hide() - self.popup_hint_widget.autocomp_list = result - - def mouseMoveEvent(self, event: QMouseEvent): - """ - 鼠标移动事件 - 移动到marker上的时候,便弹出提示框。 - 编辑器的提示位置。 - """ - super(PMPythonCodeEdit, self).mouseMoveEvent(event) - cursor: QTextCursor = self.cursorForPosition(event.pos()) - - # 如果代码量过大,则跳过 - if not len(self.toPlainText()) < 10000 * 120: - return - line, col = cursor.blockNumber(), cursor.positionInBlock() - flag = False - text = '' - if line in self.highlighter.highlight_marks: - marker_propertys = self.highlighter.highlight_marks.get(line) - for marker_property in marker_propertys: - start = marker_property[0] - if marker_property[1] == -1: - end = len(cursor.block().text()) - else: - end = start + marker_property[1] - if start <= col < end: - flag = True - text += marker_property[3] + '\n' - break - self.hint_widget.setGeometry(event.x(), event.y() + 20, - self.hint_widget.sizeHint().width(), self.hint_widget.sizeHint().height()) - - self.hint_widget.setText(text.strip()) - self.hint_widget.setVisible(flag) - event.ignore() -- Gitee From c727a5fb48a5a6c763346b9cb9aee911b017c15a Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 21:47:06 +0800 Subject: [PATCH 056/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ => assets}/translations/qt_zh_CN.qm | Bin .../{ => assets}/translations/qt_zh_CN.ts | 0 packages/code_editor/main.py | 2 +- packages/code_editor/settings.py | 1 + .../code_editor/widgets/editors/base_editor.py | 14 ++++++-------- .../widgets/editors/markdown_editor.py | 11 ----------- .../code_editor/widgets/editors/python_editor.py | 3 --- 7 files changed, 8 insertions(+), 23 deletions(-) rename packages/code_editor/{ => assets}/translations/qt_zh_CN.qm (100%) rename packages/code_editor/{ => assets}/translations/qt_zh_CN.ts (100%) diff --git a/packages/code_editor/translations/qt_zh_CN.qm b/packages/code_editor/assets/translations/qt_zh_CN.qm similarity index 100% rename from packages/code_editor/translations/qt_zh_CN.qm rename to packages/code_editor/assets/translations/qt_zh_CN.qm diff --git a/packages/code_editor/translations/qt_zh_CN.ts b/packages/code_editor/assets/translations/qt_zh_CN.ts similarity index 100% rename from packages/code_editor/translations/qt_zh_CN.ts rename to packages/code_editor/assets/translations/qt_zh_CN.ts diff --git a/packages/code_editor/main.py b/packages/code_editor/main.py index 231000b4..77bf2685 100644 --- a/packages/code_editor/main.py +++ b/packages/code_editor/main.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: app = QApplication.instance() trans_editor_tb = QTranslator() app.trans_editor_tb = trans_editor_tb -trans_editor_tb.load(os.path.join(os.path.dirname(__file__), 'translations', f'qt_{QLocale.system().name()}.qm')) +trans_editor_tb.load(os.path.join(os.path.dirname(__file__), 'assets/translations', f'qt_{QLocale.system().name()}.qm')) app.installTranslator(trans_editor_tb) from features.extensions.extensionlib import BaseInterface, BaseExtension diff --git a/packages/code_editor/settings.py b/packages/code_editor/settings.py index 9c06fe31..81be4386 100644 --- a/packages/code_editor/settings.py +++ b/packages/code_editor/settings.py @@ -9,3 +9,4 @@ class Settings: base_dir = Path(__file__).parent.absolute() assets_dir = base_dir / 'assets' icons_dir = assets_dir / 'icons' + translations_dir = assets_dir / 'translations' diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index fdd9cbf7..fb5b77dc 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -36,11 +36,11 @@ from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon from PySide2.QtWidgets import QWidget, QMessageBox, QApplication, QLabel, QHBoxLayout, QVBoxLayout, QFileDialog, \ QShortcut, QAction -from ...utils.base_object import CodeEditorBaseObject -from ...utils.utils import decode from ..dialogs.find_dialog import FindDialog from ..dialogs.goto_line_dialog import GotoLineDialog from ..text_edit.base_text_edit import PMBaseCodeEdit +from ...utils.base_object import CodeEditorBaseObject +from ...utils.utils import decode logger = logging.getLogger(__name__) @@ -59,11 +59,9 @@ class PMAbstractEditor(CodeEditorBaseObject, QWidget): def __init__(self, parent): app = QApplication.instance() - trans_editor_tb = QTranslator() - app.trans_editor_tb = trans_editor_tb - trans_editor_tb.load( - os.path.join(os.path.dirname(__file__), 'translations', 'qt_%s.qm' % QLocale.system().name())) - app.installTranslator(trans_editor_tb) + app.trans_editor_tb = QTranslator() + app.trans_editor_tb.load(str(self.settings.translations_dir / f'qt_{QLocale.system().name()}.qm')) + app.installTranslator(app.trans_editor_tb) super().__init__(parent) self.last_save_time = 0 self.extension_lib = None @@ -89,7 +87,7 @@ class PMAbstractEditor(CodeEditorBaseObject, QWidget): pass def set_lib(self, extension_lib): - pass + self.extension_lib = extension_lib def set_edit(self, edit: 'PMBaseCodeEdit'): self.text_edit = edit diff --git a/packages/code_editor/widgets/editors/markdown_editor.py b/packages/code_editor/widgets/editors/markdown_editor.py index 70bf938f..44f4974d 100644 --- a/packages/code_editor/widgets/editors/markdown_editor.py +++ b/packages/code_editor/widgets/editors/markdown_editor.py @@ -20,17 +20,6 @@ class PMMarkdownEditor(PMAbstractEditor): self.layout().setContentsMargins(0, 0, 0, 0) self.layout().addWidget(self.textEdit) - def set_lib(self, extension_lib): - """ - 获取extension_lib插件扩展库 - Args: - extension_lib: - - Returns: - - """ - self.extension_lib = extension_lib - def load_file(self, path: str): """ 加载文件 diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index 3ba2acab..7fafae96 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -105,9 +105,6 @@ class PMPythonEditor(PMAbstractEditor): self._init_actions() self._init_signals() - def set_lib(self, lib): - self.extension_lib = lib - def _init_actions(self) -> None: """初始化事件""" super(PMPythonEditor, self)._init_actions() -- Gitee From 78e053de9ff43cc5178e5d2a4f7846c10295e795 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 21:47:15 +0800 Subject: [PATCH 057/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 2 +- packages/code_editor/widgets/editors/markdown_editor.py | 4 ++-- packages/code_editor/widgets/editors/python_editor.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index fb5b77dc..bd5de73f 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -45,7 +45,7 @@ from ...utils.utils import decode logger = logging.getLogger(__name__) -class PMAbstractEditor(CodeEditorBaseObject, QWidget): +class BaseEditor(CodeEditorBaseObject, QWidget): signal_focused_in: SignalInstance = None signal_new_requested: SignalInstance = Signal(str, int) # 文件路径;文件的打开模式(目前都是0) signal_save_requested: SignalInstance = Signal() diff --git a/packages/code_editor/widgets/editors/markdown_editor.py b/packages/code_editor/widgets/editors/markdown_editor.py index 44f4974d..7746eb83 100644 --- a/packages/code_editor/widgets/editors/markdown_editor.py +++ b/packages/code_editor/widgets/editors/markdown_editor.py @@ -7,10 +7,10 @@ from PySide2.QtGui import QKeyEvent from PySide2.QtWidgets import QHBoxLayout, QMessageBox from packages.qt_vditor.client import Window -from .base_editor import PMAbstractEditor +from .base_editor import BaseEditor -class PMMarkdownEditor(PMAbstractEditor): +class PMMarkdownEditor(BaseEditor): def __init__(self, parent=None): super(PMMarkdownEditor, self).__init__(parent=parent) # TODO 不应该直接引用qt_vditor,而是应该走interface diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index 7fafae96..22f3d61e 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -43,7 +43,7 @@ from yapf.yapflib import py3compat, yapf_api from pmgwidgets import in_unit_test, PMGOneShotThreadRunner, run_python_file_in_terminal, parse_simplified_pmgjson, \ PMGPanelDialog -from .base_editor import PMAbstractEditor +from .base_editor import BaseEditor from ..text_edit.python_text_edit import PMPythonCodeEdit from ...utils.highlighter.python_highlighter import PythonHighlighter @@ -51,7 +51,7 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) -class PMPythonEditor(PMAbstractEditor): +class PMPythonEditor(BaseEditor): """ 自定义编辑器控件 """ -- Gitee From 7febcd4fc349b3a266b8965f93f1a0ed194c3d2e Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 21:47:28 +0800 Subject: [PATCH 058/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index bd5de73f..4b1f22d5 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -29,7 +29,7 @@ Created on 2020/9/7 import logging import os import time -from typing import Dict, Any, List, Callable +from typing import Dict, Any, List, Callable, TYPE_CHECKING from PySide2.QtCore import SignalInstance, Signal, QTranslator, QLocale, QDir, QCoreApplication, QPoint, Qt from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon @@ -55,7 +55,8 @@ class BaseEditor(CodeEditorBaseObject, QWidget): goto_line_dialog: 'GotoLineDialog' # 为PySide2内置函数添加代码提示 - layout: Callable[[], QVBoxLayout] + if TYPE_CHECKING: + layout: Callable[[], QVBoxLayout] def __init__(self, parent): app = QApplication.instance() -- Gitee From 05961d6fb2bf19d3656f83d360cebf3581fac04e Mon Sep 17 00:00:00 2001 From: wolfpan Date: Tue, 3 Aug 2021 21:48:26 +0800 Subject: [PATCH 059/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 4b1f22d5..f42f6fff 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -50,12 +50,12 @@ class BaseEditor(CodeEditorBaseObject, QWidget): signal_new_requested: SignalInstance = Signal(str, int) # 文件路径;文件的打开模式(目前都是0) signal_save_requested: SignalInstance = Signal() signal_request_find_in_path: SignalInstance = Signal(str) - text_edit: 'PMBaseCodeEdit' - find_dialog: 'FindDialog' - goto_line_dialog: 'GotoLineDialog' # 为PySide2内置函数添加代码提示 if TYPE_CHECKING: + text_edit: 'PMBaseCodeEdit' + find_dialog: 'FindDialog' + goto_line_dialog: 'GotoLineDialog' layout: Callable[[], QVBoxLayout] def __init__(self, parent): -- Gitee From ce58ca299bed3191ece498fda6bdce886ea35a8c Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 01:08:29 +0800 Subject: [PATCH 060/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/editors/base_editor.py | 46 ++++++++----------- .../widgets/editors/python_editor.py | 5 +- 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index f42f6fff..fc3c6010 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -33,9 +33,10 @@ from typing import Dict, Any, List, Callable, TYPE_CHECKING from PySide2.QtCore import SignalInstance, Signal, QTranslator, QLocale, QDir, QCoreApplication, QPoint, Qt from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon -from PySide2.QtWidgets import QWidget, QMessageBox, QApplication, QLabel, QHBoxLayout, QVBoxLayout, QFileDialog, \ +from PySide2.QtWidgets import QWidget, QMessageBox, QApplication, QLabel, QVBoxLayout, QFileDialog, \ QShortcut, QAction +from features.extensions.extensionlib.extension_lib import ExtensionLib from ..dialogs.find_dialog import FindDialog from ..dialogs.goto_line_dialog import GotoLineDialog from ..text_edit.base_text_edit import PMBaseCodeEdit @@ -46,16 +47,23 @@ logger = logging.getLogger(__name__) class BaseEditor(CodeEditorBaseObject, QWidget): + """ + + """ signal_focused_in: SignalInstance = None signal_new_requested: SignalInstance = Signal(str, int) # 文件路径;文件的打开模式(目前都是0) signal_save_requested: SignalInstance = Signal() signal_request_find_in_path: SignalInstance = Signal(str) + # 子控件的类型提示 + text_edit: 'PMBaseCodeEdit' + find_dialog: 'FindDialog' + goto_line_dialog: 'GotoLineDialog' + find_dialog: 'FindDialog' + extension_lib: 'ExtensionLib' + # 为PySide2内置函数添加代码提示 if TYPE_CHECKING: - text_edit: 'PMBaseCodeEdit' - find_dialog: 'FindDialog' - goto_line_dialog: 'GotoLineDialog' layout: Callable[[], QVBoxLayout] def __init__(self, parent): @@ -65,24 +73,14 @@ class BaseEditor(CodeEditorBaseObject, QWidget): app.installTranslator(app.trans_editor_tb) super().__init__(parent) self.last_save_time = 0 - self.extension_lib = None - self.last_save_time = 0 self._path = '' self._modified = False self._extension_names: List[str] = [] # 该编辑器支持的文件名 self._indicator_dict: Dict[str, str] = {} - self.line_number_area = QWidget() - self.line_number_area.setMaximumHeight(60) - self.line_number_area.setMinimumHeight(20) - self.status_label = QLabel() - line_number_area_layout = QHBoxLayout() - line_number_area_layout.addWidget(self.status_label) - line_number_area_layout.setContentsMargins(0, 0, 0, 0) - self.line_number_area.setLayout(line_number_area_layout) - self.modified_status_label = QLabel() - self.modified_status_label.setText('') - line_number_area_layout.addWidget(self.modified_status_label) + # 设置底部状态栏 + # 这里仅定义了一个对象,在设置edit对象时,会将这个对象挂载进edit中 + self.status_bar = QLabel() def set_shortcut(self): pass @@ -103,12 +101,12 @@ class BaseEditor(CodeEditorBaseObject, QWidget): layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self.layout().addWidget(self.text_edit) - self.layout().addWidget(self.status_label) + self.layout().addWidget(self.status_bar) def show_cursor_pos(self): row = self.text_edit.textCursor().block().blockNumber() col = self.text_edit.textCursor().columnNumber() - self.status_label.setText(f'行:{row + 1},列:{col + 1}') + self.status_bar.setText(f'行:{row + 1},列:{col + 1}') def slot_text_edit_focusedin(self, e): pass @@ -120,10 +118,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): pass def goto_line(self, line_no: int): - """ - 跳转到对应行列 - :return: - """ """跳转到对应行列""" doc: QTextDocument = self.text_edit.document() lines = doc.blockCount() @@ -168,11 +162,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): return ret def _init_signals(self) -> None: - """ - 初始化信号绑定 - - :return: None - """ + """初始化信号绑定""" # 绑定获得焦点信号 self.text_edit.signal_focused_in.connect(self.slot_text_edit_focusedin) diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index 22f3d61e..e74588dd 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -125,10 +125,7 @@ class PMPythonEditor(BaseEditor): self._action_remove_breakpoint.setVisible(False) def _init_signals(self) -> None: - """初始化信号绑定。 - - 无需super,因为基类已经初始化过了。 - """ + """初始化信号绑定。""" super(PMPythonEditor, self)._init_signals() self._shortcut_help.activated.connect(self.get_help) self._action_help.triggered.connect(self.get_help) -- Gitee From 5471707e3c6ad4d54703a260ca97847956f004da Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 01:08:40 +0800 Subject: [PATCH 061/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index fc3c6010..65e26593 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -82,9 +82,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # 这里仅定义了一个对象,在设置edit对象时,会将这个对象挂载进edit中 self.status_bar = QLabel() - def set_shortcut(self): - pass - def set_lib(self, extension_lib): self.extension_lib = extension_lib -- Gitee From 3dd9f0c2474b897c49677269a3bf88c6fd245e99 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 01:10:16 +0800 Subject: [PATCH 062/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 65e26593..5dfcfc57 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -50,7 +50,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): """ """ - signal_focused_in: SignalInstance = None + signal_focused_in: SignalInstance # 编辑器获得焦点 signal_new_requested: SignalInstance = Signal(str, int) # 文件路径;文件的打开模式(目前都是0) signal_save_requested: SignalInstance = Signal() signal_request_find_in_path: SignalInstance = Signal(str) -- Gitee From 0e1d78a78fe45e586db5ad3189747a74bc5f951b Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 01:21:10 +0800 Subject: [PATCH 063/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/editors/base_editor.py | 29 ++++++++++++++----- .../widgets/editors/python_editor.py | 16 ++++------ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 5dfcfc57..ccb82177 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -29,7 +29,7 @@ Created on 2020/9/7 import logging import os import time -from typing import Dict, Any, List, Callable, TYPE_CHECKING +from typing import Dict, Any, List, Callable, TYPE_CHECKING, Type from PySide2.QtCore import SignalInstance, Signal, QTranslator, QLocale, QDir, QCoreApplication, QPoint, Qt from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon @@ -48,15 +48,22 @@ logger = logging.getLogger(__name__) class BaseEditor(CodeEditorBaseObject, QWidget): """ - + 编辑器的各种操件应当由这个类及其子类进行管理,包括代码重构、代码缩进等内容。 + 快捷键也应当定义在这个类中。 + 这个类需要调用其text_edit属性提供的各种方法来实现对编辑器的操作。 + 关于具体的代码缩进调整等仅与代码文本有关的内容应当定义在TextEdit里面。 """ + + # 用于子类继承时的配置项 + text_edit_class: Type['PMBaseCodeEdit'] = None # 定义实际的代码编辑区对象 + text_edit: 'PMBaseCodeEdit' + signal_focused_in: SignalInstance # 编辑器获得焦点 signal_new_requested: SignalInstance = Signal(str, int) # 文件路径;文件的打开模式(目前都是0) signal_save_requested: SignalInstance = Signal() signal_request_find_in_path: SignalInstance = Signal(str) # 子控件的类型提示 - text_edit: 'PMBaseCodeEdit' find_dialog: 'FindDialog' goto_line_dialog: 'GotoLineDialog' find_dialog: 'FindDialog' @@ -67,21 +74,29 @@ class BaseEditor(CodeEditorBaseObject, QWidget): layout: Callable[[], QVBoxLayout] def __init__(self, parent): + + # 设置翻译文件 app = QApplication.instance() app.trans_editor_tb = QTranslator() app.trans_editor_tb.load(str(self.settings.translations_dir / f'qt_{QLocale.system().name()}.qm')) app.installTranslator(app.trans_editor_tb) super().__init__(parent) + + # 设置底部状态栏 + # 这里仅定义了一个对象,在设置edit对象时,会将这个对象挂载进edit中 + self.status_bar = QLabel() + + # 设置实际的代码编辑区 + if self.text_edit_class is not None: + self.text_edit = self.text_edit_class(self) + self.set_edit(self.text_edit) + self.last_save_time = 0 self._path = '' self._modified = False self._extension_names: List[str] = [] # 该编辑器支持的文件名 self._indicator_dict: Dict[str, str] = {} - # 设置底部状态栏 - # 这里仅定义了一个对象,在设置edit对象时,会将这个对象挂载进edit中 - self.status_bar = QLabel() - def set_lib(self, extension_lib): self.extension_lib = extension_lib diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index e74588dd..d751b817 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -34,7 +34,7 @@ import os import re from functools import cached_property from pathlib import Path -from typing import List, Tuple, Optional, Dict +from typing import List, Tuple, Optional from PySide2.QtCore import SignalInstance, Signal, Qt, QDir from PySide2.QtGui import QKeySequence, QCloseEvent @@ -62,9 +62,10 @@ class PMPythonEditor(BaseEditor): SHOW_AS_INFO = {'F841', 'F401', 'F403', 'F405', 'E303'} # 分别是变量导入未使用、定义未使用、使用*导入以及无法推断可能由*导入的类型。 help_runner: 'PMGOneShotThreadRunner' + text_edit_class = PMPythonCodeEdit + def __init__(self, parent=None): super(PMPythonEditor, self).__init__(parent) # , comment_string='# ') - self.set_edit(PMPythonCodeEdit(self)) self.browser_id = None self._extension_names.append('.py') self._parent = parent @@ -73,7 +74,6 @@ class PMPythonEditor(BaseEditor): # self._init_apis() # 编辑器主题 # self.slot_set_theme(self._theme) - self.extension_lib = None self.last_hint = '' self.prepare_actions() @@ -84,13 +84,7 @@ class PMPythonEditor(BaseEditor): # self.completer = PMPythonAutocompleter() # self.completer.signal_autocomp_parsed.connect(self.set_api) - def update_settings(self, settings: Dict[str, str]): - pass - - def open(self, path: str): - pass - - def autocomp_stop(self): + def stop_auto_complete_thread(self): logger.info('autocomp stopped') self.text_edit.auto_complete_thread.on_exit() @@ -558,7 +552,7 @@ class PMPythonEditor(BaseEditor): # self.completer.worker.set_scan_task(code=self.text_edit.text(), line=pos[0], col=pos[1], path=path) def closeEvent(self, event: QCloseEvent) -> None: - self.autocomp_stop() + self.stop_auto_complete_thread() def deleteLater(self) -> None: super(PMPythonEditor, self).deleteLater() -- Gitee From 1b6ccfaa4f8fd96d471dd4f6140254030b196f1c Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 01:23:18 +0800 Subject: [PATCH 064/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 5 ++--- .../code_editor/widgets/editors/python_editor.py | 13 ------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index ccb82177..06c46466 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -29,7 +29,7 @@ Created on 2020/9/7 import logging import os import time -from typing import Dict, Any, List, Callable, TYPE_CHECKING, Type +from typing import Dict, Any, Callable, TYPE_CHECKING, Type from PySide2.QtCore import SignalInstance, Signal, QTranslator, QLocale, QDir, QCoreApplication, QPoint, Qt from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon @@ -74,12 +74,12 @@ class BaseEditor(CodeEditorBaseObject, QWidget): layout: Callable[[], QVBoxLayout] def __init__(self, parent): - # 设置翻译文件 app = QApplication.instance() app.trans_editor_tb = QTranslator() app.trans_editor_tb.load(str(self.settings.translations_dir / f'qt_{QLocale.system().name()}.qm')) app.installTranslator(app.trans_editor_tb) + super().__init__(parent) # 设置底部状态栏 @@ -94,7 +94,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self.last_save_time = 0 self._path = '' self._modified = False - self._extension_names: List[str] = [] # 该编辑器支持的文件名 self._indicator_dict: Dict[str, str] = {} def set_lib(self, extension_lib): diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index d751b817..204456e4 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -67,23 +67,10 @@ class PMPythonEditor(BaseEditor): def __init__(self, parent=None): super(PMPythonEditor, self).__init__(parent) # , comment_string='# ') self.browser_id = None - self._extension_names.append('.py') self._parent = parent - # self._init_editor() - # self._init_lexer(QsciLexerPython(self.text_edit)) - # self._init_apis() - # 编辑器主题 - # self.slot_set_theme(self._theme) self.last_hint = '' self.prepare_actions() - # self.marker_update_timer = QTimer() - # self.marker_update_timer.start(1000) - # self.marker_update_timer.timeout.connect(self.set_marker_for_run) - - # self.completer = PMPythonAutocompleter() - # self.completer.signal_autocomp_parsed.connect(self.set_api) - def stop_auto_complete_thread(self): logger.info('autocomp stopped') self.text_edit.auto_complete_thread.on_exit() -- Gitee From 0752ae4ff27c48116a0b2a3419814df5af0288cf Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 01:23:57 +0800 Subject: [PATCH 065/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/python_editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index 204456e4..b1022756 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -356,7 +356,7 @@ class PMPythonEditor(BaseEditor): line, column = jedi_name_list[0].line, 0 path = jedi_name_list[0].module_path self.signal_goto_definition.emit(path, line, column) - logger.debug('{0},{1},{2},{3}'.format(jedi_name_list, path, line, column)) + logger.debug(f'{jedi_name_list},{path},{line},{column}') def slot_code_format(self): """ -- Gitee From 6f323f02909d34d0d6872663d603c0451b2d70ac Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 01:27:11 +0800 Subject: [PATCH 066/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 13 +++++-------- .../code_editor/widgets/editors/python_editor.py | 6 ------ 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 06c46466..eb741217 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -387,13 +387,10 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self.save() self.set_modified(False) - def default_save_path(self) -> str: - """ - 获取当前默认存储为的路径 - Default directory. - :return: - """ - return '' + @property + def default_save_directory(self): + """默认的存储路径""" + return self.extension_lib.Program.get_work_dir() def slot_text_changed(self) -> None: pass @@ -406,7 +403,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): """ QCoreApplication.translate = QCoreApplication.translate path = self._path.replace_selection(os.sep, '/') - default_dir = self.default_save_path() + default_dir = self.default_save_directory if path.startswith(QDir.tempPath().replace(os.sep, '/')): assert os.path.exists(default_dir) or default_dir == '' # 弹出对话框要求选择真实路径保存 diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index b1022756..7af35566 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -540,9 +540,3 @@ class PMPythonEditor(BaseEditor): def closeEvent(self, event: QCloseEvent) -> None: self.stop_auto_complete_thread() - - def deleteLater(self) -> None: - super(PMPythonEditor, self).deleteLater() - - def default_save_path(self) -> str: - return self.extension_lib.Program.get_work_dir() -- Gitee From 1f01a10fcc0821fb22fdacfdd0337710be342c0c Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 10:27:53 +0800 Subject: [PATCH 067/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/editors/base_editor.py | 11 +++-------- .../widgets/editors/python_editor.py | 19 +++---------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index eb741217..c7006ac1 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -179,8 +179,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self.text_edit.signal_focused_in.connect(self.slot_text_edit_focusedin) # 绑定光标变化信号 self.text_edit.cursorPositionChanged.connect(self.slot_cursor_position_changed) - # 绑定内容改变信号 - self.text_edit.textChanged.connect(self.slot_text_changed) # 绑定选中变化信号 self.text_edit.selectionChanged.connect(self.slot_selection_changed) # 绑定是否被修改信号 @@ -392,9 +390,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): """默认的存储路径""" return self.extension_lib.Program.get_work_dir() - def slot_text_changed(self) -> None: - pass - def save(self) -> bool: """保存文件时调用的方法 @@ -510,13 +505,13 @@ class BaseEditor(CodeEditorBaseObject, QWidget): raise ValueError('unrecognized input color scheme name %s' % color_scheme_name) def slot_code_format(self): - pass + """格式化代码""" def slot_code_run(self): - pass + """运行全部代码""" def slot_code_sel_run(self): - pass + """运行选中的代码""" def slot_cursor_position_changed(self): pass diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index 7af35566..3a1f3832 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -332,20 +332,8 @@ class PMPythonEditor(BaseEditor): self.help_runner = PMGOneShotThreadRunner(self.get_help_namelist) self.help_runner.signal_finished.connect(on_namelist_received) - def slot_about_close(self, save_all=False) -> QMessageBox.StandardButton: - """ - 是否需要关闭以及保存 - - :param save_all: 当整个窗口关闭时增加是否全部关闭 - :return:QMessageBox.StandardButton - """ - return super(PMPythonEditor, self).slot_about_close(save_all) - def slot_goto_definition(self): - """ - 获取函数的definition - :return: - """ + """转到函数的定义""" jedi_name_list = self.get_help_namelist() if len(jedi_name_list) > 0: # full_name: str = jedi_name_list[0].full_name @@ -359,10 +347,9 @@ class PMPythonEditor(BaseEditor): logger.debug(f'{jedi_name_list},{path},{line},{column}') def slot_code_format(self): - """ - 格式化代码 + """格式化代码 + 注意,格式化之前需要保存光标的位置,格式化之后再将光标设置回当前的位置。 - :return: """ text = self.text().strip() first_line = self.text_edit.textCursor().blockNumber() -- Gitee From 5a7a77e40132775a22d2be83fab07c705a91c13b Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 10:52:25 +0800 Subject: [PATCH 068/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/editors/python_editor.py | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index 3a1f3832..dfa16651 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -34,7 +34,7 @@ import os import re from functools import cached_property from pathlib import Path -from typing import List, Tuple, Optional +from typing import List, Tuple, Optional, TYPE_CHECKING, Callable from PySide2.QtCore import SignalInstance, Signal, Qt, QDir from PySide2.QtGui import QKeySequence, QCloseEvent @@ -63,6 +63,8 @@ class PMPythonEditor(BaseEditor): help_runner: 'PMGOneShotThreadRunner' text_edit_class = PMPythonCodeEdit + if TYPE_CHECKING: + tr: Callable[[str], str] def __init__(self, parent=None): super(PMPythonEditor, self).__init__(parent) # , comment_string='# ') @@ -105,6 +107,7 @@ class PMPythonEditor(BaseEditor): self._action_add_breakpoint.setVisible(False) self._action_remove_breakpoint.setVisible(False) + # noinspection PyUnresolvedReferences def _init_signals(self) -> None: """初始化信号绑定。""" super(PMPythonEditor, self)._init_signals() @@ -151,7 +154,7 @@ class PMPythonEditor(BaseEditor): self.text_edit.rehighlight() def get_message(self, msgid: str, msg: str, var_names_in_workspace: set) -> str: - """ + """获取报错信息 Args: msgid: error type such as 'F821' @@ -173,13 +176,13 @@ class PMPythonEditor(BaseEditor): try: return message % matches[0] except TypeError: - return message + '(%s)' % matches[0] + return f'{message}({matches[0]})' else: return message % matches except: import traceback traceback.print_exc() - return message + '(%s)' % matches + return f'{message}({matches})' else: return msg except: @@ -250,10 +253,7 @@ class PMPythonEditor(BaseEditor): return cell_text, cell_lines def get_help_in_console(self): - """ - 在console中获取帮助 - :return: - """ + """在console中获取帮助""" def show_help(help_namelist): if len(help_namelist) > 0: @@ -266,17 +266,6 @@ class PMPythonEditor(BaseEditor): if self.help_runner is None or self.help_runner.is_running() == False: self.help_runner = PMGOneShotThreadRunner(callback=show_help) - def get_hint(self): - pos = self.text_edit.get_cursor_position() - text = self.text(pos[0]) - try: - line = text[:pos[1] + 1] - except Exception as e: - logger.debug(e) - line = '' - hint: str = re.split(r'[;,:\./ \\!&\|\*\+\s\(\)\{\}\[\]]', line)[-1].strip() - return hint - def get_help_namelist(self) -> List: """ 获取函数的帮助 -- Gitee From 093f2947830a913d1037f1f5b24e820f90481f21 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 10:58:09 +0800 Subject: [PATCH 069/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/editors/base_editor.py | 30 ++----------------- .../widgets/editors/python_editor.py | 25 ++++------------ 2 files changed, 8 insertions(+), 47 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index c7006ac1..8c2aab6e 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -177,12 +177,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # 绑定获得焦点信号 self.text_edit.signal_focused_in.connect(self.slot_text_edit_focusedin) - # 绑定光标变化信号 - self.text_edit.cursorPositionChanged.connect(self.slot_cursor_position_changed) - # 绑定选中变化信号 - self.text_edit.selectionChanged.connect(self.slot_selection_changed) - # 绑定是否被修改信号 - # self.text_edit.signal_modification)_Changed.connect(self.slot_modification_changed) # 绑定右键菜单信号 self.text_edit.customContextMenuRequested.connect(self.slot_custom_context_menu_requested) # 绑定快捷键信号 @@ -216,11 +210,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # self._action_view_breakpoints.triggered.connect(self.view_break_points) def _init_actions(self) -> None: - """ - 初始化额外菜单项 - - :return: - """ + """初始化额外菜单项""" self.icon_path = os.path.dirname(os.path.dirname(__file__)) # 图标文件路径 self._action_format = QAction(QIcon(os.path.join(self.icon_path, 'icons/format.svg')), QCoreApplication.translate("PMGBaseEditor", 'Format Code'), @@ -279,9 +269,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): def autocomp(self): pass - def get_word_under_cursor(self): - pass - def set_text(self, text: str) -> None: """ 设置编辑器内容 @@ -513,21 +500,8 @@ class BaseEditor(CodeEditorBaseObject, QWidget): def slot_code_sel_run(self): """运行选中的代码""" - def slot_cursor_position_changed(self): - pass - - def slot_selection_changed(self) -> None: - """ - 选中内容变化槽函数 - - :return: None - """ - def create_context_menu(self) -> 'QMenu': - """ - 创建上下文菜单。 - :return: - """ + """创建上下文菜单。""" menu = self.text_edit.createStandardContextMenu() # 遍历本身已有的菜单项做翻译处理 diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index dfa16651..a5bf1d4f 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -374,11 +374,7 @@ class PMPythonEditor(BaseEditor): self.text_edit.clearIndicatorRange(row, 0, row, col, self._indicator_error2) def slot_code_sel_run(self): - """ - 运行选中代码 - - :return: - """ + """运行选中代码""" text = self.text(selected=True).strip() if not text: text = self.current_line_text().strip() @@ -386,11 +382,7 @@ class PMPythonEditor(BaseEditor): self._parent.slot_run_sel(text) def slot_code_run(self): - """ - 运行代码 - - :return: - """ + """运行代码""" logger.warning('run code') text = self.text().strip() if not text: @@ -399,10 +391,9 @@ class PMPythonEditor(BaseEditor): self._parent.slot_run_script(text) def slot_run_in_terminal(self): - """ - 在终端中运行代码 + """在终端中运行代码 + 调用程序的进程管理接口。 - :return: """ text = self.text().strip() if not text: @@ -417,14 +408,10 @@ class PMPythonEditor(BaseEditor): return self._parent.slot_run_script(text, self.tr( - 'Running Current Script Cell (Line %d to %d).' % (start_line + 1, start_line + lines + 1))) + f'Running Current Script Cell (Line {start_line + 1} to {start_line + lines + 1}).')) def slot_edit_widget_show(self, line): - """ - 处理显示设置控件的事件。 - :param line: - :return: - """ + """处理显示设置控件的事件。""" def get_indent(line: str): for i, s in enumerate(line): -- Gitee From 12ee4bfd8e207077ab5769ee319729e929344e47 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 11:32:36 +0800 Subject: [PATCH 070/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/widgets/editors/base_editor.py | 13 +------------ .../widgets/editors/markdown_editor.py | 17 +---------------- packages/code_editor/widgets/tab_widget.py | 5 ----- 3 files changed, 2 insertions(+), 33 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 8c2aab6e..cc22149f 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -29,7 +29,7 @@ Created on 2020/9/7 import logging import os import time -from typing import Dict, Any, Callable, TYPE_CHECKING, Type +from typing import Dict, Callable, TYPE_CHECKING, Type from PySide2.QtCore import SignalInstance, Signal, QTranslator, QLocale, QDir, QCoreApplication, QPoint, Qt from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon @@ -119,15 +119,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): col = self.text_edit.textCursor().columnNumber() self.status_bar.setText(f'行:{row + 1},列:{col + 1}') - def slot_text_edit_focusedin(self, e): - pass - - def update_settings(self, settings: Dict[str, Any]): - pass - - def slot_textedit_focusedin(self, e): - pass - def goto_line(self, line_no: int): """跳转到对应行列""" doc: QTextDocument = self.text_edit.document() @@ -175,8 +166,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): def _init_signals(self) -> None: """初始化信号绑定""" - # 绑定获得焦点信号 - self.text_edit.signal_focused_in.connect(self.slot_text_edit_focusedin) # 绑定右键菜单信号 self.text_edit.customContextMenuRequested.connect(self.slot_custom_context_menu_requested) # 绑定快捷键信号 diff --git a/packages/code_editor/widgets/editors/markdown_editor.py b/packages/code_editor/widgets/editors/markdown_editor.py index 7746eb83..ea2e935a 100644 --- a/packages/code_editor/widgets/editors/markdown_editor.py +++ b/packages/code_editor/widgets/editors/markdown_editor.py @@ -32,23 +32,8 @@ class PMMarkdownEditor(BaseEditor): self._path = path self.textEdit.load_file(path) - def update_settings(self, settings): - """ - 加载编辑器的设置 - Args: - settings: - - Returns: - - """ - pass - def filename(self): - """ - 获取当前文件名 - Returns: - - """ + """获取当前文件名""" return os.path.basename(self._path) def modified(self) -> bool: diff --git a/packages/code_editor/widgets/tab_widget.py b/packages/code_editor/widgets/tab_widget.py index 7178b834..b97404f3 100644 --- a/packages/code_editor/widgets/tab_widget.py +++ b/packages/code_editor/widgets/tab_widget.py @@ -304,8 +304,6 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): QMessageBox.warning(self, self.tr('Warning'), self.tr('Editor does not support file:\n%s') % path) logger.warning('Editor Cannot open path:%s!!' % path) - if self.settings is not None: - widget.update_settings(self.settings) widget.set_lib(self.extension_lib) widget.load_file(path) widget.windowTitleChanged.connect(self.slot_set_tab_text) @@ -733,9 +731,6 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): def update_settings(self, settings: Dict[str, Any]): self.settings = settings self.set_background_syntax_checking(settings['check_syntax_background']) - for i in range(self.count()): - w: 'PMBaseEditor' = self.widget(i) - w.update_settings(settings) def closeEvent(self, event: QCloseEvent) -> None: if self._thread_check and self._thread_check.isRunning(): -- Gitee From a64161aad45299202cb1f65b2302cb5e78857af8 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 11:35:39 +0800 Subject: [PATCH 071/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index cc22149f..25ed7106 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -115,8 +115,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self.layout().addWidget(self.status_bar) def show_cursor_pos(self): - row = self.text_edit.textCursor().block().blockNumber() - col = self.text_edit.textCursor().columnNumber() + row, col = self.text_edit.cursor_position self.status_bar.setText(f'行:{row + 1},列:{col + 1}') def goto_line(self, line_no: int): -- Gitee From fb91dcb38a9cb4f7002e4b2074b56b1de4f4f8c0 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 11:40:33 +0800 Subject: [PATCH 072/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/editors/base_editor.py | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 25ed7106..d7689b63 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -58,7 +58,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): text_edit_class: Type['PMBaseCodeEdit'] = None # 定义实际的代码编辑区对象 text_edit: 'PMBaseCodeEdit' - signal_focused_in: SignalInstance # 编辑器获得焦点 + signal_focused_in: SignalInstance # 编辑器获得焦点,是TextEdit的相关信号的直接引用 signal_new_requested: SignalInstance = Signal(str, int) # 文件路径;文件的打开模式(目前都是0) signal_save_requested: SignalInstance = Signal() signal_request_find_in_path: SignalInstance = Signal(str) @@ -82,14 +82,28 @@ class BaseEditor(CodeEditorBaseObject, QWidget): super().__init__(parent) + # 设置布局 + layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + self.setLayout(layout) + + # 设置实际的代码编辑区 + assert self.text_edit_class is not None + self.text_edit = self.text_edit_class(self) + 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) + # 设置底部状态栏 - # 这里仅定义了一个对象,在设置edit对象时,会将这个对象挂载进edit中 self.status_bar = QLabel() + self.layout().addWidget(self.status_bar) - # 设置实际的代码编辑区 - if self.text_edit_class is not None: - self.text_edit = self.text_edit_class(self) - self.set_edit(self.text_edit) + # 设置各个对话框 + self.find_dialog = FindDialog(parent=self, text_editor=self) + self.goto_line_dialog = GotoLineDialog(parent=self) self.last_save_time = 0 self._path = '' @@ -99,21 +113,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): def set_lib(self, extension_lib): self.extension_lib = extension_lib - def set_edit(self, edit: 'PMBaseCodeEdit'): - self.text_edit = edit - 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.find_dialog = FindDialog(parent=self, text_editor=self) - self.goto_line_dialog = GotoLineDialog(parent=self) - layout = QVBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - self.setLayout(layout) - self.layout().addWidget(self.text_edit) - self.layout().addWidget(self.status_bar) - def show_cursor_pos(self): row, col = self.text_edit.cursor_position self.status_bar.setText(f'行:{row + 1},列:{col + 1}') -- Gitee From bee9c26970c464175da3dd79a4385e33068b7ec6 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 11:43:37 +0800 Subject: [PATCH 073/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index d7689b63..68e10050 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -186,8 +186,8 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self._action_find_in_path.triggered.connect(self.slot_find_in_path) self._shortcut_find_in_path.activated.connect(self.slot_find_in_path) - self._action_autocomp.triggered.connect(self.autocomp) - self._shortcut_autocomp.activated.connect(self.autocomp) + self._action_autocomp.triggered.connect(self.auto_completion) + self._shortcut_autocomp.activated.connect(self.auto_completion) self._shortcut_goto.activated.connect(self.slot_goto_line) @@ -253,7 +253,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self._action_view_breakpoints = QAction(QCoreApplication.translate("PMGBaseEditor", 'View BreakPoints'), self.text_edit) - def autocomp(self): + def auto_completion(self): pass def set_text(self, text: str) -> None: -- Gitee From c1a030ec5f84b9ea912501f9706518b9eded8ef8 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 11:46:39 +0800 Subject: [PATCH 074/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 8 +------- .../code_editor/widgets/text_edit/base_text_edit.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 68e10050..842d2bae 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -119,13 +119,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): def goto_line(self, line_no: int): """跳转到对应行列""" - doc: QTextDocument = self.text_edit.document() - lines = doc.blockCount() - assert 1 <= line_no <= lines - pos = doc.findBlockByLineNumber(line_no - 1).position() - text_cursor: QTextCursor = self.text_edit.textCursor() - text_cursor.setPosition(pos) - self.text_edit.setTextCursor(text_cursor) + self.text_edit.go_to_line(line_no) def search_word(self, text_to_find: str, wrap: bool, regex: bool, case_sensitive: bool, whole_word: bool, forward: bool, index=-1, line=-1, **kwargs) -> bool: 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 505987af..9149f222 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -9,7 +9,7 @@ from typing import Callable, Tuple, Dict, List, TYPE_CHECKING, Type from PySide2.QtCore import SignalInstance, Signal, Qt, QTimer, QModelIndex, QUrl, QRect from PySide2.QtGui import QFocusEvent, QTextCursor, QMouseEvent, QKeyEvent, QDragEnterEvent, QDropEvent, QPainter, \ - QColor, QTextFormat, QFontDatabase, QFont + QColor, QTextFormat, QFontDatabase, QFont, QTextDocument from PySide2.QtWidgets import QPlainTextEdit, QWidget, QApplication, QTextEdit, QLabel from jedi.api.classes import Completion as CompletionResult @@ -566,10 +566,13 @@ class PMBaseCodeEdit(QPlainTextEdit): return self.modified def go_to_line(self, line: int): - tc = self.textCursor() - pos = self.document().findBlockByNumber(line - 1).position() - tc.setPosition(pos, QTextCursor.MoveAnchor) - # self.setTextCursor(tc) + doc: QTextDocument = self.document() + lines = doc.blockCount() + assert 1 <= line <= lines + pos = doc.findBlockByLineNumber(line - 1).position() + text_cursor: QTextCursor = self.textCursor() + text_cursor.setPosition(pos) + self.setTextCursor(text_cursor) def get_selected_text(self) -> str: if self.textCursor().hasSelection(): -- Gitee From a37d52fdc295a3a02420095ff1e044134565d58e Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 19:18:23 +0800 Subject: [PATCH 075/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 8 +++----- packages/code_editor/widgets/editors/markdown_editor.py | 9 +-------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 842d2bae..4ba9d1dc 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -273,12 +273,10 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self.slot_modification_changed(modified) def load_file(self, path: str) -> None: - """ - 加载文件 + """加载文件 - :param path: 文件路径 - :type path: str - :return: None + Args: + path: str, 需要打开的文件路径 """ self._path = '' try: diff --git a/packages/code_editor/widgets/editors/markdown_editor.py b/packages/code_editor/widgets/editors/markdown_editor.py index ea2e935a..d7bcc580 100644 --- a/packages/code_editor/widgets/editors/markdown_editor.py +++ b/packages/code_editor/widgets/editors/markdown_editor.py @@ -21,14 +21,7 @@ class PMMarkdownEditor(BaseEditor): self.layout().addWidget(self.textEdit) def load_file(self, path: str): - """ - 加载文件 - Args: - path: - - Returns: - - """ + """加载文件""" self._path = path self.textEdit.load_file(path) -- Gitee From f9345a208006cdcaed6ead6bfbfdd28d8a5b0908 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Wed, 4 Aug 2021 20:08:45 +0800 Subject: [PATCH 076/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/widgets/editors/base_editor.py | 14 ++------------ .../code_editor/widgets/editors/markdown_editor.py | 4 ---- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 4ba9d1dc..58d9879e 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -411,21 +411,11 @@ class BaseEditor(CodeEditorBaseObject, QWidget): return False def filename(self) -> str: - """ - 返回当前文件名 - - :rtype: str - :return: 返回当前文件名 - """ + """返回当前文件名""" return os.path.basename(self._path) def path(self) -> str: - """ - 返回当前文件路径 - - :rtype: str - :return: 返回当前文件路径 - """ + """返回当前文件路径""" return self._path def set_path(self, path: str) -> None: diff --git a/packages/code_editor/widgets/editors/markdown_editor.py b/packages/code_editor/widgets/editors/markdown_editor.py index d7bcc580..247bf925 100644 --- a/packages/code_editor/widgets/editors/markdown_editor.py +++ b/packages/code_editor/widgets/editors/markdown_editor.py @@ -25,10 +25,6 @@ class PMMarkdownEditor(BaseEditor): self._path = path self.textEdit.load_file(path) - def filename(self): - """获取当前文件名""" - return os.path.basename(self._path) - def modified(self) -> bool: """ 获取当前文件是否被修改 -- Gitee From 4720b4768a950b12bbb968caad4dc232d7c7f931 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Thu, 5 Aug 2021 00:40:27 +0800 Subject: [PATCH 077/108] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/widgets/editors/base_editor.py | 4 ++-- .../code_editor/widgets/editors/markdown_editor.py | 12 ------------ .../widgets/text_edit/base_text_edit.py | 14 ++------------ 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 58d9879e..a3dda50e 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -363,7 +363,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): method. """ QCoreApplication.translate = QCoreApplication.translate - path = self._path.replace_selection(os.sep, '/') + path = self._path.replace(os.sep, '/') default_dir = self.default_save_directory if path.startswith(QDir.tempPath().replace(os.sep, '/')): assert os.path.exists(default_dir) or default_dir == '' @@ -450,7 +450,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): return self.text_edit.toPlainText() def slot_file_modified_externally(self): - return + self.last_save_time = time.time() def change_color_scheme(self, color_scheme_name: str): if color_scheme_name == 'dark': diff --git a/packages/code_editor/widgets/editors/markdown_editor.py b/packages/code_editor/widgets/editors/markdown_editor.py index 247bf925..51313997 100644 --- a/packages/code_editor/widgets/editors/markdown_editor.py +++ b/packages/code_editor/widgets/editors/markdown_editor.py @@ -1,9 +1,6 @@ -import os import re import time -from PySide2.QtCore import Qt -from PySide2.QtGui import QKeyEvent from PySide2.QtWidgets import QHBoxLayout, QMessageBox from packages.qt_vditor.client import Window @@ -37,15 +34,6 @@ class PMMarkdownEditor(BaseEditor): self.textEdit.save_file() self.last_save_time = time.time() - def slot_file_modified_externally(self): - self.last_save_time = time.time() - - def keyPressEvent(self, a0: QKeyEvent) -> None: - super().keyPressEvent(a0) - if a0.key() == Qt.Key_S and a0.modifiers() == Qt.ControlModifier: - self.slot_save() - print(a0) - def get_code(self) -> str: with open(self._path, 'r', encoding='utf8') as f: text = f.read() 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 9149f222..c1204b18 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -320,26 +320,17 @@ class PMBaseCodeEdit(QPlainTextEdit): k = event.key() if k == Qt.Key_Tab: self.on_tab() - return elif k == Qt.Key_Backtab: self.on_back_tab() - return - elif k == Qt.Key_S and event.modifiers() == Qt.ControlModifier: - self.save() - return elif k == Qt.Key_Slash and event.modifiers() == Qt.ControlModifier: self.comment() elif k == Qt.Key_Return: - if not self.textCursor().atBlockEnd(): - pass - else: + if self.textCursor().atBlockEnd(): self.on_return_pressed() event.accept() - return elif k == Qt.Key_Backspace: self.on_backspace(event) event.accept() - return elif k in (Qt.Key_ParenLeft, Qt.Key_BracketLeft, Qt.Key_BraceLeft): cursor = self.textCursor() cursor.beginEditBlock() @@ -349,7 +340,6 @@ class PMBaseCodeEdit(QPlainTextEdit): cursor.endEditBlock() self.setTextCursor(cursor) event.accept() - return elif k in (Qt.Key_ParenRight, Qt.Key_BracketRight, Qt.Key_BraceRight): cursor = self.textCursor() code = self.toPlainText() @@ -367,7 +357,7 @@ class PMBaseCodeEdit(QPlainTextEdit): cursor.endEditBlock() self.setTextCursor(cursor) event.accept() - return + super().keyPressEvent(event) def on_backspace(self, _: QKeyEvent): -- Gitee From 4b2ac5f24b16ae6b89e42b1337c0b498d06ed0eb Mon Sep 17 00:00:00 2001 From: wolfpan Date: Thu, 5 Aug 2021 19:11:20 +0800 Subject: [PATCH 078/108] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E7=9A=84set=5Fencoding=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index a3dda50e..ec11187a 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -284,7 +284,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): with open(path, 'rb') as fp: text = fp.read() text, coding = decode(text) - self.set_encoding(coding) self.set_text(text) self.set_modified(False) self.text_edit.set_eol_status() @@ -298,15 +297,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self.last_save_time = time.time() self.set_modified(False) - def set_encoding(self, encoding: str): - """ - 设置文本编码,仅支持 ASCII 和 UTF-8 - - :param encoding: ascii or gbk or utf-8 - :type: str - :return: - """ - def slot_about_close(self, save_all=False) -> QMessageBox.StandardButton: """ 是否需要关闭以及保存 -- Gitee From 4c4bd843170631289606306d0c6cce13c7d22c92 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Thu, 5 Aug 2021 19:13:46 +0800 Subject: [PATCH 079/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/tab_widget.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/code_editor/widgets/tab_widget.py b/packages/code_editor/widgets/tab_widget.py index b97404f3..890db8c1 100644 --- a/packages/code_editor/widgets/tab_widget.py +++ b/packages/code_editor/widgets/tab_widget.py @@ -740,7 +740,6 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): 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()] - print(widgets) if not widgets: return save_all = False -- Gitee From 5d4e23db384656c89b1394673653bb573d465e39 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Thu, 5 Aug 2021 19:21:05 +0800 Subject: [PATCH 080/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/widgets/editors/base_editor.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index ec11187a..e10ee66d 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -34,7 +34,7 @@ from typing import Dict, Callable, TYPE_CHECKING, Type from PySide2.QtCore import SignalInstance, Signal, QTranslator, QLocale, QDir, QCoreApplication, QPoint, Qt from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon from PySide2.QtWidgets import QWidget, QMessageBox, QApplication, QLabel, QVBoxLayout, QFileDialog, \ - QShortcut, QAction + QShortcut, QAction, QMenu from features.extensions.extensionlib.extension_lib import ExtensionLib from ..dialogs.find_dialog import FindDialog @@ -483,13 +483,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): return menu def slot_custom_context_menu_requested(self, pos: QPoint) -> None: - """ - 右键菜单修改 - - :param pos: - :type pos: QPoint - :return: None - """ + """打开右键菜单""" menu = self.create_context_menu() # 根据条件决定菜单是否可用 enabled = len(self.text().strip()) > 0 @@ -497,8 +491,10 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self._action_run_code.setEnabled(enabled) # self._action_run_sel_code.setEnabled(self.textEdit.hasSelectedText()) self._action_run_sel_code.setEnabled(enabled) + logger.setLevel(logging.DEBUG) + logger.debug('menu craeted') menu.exec_(self.text_edit.mapToGlobal(pos)) - del menu + logger.debug('menu deleted') def slot_find_in_path(self): sel = self.text_edit.get_selected_text() -- Gitee From 11f7be538a4a1ee7b982095abd8bc7ab7794ba25 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Thu, 5 Aug 2021 21:20:35 +0800 Subject: [PATCH 081/108] =?UTF-8?q?=E8=B0=83=E6=95=B4code=5Feditor?= =?UTF-8?q?=E7=9A=84=E7=BF=BB=E8=AF=91=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/main.py | 12 ++++++------ packages/code_editor/widgets/editors/base_editor.py | 11 ++--------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/packages/code_editor/main.py b/packages/code_editor/main.py index 77bf2685..985b0ccb 100644 --- a/packages/code_editor/main.py +++ b/packages/code_editor/main.py @@ -1,5 +1,6 @@ import logging import os +from pathlib import Path from typing import Dict, Union, TYPE_CHECKING from PySide2.QtCore import QLocale, QTranslator @@ -8,12 +9,6 @@ from PySide2.QtWidgets import QApplication if TYPE_CHECKING: from packages.file_tree.main import Interface as FileTreeInterface -app = QApplication.instance() -trans_editor_tb = QTranslator() -app.trans_editor_tb = trans_editor_tb -trans_editor_tb.load(os.path.join(os.path.dirname(__file__), 'assets/translations', f'qt_{QLocale.system().name()}.qm')) -app.installTranslator(trans_editor_tb) - from features.extensions.extensionlib import BaseInterface, BaseExtension from .widgets.tab_widget import PMCodeEditTabWidget from .widgets.debugger import PMDebugConsoleTabWidget @@ -23,6 +18,11 @@ from pmgwidgets import PMGPanel, load_json, dump_json __prevent_from_ide_optimization = PMEditorToolbar # 这一行的目的是防止导入被编辑器自动优化。 logger = logging.getLogger('code_editor') +# 翻译文件只需要加载一次 +__translator = QTranslator() +__translator.load(str(Path(__file__).parent / 'assets' / 'translations' / f'qt_{QLocale.system().name()}.qm')) +QApplication.instance().installTranslator(__translator) + class Extension(BaseExtension): editor_widget: 'PMCodeEditTabWidget' diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index e10ee66d..5d4c0099 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -31,9 +31,9 @@ import os import time from typing import Dict, Callable, TYPE_CHECKING, Type -from PySide2.QtCore import SignalInstance, Signal, QTranslator, QLocale, QDir, QCoreApplication, QPoint, Qt +from PySide2.QtCore import SignalInstance, Signal, QDir, QCoreApplication, QPoint, Qt from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon -from PySide2.QtWidgets import QWidget, QMessageBox, QApplication, QLabel, QVBoxLayout, QFileDialog, \ +from PySide2.QtWidgets import QWidget, QMessageBox, QLabel, QVBoxLayout, QFileDialog, \ QShortcut, QAction, QMenu from features.extensions.extensionlib.extension_lib import ExtensionLib @@ -74,12 +74,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): layout: Callable[[], QVBoxLayout] def __init__(self, parent): - # 设置翻译文件 - app = QApplication.instance() - app.trans_editor_tb = QTranslator() - app.trans_editor_tb.load(str(self.settings.translations_dir / f'qt_{QLocale.system().name()}.qm')) - app.installTranslator(app.trans_editor_tb) - super().__init__(parent) # 设置布局 @@ -352,7 +346,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): If the file is not saved yet, the qfiledialog will open save dialog at default_dir,generated by get_default_dir method. """ - QCoreApplication.translate = QCoreApplication.translate path = self._path.replace(os.sep, '/') default_dir = self.default_save_directory if path.startswith(QDir.tempPath().replace(os.sep, '/')): -- Gitee From 4f59ab6595046f7884a30f0f80a7ec071fca179d Mon Sep 17 00:00:00 2001 From: wolfpan Date: Thu, 5 Aug 2021 21:42:39 +0800 Subject: [PATCH 082/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pmgwidgets/utilities/platform/fileutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmgwidgets/utilities/platform/fileutils.py b/pmgwidgets/utilities/platform/fileutils.py index f681e9dd..777d70a1 100644 --- a/pmgwidgets/utilities/platform/fileutils.py +++ b/pmgwidgets/utilities/platform/fileutils.py @@ -56,7 +56,7 @@ def create_file_if_not_exist(abso_file_path: str, default_content: bytes = b''): # assert os.path.isfile(abso_file_path), 'Path \'%s\' is not a file!' % abso_file_path if os.path.isfile(abso_file_path): return - while (1): + while 1: parent_path = os.path.dirname(path) if not os.path.exists(parent_path): dir_stack.append(parent_path) -- Gitee From ac26a76383acce6f5759e340570cb61ab73e7d7c Mon Sep 17 00:00:00 2001 From: wolfpan Date: Thu, 5 Aug 2021 23:26:27 +0800 Subject: [PATCH 083/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/settings.py | 5 +++ packages/code_editor/utils/base_object.py | 5 +++ .../widgets/editors/base_editor.py | 37 ++++++++----------- utils/__init__.py | 3 +- 4 files changed, 27 insertions(+), 23 deletions(-) diff --git a/packages/code_editor/settings.py b/packages/code_editor/settings.py index 81be4386..2b9820fb 100644 --- a/packages/code_editor/settings.py +++ b/packages/code_editor/settings.py @@ -4,9 +4,14 @@ from pathlib import Path +from PySide2.QtGui import QIcon + class Settings: base_dir = Path(__file__).parent.absolute() assets_dir = base_dir / 'assets' icons_dir = assets_dir / 'icons' translations_dir = assets_dir / 'translations' + + def get_icon(self, name: str) -> QIcon: + return QIcon(str(self.icons_dir / name)) diff --git a/packages/code_editor/utils/base_object.py b/packages/code_editor/utils/base_object.py index 3cdfe901..4bfe0169 100644 --- a/packages/code_editor/utils/base_object.py +++ b/packages/code_editor/utils/base_object.py @@ -1,5 +1,10 @@ +from typing import TYPE_CHECKING, Callable + from ..settings import Settings class CodeEditorBaseObject: settings = Settings() + + if TYPE_CHECKING: + tr: Callable[[str], str] diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 5d4c0099..ef4101d3 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -32,7 +32,7 @@ import time from typing import Dict, Callable, TYPE_CHECKING, Type from PySide2.QtCore import SignalInstance, Signal, QDir, QCoreApplication, QPoint, Qt -from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon +from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence from PySide2.QtWidgets import QWidget, QMessageBox, QLabel, QVBoxLayout, QFileDialog, \ QShortcut, QAction, QMenu @@ -186,24 +186,17 @@ class BaseEditor(CodeEditorBaseObject, QWidget): def _init_actions(self) -> None: """初始化额外菜单项""" - self.icon_path = os.path.dirname(os.path.dirname(__file__)) # 图标文件路径 - self._action_format = QAction(QIcon(os.path.join(self.icon_path, 'icons/format.svg')), - QCoreApplication.translate("PMGBaseEditor", 'Format Code'), - self.text_edit) - self._action_run_code = QAction(QIcon(os.path.join(self.icon_path, 'icons/run.svg')), - QCoreApplication.translate("PMGBaseEditor", 'Run Code'), - self.text_edit) - self._action_run_sel_code = QAction(QIcon(os.path.join(self.icon_path, 'icons/python.svg')), - QCoreApplication.translate("PMGBaseEditor", 'Run Selected Code'), + # self.icon_path = self.settings.icons_dir + self._action_format = QAction(self.settings.get_icon('format.svg'), self.tr('Format Code'), self.text_edit) + self._action_run_code = QAction(self.settings.get_icon('run.svg'), self.tr('Run Code'), self.text_edit) + self._action_run_sel_code = QAction(self.settings.get_icon('python.svg'), self.tr('Run Selected Code'), self.text_edit) - self._action_save = QAction(QIcon(os.path.join(self.icon_path, 'icons/save.svg')), - QCoreApplication.translate("PMGBaseEditor", 'Save'), - self.text_edit) - self._action_find = QAction(QCoreApplication.translate("PMGBaseEditor", 'Find'), self.text_edit) - self._action_replace = QAction(QCoreApplication.translate("PMGBaseEditor", 'Replace'), self.text_edit) + self._action_save = QAction(self.settings.get_icon('save.svg'), self.tr('Save'), self.text_edit) + self._action_find = QAction(self.tr('Find'), self.text_edit) + self._action_replace = QAction(self.tr('Replace'), self.text_edit) - self._action_find_in_path = QAction(QCoreApplication.translate('PMGBaseEditor', 'Find In Path'), self.text_edit) - self._action_autocomp = QAction(QCoreApplication.translate("PMGBaseEditor", 'AutoComp'), self.text_edit) + self._action_find_in_path = QAction(self.tr('Find In Path'), self.text_edit) + self._action_autocomp = QAction(self.tr('AutoComp'), self.text_edit) # 设置快捷键 self._shortcut_format = QShortcut(QKeySequence('Ctrl+Alt+F'), self.text_edit, context=Qt.WidgetShortcut) @@ -232,13 +225,13 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self._shortcut_goto = QShortcut(QKeySequence('Ctrl+G'), self.text_edit, context=Qt.WidgetShortcut) - self._action_add_breakpoint = QAction(QIcon(os.path.join(self.icon_path, 'icons/breakpoint.svg')), - QCoreApplication.translate("PMGBaseEditor", 'Add Breakpoint'), + self._action_add_breakpoint = QAction(self.settings.get_icon('breakpoint.svg'), + self.tr('Add Breakpoint'), self.text_edit) - self._action_remove_breakpoint = QAction(QCoreApplication.translate("PMGBaseEditor", 'Remove Breakpoint'), + self._action_remove_breakpoint = QAction(self.tr('Remove Breakpoint'), self.text_edit) - self._action_view_breakpoints = QAction(QCoreApplication.translate("PMGBaseEditor", 'View BreakPoints'), + self._action_view_breakpoints = QAction(self.tr('View BreakPoints'), self.text_edit) def auto_completion(self): @@ -351,7 +344,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): if path.startswith(QDir.tempPath().replace(os.sep, '/')): assert os.path.exists(default_dir) or default_dir == '' # 弹出对话框要求选择真实路径保存 - path, ext = QFileDialog.getSaveFileName(self, QCoreApplication.translate("PMGBaseEditor", 'Save file'), + path, ext = QFileDialog.getSaveFileName(self, self.tr('Save file'), default_dir, filter='*.py') diff --git a/utils/__init__.py b/utils/__init__.py index e70f3f75..e565842a 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -54,11 +54,12 @@ def get_application() -> "QApplication": return _application -def get_main_window() -> Optional["app2.MainWindow"]: +def get_main_window() -> "app2.MainWindow": """ 获取主窗口或者主控件。 Returns: """ + assert _main_window is not None return _main_window -- Gitee From e36ce795df3850c5a8bf249a8b9fc38bf67bc3c7 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Thu, 5 Aug 2021 23:27:15 +0800 Subject: [PATCH 084/108] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index ef4101d3..1aa7d0cc 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -186,7 +186,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): def _init_actions(self) -> None: """初始化额外菜单项""" - # self.icon_path = self.settings.icons_dir self._action_format = QAction(self.settings.get_icon('format.svg'), self.tr('Format Code'), self.text_edit) self._action_run_code = QAction(self.settings.get_icon('run.svg'), self.tr('Run Code'), self.text_edit) self._action_run_sel_code = QAction(self.settings.get_icon('python.svg'), self.tr('Run Selected Code'), -- Gitee From 577496210f11f0e62e51b130de758ae92a227540 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Fri, 6 Aug 2021 10:14:20 +0800 Subject: [PATCH 085/108] =?UTF-8?q?=E5=B0=86code=5Feditor=E7=9A=84?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=94=AE=E9=83=A8=E5=88=86=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=E6=8A=BD=E6=88=90=E4=B8=80=E4=B8=AA=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/editors/base_editor.py | 70 +++++++++++++++---- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 1aa7d0cc..5612602a 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -29,10 +29,10 @@ Created on 2020/9/7 import logging import os import time -from typing import Dict, Callable, TYPE_CHECKING, Type +from typing import Dict, Callable, TYPE_CHECKING, Type, List from PySide2.QtCore import SignalInstance, Signal, QDir, QCoreApplication, QPoint, Qt -from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence +from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon from PySide2.QtWidgets import QWidget, QMessageBox, QLabel, QVBoxLayout, QFileDialog, \ QShortcut, QAction, QMenu @@ -46,6 +46,48 @@ from ...utils.utils import decode logger = logging.getLogger(__name__) +class Operation(CodeEditorBaseObject): + def __init__(self, widget: QWidget, name: str, label: str, slot: Callable, + conditions: List[Callable[[], bool]] = None, + key_sequence: str = None, icon_name: str = None): + """绑定一个快捷键操作。 + + 这个操作可以在widget环境下直接访问,也可以添加到QMenu中。 + + Args: + widget: 需要绑定事件的小部件,似乎text_edit可以用而QWidget不行,具体没有详查 + name: 快捷键的名称,用于后面显示在设置项中 + label: 显示在界面中的标签 + slot: 回调事件 + conditions: 根据这些条件判断是否可以调用 + key_sequence: 快捷键,QSequence的参数 + icon_name: 在code_editor/assets/icons下面查找的路径 + """ + super(Operation, self).__init__() + self.conditions = conditions + self.name = name + + # 设置Action的图标 + if icon_name is not None: + icon = QIcon(self.settings.get_icon(icon_name)) + self._qt_action: QAction = QAction(icon, label, widget) + self._qt_action.triggered.connect(slot) + + # 设置QAction和QShortcut快捷键 + if key_sequence is not None: + qt_sequence: QKeySequence = QKeySequence(key_sequence) + self._qt_action.setShortcut(qt_sequence) + qt_shortcut = QShortcut(key_sequence, widget, context=Qt.WidgetShortcut) + qt_shortcut.activated.connect(slot) + + @property + def qt_action(self) -> QAction: + """获取action对象用于创建menu,并根据当前是否可用,判断是否显示为可用状态""" + assert self.conditions is None or len(self.conditions) >= 1 + self.conditions is not None and self._qt_action.setEnabled(all(c() for c in self.conditions)) + return self._qt_action + + class BaseEditor(CodeEditorBaseObject, QWidget): """ 编辑器的各种操件应当由这个类及其子类进行管理,包括代码重构、代码缩进等内容。 @@ -155,8 +197,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # 绑定右键菜单信号 self.text_edit.customContextMenuRequested.connect(self.slot_custom_context_menu_requested) # 绑定快捷键信号 - self._action_format.triggered.connect(self.slot_code_format) - self._shortcut_format.activated.connect(self.slot_code_format) + # self.__operation_format.qt_shortcut.activated.connect(self.slot_code_format) self._action_run_code.triggered.connect(self.slot_code_run) self._shortcut_run.activated.connect(self.slot_code_run) self._action_run_sel_code.triggered.connect(self.slot_code_sel_run) @@ -186,27 +227,27 @@ class BaseEditor(CodeEditorBaseObject, QWidget): def _init_actions(self) -> None: """初始化额外菜单项""" - self._action_format = QAction(self.settings.get_icon('format.svg'), self.tr('Format Code'), self.text_edit) + self.__operation_format = Operation( + widget=self.text_edit, name='format code', label=self.tr('Format Code'), + slot=lambda: self.slot_code_format(), key_sequence='Ctrl+Alt+F', icon_name='format.svg') + self._action_run_code = QAction(self.settings.get_icon('run.svg'), self.tr('Run Code'), self.text_edit) + self._action_run_code.setShortcut(QKeySequence('Ctrl+R')) + self._shortcut_run = QShortcut(QKeySequence('Ctrl+R'), self.text_edit, context=Qt.WidgetShortcut) + self._action_run_sel_code = QAction(self.settings.get_icon('python.svg'), self.tr('Run Selected Code'), self.text_edit) + self._action_save = QAction(self.settings.get_icon('save.svg'), self.tr('Save'), self.text_edit) self._action_find = QAction(self.tr('Find'), self.text_edit) self._action_replace = QAction(self.tr('Replace'), self.text_edit) self._action_find_in_path = QAction(self.tr('Find In Path'), self.text_edit) - self._action_autocomp = QAction(self.tr('AutoComp'), self.text_edit) - - # 设置快捷键 - self._shortcut_format = QShortcut(QKeySequence('Ctrl+Alt+F'), self.text_edit, context=Qt.WidgetShortcut) - self._action_format.setShortcut(QKeySequence('Ctrl+Alt+F')) + self._action_autocomp = QAction(self.tr('AutoComp'), self.text_edit) self._shortcut_autocomp = QShortcut(QKeySequence('Ctrl+P'), self.text_edit, context=Qt.WidgetShortcut) self._action_autocomp.setShortcut(QKeySequence("Ctrl+P")) - self._shortcut_run = QShortcut(QKeySequence('Ctrl+R'), self.text_edit, context=Qt.WidgetShortcut) - self._action_run_code.setShortcut(QKeySequence('Ctrl+R')) - self._shortcut_run_sel = QShortcut(QKeySequence('F9'), self.text_edit, context=Qt.WidgetShortcut) self._action_run_sel_code.setShortcut(QKeySequence('F9')) @@ -454,7 +495,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): action.setText(QCoreApplication.translate('QTextControl', action.text())) # 添加额外菜单 menu.addSeparator() - menu.addAction(self._action_format) + menu.addAction(self.__operation_format.qt_action) menu.addAction(self._action_run_code) menu.addAction(self._action_run_sel_code) menu.addAction(self._action_save) @@ -472,7 +513,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): menu = self.create_context_menu() # 根据条件决定菜单是否可用 enabled = len(self.text().strip()) > 0 - self._action_format.setEnabled(enabled) self._action_run_code.setEnabled(enabled) # self._action_run_sel_code.setEnabled(self.textEdit.hasSelectedText()) self._action_run_sel_code.setEnabled(enabled) -- Gitee From 302a8f966378b01720152bf4eb46b6bc7fb1d1c7 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Fri, 6 Aug 2021 11:20:19 +0800 Subject: [PATCH 086/108] =?UTF-8?q?code=5Feditor:=20=E5=B0=86=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E9=94=AE=E7=8B=AC=E7=AB=8B=E4=B8=BA=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/utils/operation.py | 61 +++++++++++++++++++ .../widgets/editors/base_editor.py | 49 ++------------- 2 files changed, 65 insertions(+), 45 deletions(-) create mode 100644 packages/code_editor/utils/operation.py diff --git a/packages/code_editor/utils/operation.py b/packages/code_editor/utils/operation.py new file mode 100644 index 00000000..c4442cd7 --- /dev/null +++ b/packages/code_editor/utils/operation.py @@ -0,0 +1,61 @@ +from typing import Callable, List + +from PySide2.QtCore import Qt +from PySide2.QtGui import QIcon, QKeySequence +from PySide2.QtWidgets import QWidget, QAction, QShortcut + +from packages.code_editor.utils.base_object import CodeEditorBaseObject + + +class Operation(CodeEditorBaseObject): + widget: QWidget # 快捷键绑定的小部件 + name: str # 快捷键的名称,在每个插件中应当是唯一存在的,用于后面显示在设置项中 + label: str # 显示在菜单中的名称 + + def __init__(self, widget: QWidget, name: str, label: str, slot: Callable, + conditions: List[Callable[[], bool]] = None, + key_sequence: str = None, icon_name: str = None): + """绑定一个快捷键操作。 + + 这个操作可以在widget环境下直接访问,也可以添加到QMenu中。 + + Args: + widget: 需要绑定事件的小部件,似乎text_edit可以用而QWidget不行,具体没有详查 + name: 快捷键的名称,用于后面显示在设置项中 + label: 显示在界面中的标签 + slot: 回调事件 + conditions: 根据这些条件判断是否可以调用,是一个函数的列表,每个函数都应返回一个布尔值,指示是否可用 + key_sequence: 快捷键,QSequence的参数 + icon_name: 在code_editor/assets/icons下面查找的路径 + """ + super(Operation, self).__init__() + self.widget = widget + self.__conditions = conditions + self.name = name + + # 设置Action的图标 + if icon_name is None: + icon = None + else: + icon = QIcon(self.settings.get_icon(icon_name)) + self.__action: QAction = QAction(icon, label, widget) # 用于进行缓存,以免每次都要重新创建一个action对象 + # noinspection PyUnresolvedReferences + self.__action.triggered.connect(slot) + + # 设置QAction和QShortcut快捷键 + if key_sequence is not None: + qt_sequence: QKeySequence = QKeySequence(key_sequence) + self.__action.setShortcut(qt_sequence) + # noinspection PyArgumentList + qt_shortcut = QShortcut(key_sequence, widget, context=Qt.WidgetShortcut) + qt_shortcut.activated.connect(slot) + + @property + def action(self) -> QAction: + """获取action对象用于创建menu,并根据当前是否可用,判断是否显示为可用状态 + + 这里采用实时计算属性来实现,因为在每次打开右键菜单时都需要判断其是否可用。 + """ + assert self.__conditions is None or len(self.__conditions) >= 1 + self.__conditions is not None and self.__action.setEnabled(all(c() for c in self.__conditions)) + return self.__action diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 5612602a..c5a1be63 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -29,10 +29,10 @@ Created on 2020/9/7 import logging import os import time -from typing import Dict, Callable, TYPE_CHECKING, Type, List +from typing import Dict, Callable, TYPE_CHECKING, Type from PySide2.QtCore import SignalInstance, Signal, QDir, QCoreApplication, QPoint, Qt -from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence, QIcon +from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence from PySide2.QtWidgets import QWidget, QMessageBox, QLabel, QVBoxLayout, QFileDialog, \ QShortcut, QAction, QMenu @@ -41,53 +41,12 @@ from ..dialogs.find_dialog import FindDialog from ..dialogs.goto_line_dialog import GotoLineDialog from ..text_edit.base_text_edit import PMBaseCodeEdit from ...utils.base_object import CodeEditorBaseObject +from ...utils.operation import Operation from ...utils.utils import decode logger = logging.getLogger(__name__) -class Operation(CodeEditorBaseObject): - def __init__(self, widget: QWidget, name: str, label: str, slot: Callable, - conditions: List[Callable[[], bool]] = None, - key_sequence: str = None, icon_name: str = None): - """绑定一个快捷键操作。 - - 这个操作可以在widget环境下直接访问,也可以添加到QMenu中。 - - Args: - widget: 需要绑定事件的小部件,似乎text_edit可以用而QWidget不行,具体没有详查 - name: 快捷键的名称,用于后面显示在设置项中 - label: 显示在界面中的标签 - slot: 回调事件 - conditions: 根据这些条件判断是否可以调用 - key_sequence: 快捷键,QSequence的参数 - icon_name: 在code_editor/assets/icons下面查找的路径 - """ - super(Operation, self).__init__() - self.conditions = conditions - self.name = name - - # 设置Action的图标 - if icon_name is not None: - icon = QIcon(self.settings.get_icon(icon_name)) - self._qt_action: QAction = QAction(icon, label, widget) - self._qt_action.triggered.connect(slot) - - # 设置QAction和QShortcut快捷键 - if key_sequence is not None: - qt_sequence: QKeySequence = QKeySequence(key_sequence) - self._qt_action.setShortcut(qt_sequence) - qt_shortcut = QShortcut(key_sequence, widget, context=Qt.WidgetShortcut) - qt_shortcut.activated.connect(slot) - - @property - def qt_action(self) -> QAction: - """获取action对象用于创建menu,并根据当前是否可用,判断是否显示为可用状态""" - assert self.conditions is None or len(self.conditions) >= 1 - self.conditions is not None and self._qt_action.setEnabled(all(c() for c in self.conditions)) - return self._qt_action - - class BaseEditor(CodeEditorBaseObject, QWidget): """ 编辑器的各种操件应当由这个类及其子类进行管理,包括代码重构、代码缩进等内容。 @@ -495,7 +454,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): action.setText(QCoreApplication.translate('QTextControl', action.text())) # 添加额外菜单 menu.addSeparator() - menu.addAction(self.__operation_format.qt_action) + menu.addAction(self.__operation_format.action) menu.addAction(self._action_run_code) menu.addAction(self._action_run_sel_code) menu.addAction(self._action_save) -- Gitee From fabb76bc93d50008fdbec2a8cb5a2c83a0c18424 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Fri, 6 Aug 2021 11:56:42 +0800 Subject: [PATCH 087/108] =?UTF-8?q?code=5Feditor:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=B8=80=E4=B8=AAGUI=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=9A=84=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/widgets/editors/base_editor.py | 2 +- .../test_gui/test_python_editor.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/test_code_editor/test_gui/test_python_editor.py diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index c5a1be63..9ca071a3 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -74,7 +74,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): if TYPE_CHECKING: layout: Callable[[], QVBoxLayout] - def __init__(self, parent): + def __init__(self, parent=None): super().__init__(parent) # 设置布局 diff --git a/tests/test_code_editor/test_gui/test_python_editor.py b/tests/test_code_editor/test_gui/test_python_editor.py new file mode 100644 index 00000000..5e0f1f41 --- /dev/null +++ b/tests/test_code_editor/test_gui/test_python_editor.py @@ -0,0 +1,15 @@ +from time import sleep + +from PySide2.QtCore import Qt + +from packages.code_editor.widgets.editors.python_editor import PMPythonEditor + + +def test_format_code(qtbot): + window = PMPythonEditor() + qtbot.addWidget(window) + window.show() + qtbot.waitForWindowShown(window) + qtbot.keyClicks(window.text_edit, 'a = 123') + qtbot.keyPress(window.text_edit, 'F', modifier=Qt.ControlModifier | Qt.AltModifier) + assert window.text() == 'a = 123\n' -- Gitee From a42529df05264a4775d932902318c67c514ca340 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Fri, 6 Aug 2021 13:37:38 +0800 Subject: [PATCH 088/108] =?UTF-8?q?code=5Feditor:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=A1=A5=E5=85=A8=E7=9A=84=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/text_edit/base_text_edit.py | 36 +++++++++---------- .../test_gui/test_python_editor.py | 15 ++++++-- 2 files changed, 31 insertions(+), 20 deletions(-) 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 c1204b18..b9c633cc 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -110,8 +110,8 @@ class PMBaseCodeEdit(QPlainTextEdit): self.setTabChangesFocus(False) # 设置代码提示的弹出框 - self.popup_hint_widget = BaseAutoCompleteDropdownWidget(self) - self.popup_hint_widget.hide() + self.autocompletion_dropdown = BaseAutoCompleteDropdownWidget(self) + self.autocompletion_dropdown.hide() self.setContextMenuPolicy(Qt.CustomContextMenu) # 用于更新界面的定时器,start的参数为毫秒 @@ -139,7 +139,7 @@ class PMBaseCodeEdit(QPlainTextEdit): # 文本发生改变后,保存当前时间 self.textChanged.connect(self.update_last_operation_time) # 在代码提示框里面双击后,将自动补全的内容添加至代码 - self.popup_hint_widget.doubleClicked.connect(self._insert_autocomp) + self.autocompletion_dropdown.doubleClicked.connect(self._insert_autocomp) @property def line_number_area_width(self): @@ -244,7 +244,7 @@ class PMBaseCodeEdit(QPlainTextEdit): self.hide_autocomp() def hide_autocomp(self): - self.popup_hint_widget.hide_autocomp() + self.autocompletion_dropdown.hide_autocomp() def on_text_changed(self): """文字发生改变时的方法""" @@ -257,17 +257,17 @@ class PMBaseCodeEdit(QPlainTextEdit): # 代码提示 cursor_pos = self.cursorRect() - self.popup_hint_widget.setGeometry( + self.autocompletion_dropdown.setGeometry( cursor_pos.x() + 5, cursor_pos.y() + 20, - self.popup_hint_widget.sizeHint().width(), - self.popup_hint_widget.sizeHint().height()) + self.autocompletion_dropdown.sizeHint().width(), + self.autocompletion_dropdown.sizeHint().height()) self._request_autocomp() def _insert_autocomp(self, event: QModelIndex = None): - row = self.popup_hint_widget.currentRow() - if 0 <= row < self.popup_hint_widget.count(): - complete, word_type = self.popup_hint_widget.get_complete(row) - word = self.popup_hint_widget.get_text(row) + row = self.autocompletion_dropdown.currentRow() + if 0 <= row < self.autocompletion_dropdown.count(): + complete, word_type = self.autocompletion_dropdown.get_complete(row) + word = self.autocompletion_dropdown.get_text(row) if not word.startswith(self._get_hint()): return comp = word[len(self._get_hint()):] @@ -279,7 +279,7 @@ class PMBaseCodeEdit(QPlainTextEdit): self.setTextCursor(tc) elif word_type == 'keyword': self.insertPlainText(' ') - self.popup_hint_widget.hide() + self.autocompletion_dropdown.hide() def _get_nearby_text(self): block_text = self.textCursor().block().text() @@ -301,7 +301,7 @@ class PMBaseCodeEdit(QPlainTextEdit): hint = self._get_hint() if hint == '' and not nearby_text.endswith(('.', '\\\\', '/')): - self.popup_hint_widget.hide_autocomp() + self.autocompletion_dropdown.hide_autocomp() return self.auto_complete_thread.text_cursor_pos = (position[0] + 1, position[1]) self.auto_complete_thread.text = self.toPlainText() @@ -311,8 +311,8 @@ class PMBaseCodeEdit(QPlainTextEdit): return self.textCursor().blockNumber(), self.textCursor().columnNumber() def mousePressEvent(self, event: QMouseEvent) -> None: - if self.popup_hint_widget.isVisible(): - self.popup_hint_widget.hide_autocomp() + if self.autocompletion_dropdown.isVisible(): + self.autocompletion_dropdown.hide_autocomp() self.signal_focused_in.emit(None) super().mousePressEvent(event) @@ -690,10 +690,10 @@ class PMBaseCodeEdit(QPlainTextEdit): def autocomp_show(self, completions: List['CompletionResult']): result = [] if len(completions) != 0: - self.popup_hint_widget.set_completions(completions) + self.autocompletion_dropdown.set_completions(completions) else: - self.popup_hint_widget.hide() - self.popup_hint_widget.autocomp_list = result + self.autocompletion_dropdown.hide() + self.autocompletion_dropdown.autocomp_list = result def mouseMoveEvent(self, event: QMouseEvent): """ diff --git a/tests/test_code_editor/test_gui/test_python_editor.py b/tests/test_code_editor/test_gui/test_python_editor.py index 5e0f1f41..5d10fcff 100644 --- a/tests/test_code_editor/test_gui/test_python_editor.py +++ b/tests/test_code_editor/test_gui/test_python_editor.py @@ -1,5 +1,3 @@ -from time import sleep - from PySide2.QtCore import Qt from packages.code_editor.widgets.editors.python_editor import PMPythonEditor @@ -13,3 +11,16 @@ def test_format_code(qtbot): qtbot.keyClicks(window.text_edit, 'a = 123') qtbot.keyPress(window.text_edit, 'F', modifier=Qt.ControlModifier | Qt.AltModifier) assert window.text() == 'a = 123\n' + + +def test_auto_completion(qtbot): + window = PMPythonEditor() + qtbot.addWidget(window) + window.show() + qtbot.waitForWindowShown(window) + qtbot.keyClicks(window.text_edit, 'import num') + qtbot.wait(1000) + # TODO 这里的按键事件为何是传递到dropdown而非text_edit + qtbot.keyClick(window.text_edit.autocompletion_dropdown, Qt.Key_Return) + qtbot.wait(1000) + assert window.text() == 'import numbers' -- Gitee From 2a8e311f9af09b43ba7a593992d25cab0a012cfd Mon Sep 17 00:00:00 2001 From: wolfpan Date: Fri, 6 Aug 2021 15:42:31 +0800 Subject: [PATCH 089/108] =?UTF-8?q?code=5Feditor:=20=E8=B0=83=E6=95=B4run?= =?UTF-8?q?=5Fcode=E7=9A=84=E5=BF=AB=E6=8D=B7=E9=94=AE=E7=9A=84=E5=86=99?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/widgets/editors/base_editor.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 9ca071a3..0c8a0290 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -157,8 +157,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self.text_edit.customContextMenuRequested.connect(self.slot_custom_context_menu_requested) # 绑定快捷键信号 # self.__operation_format.qt_shortcut.activated.connect(self.slot_code_format) - self._action_run_code.triggered.connect(self.slot_code_run) - self._shortcut_run.activated.connect(self.slot_code_run) self._action_run_sel_code.triggered.connect(self.slot_code_sel_run) self._shortcut_run_sel.activated.connect(self.slot_code_sel_run) @@ -186,13 +184,15 @@ class BaseEditor(CodeEditorBaseObject, QWidget): def _init_actions(self) -> None: """初始化额外菜单项""" + text_exists = lambda: len(self.text().strip()) > 0 + self.__operation_format = Operation( widget=self.text_edit, name='format code', label=self.tr('Format Code'), - slot=lambda: self.slot_code_format(), key_sequence='Ctrl+Alt+F', icon_name='format.svg') + slot=self.slot_code_format, key_sequence='Ctrl+Alt+F', icon_name='format.svg', conditions=[text_exists]) - self._action_run_code = QAction(self.settings.get_icon('run.svg'), self.tr('Run Code'), self.text_edit) - self._action_run_code.setShortcut(QKeySequence('Ctrl+R')) - self._shortcut_run = QShortcut(QKeySequence('Ctrl+R'), self.text_edit, context=Qt.WidgetShortcut) + self.__operation_run_code = Operation( + widget=self.text_edit, name='run code', label=self.tr('Run Code'), + slot=self.slot_code_run, key_sequence='Ctrl+R', icon_name='run.svg', conditions=[text_exists]) self._action_run_sel_code = QAction(self.settings.get_icon('python.svg'), self.tr('Run Selected Code'), self.text_edit) @@ -455,7 +455,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # 添加额外菜单 menu.addSeparator() menu.addAction(self.__operation_format.action) - menu.addAction(self._action_run_code) + menu.addAction(self.__operation_run_code.action) menu.addAction(self._action_run_sel_code) menu.addAction(self._action_save) menu.addAction(self._action_find) @@ -472,7 +472,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): menu = self.create_context_menu() # 根据条件决定菜单是否可用 enabled = len(self.text().strip()) > 0 - self._action_run_code.setEnabled(enabled) # self._action_run_sel_code.setEnabled(self.textEdit.hasSelectedText()) self._action_run_sel_code.setEnabled(enabled) logger.setLevel(logging.DEBUG) -- Gitee From def791333861659f71e1ed255c47136822d9dccd Mon Sep 17 00:00:00 2001 From: wolfpan Date: Fri, 6 Aug 2021 15:48:08 +0800 Subject: [PATCH 090/108] =?UTF-8?q?code=5Feditor:=20=E8=B0=83=E6=95=B4run?= =?UTF-8?q?=5Fselected=5Fcode=E7=9A=84=E5=BF=AB=E6=8D=B7=E9=94=AE=E7=9A=84?= =?UTF-8?q?=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/editors/base_editor.py | 23 ++++++++----------- .../widgets/editors/python_editor.py | 1 + 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 0c8a0290..73137f03 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -157,8 +157,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self.text_edit.customContextMenuRequested.connect(self.slot_custom_context_menu_requested) # 绑定快捷键信号 # self.__operation_format.qt_shortcut.activated.connect(self.slot_code_format) - self._action_run_sel_code.triggered.connect(self.slot_code_sel_run) - self._shortcut_run_sel.activated.connect(self.slot_code_sel_run) self._shortcut_save.activated.connect(self.slot_save) self._action_save.triggered.connect(self.slot_save) @@ -188,14 +186,19 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self.__operation_format = Operation( widget=self.text_edit, name='format code', label=self.tr('Format Code'), - slot=self.slot_code_format, key_sequence='Ctrl+Alt+F', icon_name='format.svg', conditions=[text_exists]) + slot=self.slot_code_format, key_sequence='Ctrl+Alt+F', icon_name='format.svg', conditions=[text_exists], + ) self.__operation_run_code = Operation( widget=self.text_edit, name='run code', label=self.tr('Run Code'), - slot=self.slot_code_run, key_sequence='Ctrl+R', icon_name='run.svg', conditions=[text_exists]) + slot=self.slot_code_run, key_sequence='Ctrl+R', icon_name='run.svg', conditions=[text_exists], + ) - self._action_run_sel_code = QAction(self.settings.get_icon('python.svg'), self.tr('Run Selected Code'), - self.text_edit) + self.__operation_run_selected_code = Operation( + widget=self.text_edit, name='run code', label=self.tr('Run Selected Code'), + slot=self.slot_code_sel_run, key_sequence='F9', icon_name='python.svg', conditions=[text_exists], + # TODO 添加判别条件:仅当有文本选中时才可用 + ) self._action_save = QAction(self.settings.get_icon('save.svg'), self.tr('Save'), self.text_edit) self._action_find = QAction(self.tr('Find'), self.text_edit) @@ -207,9 +210,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self._shortcut_autocomp = QShortcut(QKeySequence('Ctrl+P'), self.text_edit, context=Qt.WidgetShortcut) self._action_autocomp.setShortcut(QKeySequence("Ctrl+P")) - self._shortcut_run_sel = QShortcut(QKeySequence('F9'), self.text_edit, context=Qt.WidgetShortcut) - self._action_run_sel_code.setShortcut(QKeySequence('F9')) - self._action_save.setShortcut(QKeySequence('Ctrl+S')) self._shortcut_save = QShortcut(QKeySequence('Ctrl+S'), self.text_edit, context=Qt.WidgetShortcut) @@ -456,7 +456,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): menu.addSeparator() menu.addAction(self.__operation_format.action) menu.addAction(self.__operation_run_code.action) - menu.addAction(self._action_run_sel_code) + menu.addAction(self.__operation_run_selected_code.action) menu.addAction(self._action_save) menu.addAction(self._action_find) menu.addAction(self._action_replace) @@ -471,9 +471,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): """打开右键菜单""" menu = self.create_context_menu() # 根据条件决定菜单是否可用 - enabled = len(self.text().strip()) > 0 - # self._action_run_sel_code.setEnabled(self.textEdit.hasSelectedText()) - self._action_run_sel_code.setEnabled(enabled) logger.setLevel(logging.DEBUG) logger.debug('menu craeted') menu.exec_(self.text_edit.mapToGlobal(pos)) diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index a5bf1d4f..9aea8971 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -375,6 +375,7 @@ class PMPythonEditor(BaseEditor): def slot_code_sel_run(self): """运行选中代码""" + # TODO 存在问题,当选中了多行时,会报错 text = self.text(selected=True).strip() if not text: text = self.current_line_text().strip() -- Gitee From d38d7dfaee77d6a395bf47884646c5a4b0253e8d Mon Sep 17 00:00:00 2001 From: wolfpan Date: Fri, 6 Aug 2021 15:52:00 +0800 Subject: [PATCH 091/108] =?UTF-8?q?code=5Feditor:=20=E8=B0=83=E6=95=B4save?= =?UTF-8?q?=5Fcode=E7=9A=84=E5=BF=AB=E6=8D=B7=E9=94=AE=E7=9A=84=E5=86=99?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/editors/base_editor.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 73137f03..7bc1f348 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -158,9 +158,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # 绑定快捷键信号 # self.__operation_format.qt_shortcut.activated.connect(self.slot_code_format) - self._shortcut_save.activated.connect(self.slot_save) - self._action_save.triggered.connect(self.slot_save) - self._action_find.triggered.connect(self.slot_find) self._action_replace.triggered.connect(self.slot_replace) @@ -181,26 +178,34 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # self._action_view_breakpoints.triggered.connect(self.view_break_points) def _init_actions(self) -> None: - """初始化额外菜单项""" + """初始化快捷键和菜单项""" text_exists = lambda: len(self.text().strip()) > 0 + # 格式化代码 self.__operation_format = Operation( widget=self.text_edit, name='format code', label=self.tr('Format Code'), slot=self.slot_code_format, key_sequence='Ctrl+Alt+F', icon_name='format.svg', conditions=[text_exists], ) + # 运行代码 self.__operation_run_code = Operation( widget=self.text_edit, name='run code', label=self.tr('Run Code'), slot=self.slot_code_run, key_sequence='Ctrl+R', icon_name='run.svg', conditions=[text_exists], ) + # 运行选中代码 self.__operation_run_selected_code = Operation( widget=self.text_edit, name='run code', label=self.tr('Run Selected Code'), slot=self.slot_code_sel_run, key_sequence='F9', icon_name='python.svg', conditions=[text_exists], # TODO 添加判别条件:仅当有文本选中时才可用 ) - self._action_save = QAction(self.settings.get_icon('save.svg'), self.tr('Save'), self.text_edit) + # 保存代码 + self.__operation_save_code = Operation( + widget=self.text_edit, name='save code', label=self.tr('save.svg'), + slot=self.slot_save, key_sequence='Ctrl+S', icon_name='save.svg', + ) + self._action_find = QAction(self.tr('Find'), self.text_edit) self._action_replace = QAction(self.tr('Replace'), self.text_edit) @@ -210,9 +215,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self._shortcut_autocomp = QShortcut(QKeySequence('Ctrl+P'), self.text_edit, context=Qt.WidgetShortcut) self._action_autocomp.setShortcut(QKeySequence("Ctrl+P")) - self._action_save.setShortcut(QKeySequence('Ctrl+S')) - self._shortcut_save = QShortcut(QKeySequence('Ctrl+S'), self.text_edit, context=Qt.WidgetShortcut) - self._action_find.setShortcut(QKeySequence('Ctrl+F')) self._shortcut_find = QShortcut(QKeySequence('Ctrl+F'), self.text_edit, context=Qt.WidgetShortcut) @@ -457,7 +459,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): menu.addAction(self.__operation_format.action) menu.addAction(self.__operation_run_code.action) menu.addAction(self.__operation_run_selected_code.action) - menu.addAction(self._action_save) + menu.addAction(self.__operation_save_code.action) menu.addAction(self._action_find) menu.addAction(self._action_replace) menu.addAction(self._action_find_in_path) -- Gitee From 8e9bd348807ba6093720a0575275bc119d1bc4fc Mon Sep 17 00:00:00 2001 From: wolfpan Date: Fri, 6 Aug 2021 23:01:30 +0800 Subject: [PATCH 092/108] =?UTF-8?q?code=5Feditor:=20=E8=B0=83=E6=95=B4find?= =?UTF-8?q?=5Fcode=E7=9A=84=E5=BF=AB=E6=8D=B7=E9=94=AE=E7=9A=84=E5=86=99?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/dialogs/find_dialog.py | 23 ++++++++++--------- .../widgets/editors/base_editor.py | 15 ++++++------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/code_editor/widgets/dialogs/find_dialog.py b/packages/code_editor/widgets/dialogs/find_dialog.py index 47a20e33..a61bd5b1 100644 --- a/packages/code_editor/widgets/dialogs/find_dialog.py +++ b/packages/code_editor/widgets/dialogs/find_dialog.py @@ -5,24 +5,25 @@ from PySide2.QtWidgets import QDialog, QVBoxLayout, QPushButton, QHBoxLayout from pmgwidgets import PMGPanel if TYPE_CHECKING: - from ...widgets.editors.base_editor import PMGBaseEditor from ...widgets.text_edit.base_text_edit import PMBaseCodeEdit + from ..editors.base_editor import BaseEditor class FindDialog(QDialog): tr: Callable[[str], str] - def __init__(self, parent=None, text_editor: 'PMGBaseEditor' = None): + def __init__(self, parent: 'BaseEditor' = None): super(FindDialog, self).__init__(parent) - self.text_editor = text_editor - self.text_edit: 'PMBaseCodeEdit' = text_editor.text_edit - views = [('line_ctrl', 'text_to_find', self.tr('Text to Find'), ''), - ('line_ctrl', 'text_to_replace', self.tr('Text to Replace'), ''), - ('check_ctrl', 'wrap', self.tr('Wrap'), True), - ('check_ctrl', 'regex', self.tr('Regex'), False), - ('check_ctrl', 'case_sensitive', self.tr('Case Sensitive'), True), - ('check_ctrl', 'whole_word', self.tr('Whole Word'), True), - ] + self.text_editor = parent + self.text_edit: 'PMBaseCodeEdit' = parent.text_edit + views = [ + ('line_ctrl', 'text_to_find', self.tr('Text to Find'), ''), + ('line_ctrl', 'text_to_replace', self.tr('Text to Replace'), ''), + ('check_ctrl', 'wrap', self.tr('Wrap'), True), + ('check_ctrl', 'regex', self.tr('Regex'), False), + ('check_ctrl', 'case_sensitive', self.tr('Case Sensitive'), True), + ('check_ctrl', 'whole_word', self.tr('Whole Word'), True), + ] self.settings_panel = PMGPanel(parent=self, views=views) self.setLayout(QVBoxLayout()) self.layout().addWidget(self.settings_panel) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 7bc1f348..cab6654f 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -97,7 +97,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self.layout().addWidget(self.status_bar) # 设置各个对话框 - self.find_dialog = FindDialog(parent=self, text_editor=self) + self.find_dialog = FindDialog(parent=self) self.goto_line_dialog = GotoLineDialog(parent=self) self.last_save_time = 0 @@ -158,10 +158,8 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # 绑定快捷键信号 # self.__operation_format.qt_shortcut.activated.connect(self.slot_code_format) - self._action_find.triggered.connect(self.slot_find) self._action_replace.triggered.connect(self.slot_replace) - self._shortcut_find.activated.connect(self.slot_find) self._shortcut_replace.activated.connect(self.slot_replace) self._action_find_in_path.triggered.connect(self.slot_find_in_path) @@ -206,7 +204,11 @@ class BaseEditor(CodeEditorBaseObject, QWidget): slot=self.slot_save, key_sequence='Ctrl+S', icon_name='save.svg', ) - self._action_find = QAction(self.tr('Find'), self.text_edit) + self.__operation_find = Operation( + widget=self.text_edit, name='find code', label=self.tr('Find'), + slot=self.slot_find, key_sequence='Ctrl+F', + ) + self._action_replace = QAction(self.tr('Replace'), self.text_edit) self._action_find_in_path = QAction(self.tr('Find In Path'), self.text_edit) @@ -215,9 +217,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self._shortcut_autocomp = QShortcut(QKeySequence('Ctrl+P'), self.text_edit, context=Qt.WidgetShortcut) self._action_autocomp.setShortcut(QKeySequence("Ctrl+P")) - self._action_find.setShortcut(QKeySequence('Ctrl+F')) - self._shortcut_find = QShortcut(QKeySequence('Ctrl+F'), self.text_edit, context=Qt.WidgetShortcut) - self._action_replace.setShortcut(QKeySequence('Ctrl+H')) self._shortcut_replace = QShortcut(QKeySequence('Ctrl+H'), self.text_edit, context=Qt.WidgetShortcut) @@ -460,7 +459,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): menu.addAction(self.__operation_run_code.action) menu.addAction(self.__operation_run_selected_code.action) menu.addAction(self.__operation_save_code.action) - menu.addAction(self._action_find) + menu.addAction(self.__operation_find.action) menu.addAction(self._action_replace) menu.addAction(self._action_find_in_path) menu.addAction(self._action_add_breakpoint) -- Gitee From 27260208796bc60b86f802e3a9c446f124a257f6 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Fri, 6 Aug 2021 23:05:27 +0800 Subject: [PATCH 093/108] =?UTF-8?q?code=5Feditor:=20=E8=B0=83=E6=95=B4repl?= =?UTF-8?q?ace=20code=E7=9A=84=E5=BF=AB=E6=8D=B7=E9=94=AE=E7=9A=84?= =?UTF-8?q?=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/widgets/editors/base_editor.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index cab6654f..0e8e8ac1 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -158,10 +158,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # 绑定快捷键信号 # self.__operation_format.qt_shortcut.activated.connect(self.slot_code_format) - self._action_replace.triggered.connect(self.slot_replace) - - self._shortcut_replace.activated.connect(self.slot_replace) - self._action_find_in_path.triggered.connect(self.slot_find_in_path) self._shortcut_find_in_path.activated.connect(self.slot_find_in_path) @@ -204,12 +200,17 @@ class BaseEditor(CodeEditorBaseObject, QWidget): slot=self.slot_save, key_sequence='Ctrl+S', icon_name='save.svg', ) + # 查找代码 self.__operation_find = Operation( widget=self.text_edit, name='find code', label=self.tr('Find'), slot=self.slot_find, key_sequence='Ctrl+F', ) - self._action_replace = QAction(self.tr('Replace'), self.text_edit) + # 替换代码 + self.__operation_replace = Operation( + widget=self.text_edit, name='replace code', label=self.tr('Replace'), + slot=self.slot_replace, key_sequence='Ctrl+H', + ) self._action_find_in_path = QAction(self.tr('Find In Path'), self.text_edit) @@ -217,9 +218,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self._shortcut_autocomp = QShortcut(QKeySequence('Ctrl+P'), self.text_edit, context=Qt.WidgetShortcut) self._action_autocomp.setShortcut(QKeySequence("Ctrl+P")) - self._action_replace.setShortcut(QKeySequence('Ctrl+H')) - self._shortcut_replace = QShortcut(QKeySequence('Ctrl+H'), self.text_edit, context=Qt.WidgetShortcut) - self._action_find_in_path.setShortcut(QKeySequence('Ctrl+Shift+F')) self._shortcut_find_in_path = QShortcut(QKeySequence('Ctrl+Shift+F'), self.text_edit, context=Qt.WidgetShortcut) @@ -460,7 +458,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): menu.addAction(self.__operation_run_selected_code.action) menu.addAction(self.__operation_save_code.action) menu.addAction(self.__operation_find.action) - menu.addAction(self._action_replace) + menu.addAction(self.__operation_replace.action) menu.addAction(self._action_find_in_path) menu.addAction(self._action_add_breakpoint) menu.addAction(self._action_remove_breakpoint) -- Gitee From 74aff75e9d27f552139e625107981dfe4c0ed290 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sat, 7 Aug 2021 00:06:05 +0800 Subject: [PATCH 094/108] =?UTF-8?q?code=5Feditor:=20=E8=B0=83=E6=95=B4opti?= =?UTF-8?q?on=E7=9A=84=E5=8F=82=E6=95=B0key=5Fsequence=E4=B8=BAkey?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/utils/operation.py | 10 +++++----- packages/code_editor/widgets/editors/base_editor.py | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/code_editor/utils/operation.py b/packages/code_editor/utils/operation.py index c4442cd7..0d3f2168 100644 --- a/packages/code_editor/utils/operation.py +++ b/packages/code_editor/utils/operation.py @@ -14,7 +14,7 @@ class Operation(CodeEditorBaseObject): def __init__(self, widget: QWidget, name: str, label: str, slot: Callable, conditions: List[Callable[[], bool]] = None, - key_sequence: str = None, icon_name: str = None): + key: str = None, icon_name: str = None): """绑定一个快捷键操作。 这个操作可以在widget环境下直接访问,也可以添加到QMenu中。 @@ -25,7 +25,7 @@ class Operation(CodeEditorBaseObject): label: 显示在界面中的标签 slot: 回调事件 conditions: 根据这些条件判断是否可以调用,是一个函数的列表,每个函数都应返回一个布尔值,指示是否可用 - key_sequence: 快捷键,QSequence的参数 + key: 快捷键,QSequence的参数 icon_name: 在code_editor/assets/icons下面查找的路径 """ super(Operation, self).__init__() @@ -43,11 +43,11 @@ class Operation(CodeEditorBaseObject): self.__action.triggered.connect(slot) # 设置QAction和QShortcut快捷键 - if key_sequence is not None: - qt_sequence: QKeySequence = QKeySequence(key_sequence) + if key is not None: + qt_sequence: QKeySequence = QKeySequence(key) self.__action.setShortcut(qt_sequence) # noinspection PyArgumentList - qt_shortcut = QShortcut(key_sequence, widget, context=Qt.WidgetShortcut) + qt_shortcut = QShortcut(key, widget, context=Qt.WidgetShortcut) qt_shortcut.activated.connect(slot) @property diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 0e8e8ac1..fc8ff601 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -178,38 +178,38 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # 格式化代码 self.__operation_format = Operation( widget=self.text_edit, name='format code', label=self.tr('Format Code'), - slot=self.slot_code_format, key_sequence='Ctrl+Alt+F', icon_name='format.svg', conditions=[text_exists], + slot=self.slot_code_format, key='Ctrl+Alt+F', icon_name='format.svg', conditions=[text_exists], ) # 运行代码 self.__operation_run_code = Operation( widget=self.text_edit, name='run code', label=self.tr('Run Code'), - slot=self.slot_code_run, key_sequence='Ctrl+R', icon_name='run.svg', conditions=[text_exists], + slot=self.slot_code_run, key='Ctrl+R', icon_name='run.svg', conditions=[text_exists], ) # 运行选中代码 self.__operation_run_selected_code = Operation( widget=self.text_edit, name='run code', label=self.tr('Run Selected Code'), - slot=self.slot_code_sel_run, key_sequence='F9', icon_name='python.svg', conditions=[text_exists], + slot=self.slot_code_sel_run, key='F9', icon_name='python.svg', conditions=[text_exists], # TODO 添加判别条件:仅当有文本选中时才可用 ) # 保存代码 self.__operation_save_code = Operation( widget=self.text_edit, name='save code', label=self.tr('save.svg'), - slot=self.slot_save, key_sequence='Ctrl+S', icon_name='save.svg', + slot=self.slot_save, key='Ctrl+S', icon_name='save.svg', ) # 查找代码 self.__operation_find = Operation( widget=self.text_edit, name='find code', label=self.tr('Find'), - slot=self.slot_find, key_sequence='Ctrl+F', + slot=self.slot_find, key='Ctrl+F', ) # 替换代码 self.__operation_replace = Operation( widget=self.text_edit, name='replace code', label=self.tr('Replace'), - slot=self.slot_replace, key_sequence='Ctrl+H', + slot=self.slot_replace, key='Ctrl+H', ) self._action_find_in_path = QAction(self.tr('Find In Path'), self.text_edit) -- Gitee From 05360dbab3bd0b9a43fece840daf9f1d46f76243 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sat, 7 Aug 2021 00:10:40 +0800 Subject: [PATCH 095/108] =?UTF-8?q?code=5Feditor:=20=E4=BF=AE=E6=94=B9find?= =?UTF-8?q?=5Fin=5Fpath=E7=9A=84=E5=BF=AB=E6=8D=B7=E9=94=AE=E5=86=99?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code_editor/widgets/editors/base_editor.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index fc8ff601..d6dc6464 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -158,9 +158,6 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # 绑定快捷键信号 # self.__operation_format.qt_shortcut.activated.connect(self.slot_code_format) - self._action_find_in_path.triggered.connect(self.slot_find_in_path) - self._shortcut_find_in_path.activated.connect(self.slot_find_in_path) - self._action_autocomp.triggered.connect(self.auto_completion) self._shortcut_autocomp.activated.connect(self.auto_completion) @@ -212,15 +209,16 @@ class BaseEditor(CodeEditorBaseObject, QWidget): slot=self.slot_replace, key='Ctrl+H', ) - self._action_find_in_path = QAction(self.tr('Find In Path'), self.text_edit) + # 在路径中查找,暂不理解这个功能的含义 + self.__operation_find_in_path = Operation( + widget=self.text_edit, name='find in path', label=self.tr('Find In Path'), + slot=self.slot_find_in_path, key='Ctrl+Shift+F', + ) self._action_autocomp = QAction(self.tr('AutoComp'), self.text_edit) self._shortcut_autocomp = QShortcut(QKeySequence('Ctrl+P'), self.text_edit, context=Qt.WidgetShortcut) self._action_autocomp.setShortcut(QKeySequence("Ctrl+P")) - self._action_find_in_path.setShortcut(QKeySequence('Ctrl+Shift+F')) - self._shortcut_find_in_path = QShortcut(QKeySequence('Ctrl+Shift+F'), self.text_edit, context=Qt.WidgetShortcut) - self._shortcut_goto = QShortcut(QKeySequence('Ctrl+G'), self.text_edit, context=Qt.WidgetShortcut) self._action_add_breakpoint = QAction(self.settings.get_icon('breakpoint.svg'), @@ -459,7 +457,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): menu.addAction(self.__operation_save_code.action) menu.addAction(self.__operation_find.action) menu.addAction(self.__operation_replace.action) - menu.addAction(self._action_find_in_path) + menu.addAction(self.__operation_find_in_path.action) menu.addAction(self._action_add_breakpoint) menu.addAction(self._action_remove_breakpoint) menu.addAction(self._action_view_breakpoints) @@ -476,8 +474,8 @@ class BaseEditor(CodeEditorBaseObject, QWidget): logger.debug('menu deleted') def slot_find_in_path(self): - sel = self.text_edit.get_selected_text() - self.signal_request_find_in_path.emit(sel) + selected_text = self.text_edit.get_selected_text() + self.signal_request_find_in_path.emit(selected_text) def slot_find(self): self.find_dialog.show_replace_actions(replace_on=False) -- Gitee From f33d5faf51ba364d7594e8f20e51137ed4fd1ca5 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sat, 7 Aug 2021 00:21:28 +0800 Subject: [PATCH 096/108] =?UTF-8?q?code=5Feditor:=20=E4=BF=AE=E6=94=B9goto?= =?UTF-8?q?=20line=E7=9A=84=E5=BF=AB=E6=8D=B7=E9=94=AE=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/editors/base_editor.py | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index d6dc6464..178e5007 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -31,10 +31,10 @@ import os import time from typing import Dict, Callable, TYPE_CHECKING, Type -from PySide2.QtCore import SignalInstance, Signal, QDir, QCoreApplication, QPoint, Qt -from PySide2.QtGui import QTextDocument, QTextCursor, QKeySequence +from PySide2.QtCore import SignalInstance, Signal, QDir, QCoreApplication, QPoint +from PySide2.QtGui import QTextDocument, QTextCursor from PySide2.QtWidgets import QWidget, QMessageBox, QLabel, QVBoxLayout, QFileDialog, \ - QShortcut, QAction, QMenu + QAction, QMenu from features.extensions.extensionlib.extension_lib import ExtensionLib from ..dialogs.find_dialog import FindDialog @@ -155,22 +155,13 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # 绑定右键菜单信号 self.text_edit.customContextMenuRequested.connect(self.slot_custom_context_menu_requested) - # 绑定快捷键信号 - # self.__operation_format.qt_shortcut.activated.connect(self.slot_code_format) - - self._action_autocomp.triggered.connect(self.auto_completion) - self._shortcut_autocomp.activated.connect(self.auto_completion) - - self._shortcut_goto.activated.connect(self.slot_goto_line) - - # self._action_add_breakpoint.triggered.connect(self.slot_add_breakpoint_triggered) - # self._action_remove_breakpoint.triggered.connect(self.slot_remove_breakpoint_triggered) - - # self._action_view_breakpoints.triggered.connect(self.view_break_points) def _init_actions(self) -> None: """初始化快捷键和菜单项""" - text_exists = lambda: len(self.text().strip()) > 0 + + def text_exists(): + """判断是否有文本,如果有文本才允许使用自动排版等功能""" + return len(self.text().strip()) > 0 # 格式化代码 self.__operation_format = Operation( @@ -215,11 +206,17 @@ class BaseEditor(CodeEditorBaseObject, QWidget): slot=self.slot_find_in_path, key='Ctrl+Shift+F', ) - self._action_autocomp = QAction(self.tr('AutoComp'), self.text_edit) - self._shortcut_autocomp = QShortcut(QKeySequence('Ctrl+P'), self.text_edit, context=Qt.WidgetShortcut) - self._action_autocomp.setShortcut(QKeySequence("Ctrl+P")) + # 自动补全功能是每隔一段时间自动显示的,使用快捷键可以立刻显示 + self.__operation_auto_completion = Operation( + widget=self.text_edit, name='auto completion', label=self.tr('AutoComp'), + slot=self.auto_completion, key='Ctrl+P', + ) - self._shortcut_goto = QShortcut(QKeySequence('Ctrl+G'), self.text_edit, context=Qt.WidgetShortcut) + # 跳转到行 + self.__operation_goto_line = Operation( + widget=self.text_edit, name='goto line', label=self.tr('Goto Line'), + slot=self.slot_goto_line, key='Ctrl+G', + ) self._action_add_breakpoint = QAction(self.settings.get_icon('breakpoint.svg'), self.tr('Add Breakpoint'), -- Gitee From cc9294c1bad28027f9eca93486aa9383a949e476 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sat, 7 Aug 2021 00:34:34 +0800 Subject: [PATCH 097/108] =?UTF-8?q?code=5Feditor:=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=96=AD=E7=82=B9=E7=9A=84=E5=A2=9E=E5=88=A0=E6=9F=A5=E7=9A=84?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=94=AE=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/utils/operation.py | 26 +++++++++-------- .../widgets/editors/base_editor.py | 29 ++++++++++++------- .../widgets/editors/python_editor.py | 2 -- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/packages/code_editor/utils/operation.py b/packages/code_editor/utils/operation.py index 0d3f2168..13c26cae 100644 --- a/packages/code_editor/utils/operation.py +++ b/packages/code_editor/utils/operation.py @@ -8,11 +8,7 @@ from packages.code_editor.utils.base_object import CodeEditorBaseObject class Operation(CodeEditorBaseObject): - widget: QWidget # 快捷键绑定的小部件 - name: str # 快捷键的名称,在每个插件中应当是唯一存在的,用于后面显示在设置项中 - label: str # 显示在菜单中的名称 - - def __init__(self, widget: QWidget, name: str, label: str, slot: Callable, + def __init__(self, widget: QWidget, name: str, label: str, slot: Callable = None, conditions: List[Callable[[], bool]] = None, key: str = None, icon_name: str = None): """绑定一个快捷键操作。 @@ -21,17 +17,19 @@ class Operation(CodeEditorBaseObject): Args: widget: 需要绑定事件的小部件,似乎text_edit可以用而QWidget不行,具体没有详查 - name: 快捷键的名称,用于后面显示在设置项中 + name: 快捷键的名称,用于后面显示在设置项中,可以根据这个Name来设置并查找快捷键 label: 显示在界面中的标签 - slot: 回调事件 + slot: 回调事件,如不定义回调事件,则在Menu中永远显示为不可用状态 conditions: 根据这些条件判断是否可以调用,是一个函数的列表,每个函数都应返回一个布尔值,指示是否可用 key: 快捷键,QSequence的参数 icon_name: 在code_editor/assets/icons下面查找的路径 """ super(Operation, self).__init__() - self.widget = widget + self.widget: QWidget = widget self.__conditions = conditions - self.name = name + self.name: str = name + + has_slot = slot is not None # 设置Action的图标 if icon_name is None: @@ -40,15 +38,19 @@ class Operation(CodeEditorBaseObject): icon = QIcon(self.settings.get_icon(icon_name)) self.__action: QAction = QAction(icon, label, widget) # 用于进行缓存,以免每次都要重新创建一个action对象 # noinspection PyUnresolvedReferences - self.__action.triggered.connect(slot) + has_slot and self.__action.triggered.connect(slot) # 设置QAction和QShortcut快捷键 if key is not None: qt_sequence: QKeySequence = QKeySequence(key) self.__action.setShortcut(qt_sequence) # noinspection PyArgumentList - qt_shortcut = QShortcut(key, widget, context=Qt.WidgetShortcut) - qt_shortcut.activated.connect(slot) + has_slot and QShortcut(key, widget, context=Qt.WidgetShortcut).activated.connect(slot) + + # 如果没有定义回调事件,则直接设置这个控件为不可用状态,不过仍然显示在菜单中 + if not has_slot: + self.__action.setEnabled(False) + self.__conditions = None @property def action(self) -> QAction: diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 178e5007..1af25aa7 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -34,7 +34,7 @@ from typing import Dict, Callable, TYPE_CHECKING, Type from PySide2.QtCore import SignalInstance, Signal, QDir, QCoreApplication, QPoint from PySide2.QtGui import QTextDocument, QTextCursor from PySide2.QtWidgets import QWidget, QMessageBox, QLabel, QVBoxLayout, QFileDialog, \ - QAction, QMenu + QMenu from features.extensions.extensionlib.extension_lib import ExtensionLib from ..dialogs.find_dialog import FindDialog @@ -218,14 +218,21 @@ class BaseEditor(CodeEditorBaseObject, QWidget): slot=self.slot_goto_line, key='Ctrl+G', ) - self._action_add_breakpoint = QAction(self.settings.get_icon('breakpoint.svg'), - self.tr('Add Breakpoint'), - self.text_edit) - self._action_remove_breakpoint = QAction(self.tr('Remove Breakpoint'), - self.text_edit) + # 添加断点 + self.__operation_add_breakpoint = Operation( + widget=self.text_edit, name='add breakpoint', label=self.tr('Add Breakpoint'), + icon_name='breakpoint.svg', + ) + + # 移除断点 + self.__operation_remove_breakpoint = Operation( + widget=self.text_edit, name='remove breakpoint', label=self.tr('Remove Breakpoint'), + ) - self._action_view_breakpoints = QAction(self.tr('View BreakPoints'), - self.text_edit) + # 查看所有断点 + self.__operation_view_breakpoints = Operation( + widget=self.text_edit, name='view breakpoints', label=self.tr('View BreakPoints'), + ) def auto_completion(self): pass @@ -455,9 +462,9 @@ class BaseEditor(CodeEditorBaseObject, QWidget): menu.addAction(self.__operation_find.action) menu.addAction(self.__operation_replace.action) menu.addAction(self.__operation_find_in_path.action) - menu.addAction(self._action_add_breakpoint) - menu.addAction(self._action_remove_breakpoint) - menu.addAction(self._action_view_breakpoints) + menu.addAction(self.__operation_add_breakpoint.action) + menu.addAction(self.__operation_remove_breakpoint.action) + menu.addAction(self.__operation_view_breakpoints.action) # menu.addAction(self) return menu diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index 9aea8971..ffe78795 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -104,8 +104,6 @@ class PMPythonEditor(BaseEditor): self._action_goto_definition.setShortcut(QKeySequence('Ctrl+B')) self._action_help_in_console.setVisible(False) - self._action_add_breakpoint.setVisible(False) - self._action_remove_breakpoint.setVisible(False) # noinspection PyUnresolvedReferences def _init_signals(self) -> None: -- Gitee From 54296b1615a380ecf1e7ef940b472cb3b108d610 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sat, 7 Aug 2021 10:47:45 +0800 Subject: [PATCH 098/108] =?UTF-8?q?code=5Feditor:=20=E8=B0=83=E6=95=B4goto?= =?UTF-8?q?=5Fline=E8=87=B3text=5Fedit=EF=BC=8C=E6=9C=AA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/widgets/editors/base_editor.py | 8 +++++--- packages/code_editor/widgets/editors/python_editor.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 1af25aa7..2d2bef97 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -84,7 +84,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # 设置实际的代码编辑区 assert self.text_edit_class is not None - self.text_edit = self.text_edit_class(self) + self.text_edit: 'PMBaseCodeEdit' = self.text_edit_class(self) 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)) @@ -113,7 +113,9 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self.status_bar.setText(f'行:{row + 1},列:{col + 1}') def goto_line(self, line_no: int): - """跳转到对应行列""" + """跳转到对应行列 + + TODO 这个函数应当是属于text_edit的功能""" self.text_edit.go_to_line(line_no) def search_word(self, text_to_find: str, wrap: bool, regex: bool, case_sensitive: bool, whole_word: bool, @@ -494,7 +496,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): self.goto_line_dialog.set_max_row_count(self.text_edit.blockCount()) ret = self.goto_line_dialog.exec_() if ret: - self.goto_line(self.goto_line_dialog.get_line()) + self.text_edit.go_to_line(self.goto_line_dialog.get_line()) def set_indicators(self, msg, clear=True): """ diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index ffe78795..1b9db73b 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -352,7 +352,7 @@ class PMPythonEditor(BaseEditor): ) self.set_text(reformatted_source) # TODO 重构代码后需要保证光标的位置不动,可以考虑使用parso实现,这里目前的bug有些多 - self.goto_line(first_line + 1) # 跳转回开始时的行 + self.text_edit.go_to_line(first_line + 1) # 跳转回开始时的行 # self.text_edit.goToLine(first_line) except Exception as e: logger.warning(str(e)) -- Gitee From 2fd37dc842a6fb3f83db66f2f5280793c98dac1c Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sat, 7 Aug 2021 23:44:34 +0800 Subject: [PATCH 099/108] =?UTF-8?q?code=5Feditor:=20=E5=B0=86text=5Fedit?= =?UTF-8?q?=E7=9A=84=E5=BF=AB=E6=8D=B7=E9=94=AE=E5=A4=84=E7=90=86=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E4=BB=8E=E5=A4=9A=E5=88=86=E6=94=AFif-elif-else?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=BA=E5=AD=97=E5=85=B8=E6=98=A0=E5=B0=84?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/text_edit/base_text_edit.py | 141 ++++++++++++------ 1 file changed, 95 insertions(+), 46 deletions(-) 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 b9c633cc..1c3b0eb6 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -3,9 +3,10 @@ import logging import os import re import time +from functools import cached_property from itertools import groupby from queue import Queue -from typing import Callable, Tuple, Dict, List, TYPE_CHECKING, Type +from typing import Callable, Tuple, Dict, List, TYPE_CHECKING, Type, Any from PySide2.QtCore import SignalInstance, Signal, Qt, QTimer, QModelIndex, QUrl, QRect from PySide2.QtGui import QFocusEvent, QTextCursor, QMouseEvent, QKeyEvent, QDragEnterEvent, QDropEvent, QPainter, \ @@ -316,51 +317,94 @@ class PMBaseCodeEdit(QPlainTextEdit): self.signal_focused_in.emit(None) super().mousePressEvent(event) + @cached_property + def key_press_mapping(self) -> Dict[Tuple[Any, Any], Callable[[QKeyEvent], None]]: + """将按键分配给各个函数的映射。 + + 键是一对数值,分别是按键及Modifier,也就是Ctrl、ALt等辅助键。 + + 值就是回调函数。 + """ + return { + (Qt.Key_Tab, Qt.NoModifier): self.on_tab, + (Qt.Key_Backtab, Qt.NoModifier): self.on_back_tab, + # TODO 迁移至Operation体系 + # 这个就比较适合使用Operation来处理,而不是定义在Key中。 + (Qt.Key_Slash, Qt.ControlModifier): self.comment, + (Qt.Key_Return, Qt.NoModifier): self.on_return_pressed, + (Qt.Key_Backspace, Qt.NoModifier): self.on_backspace, + # 左括号采用同一个回调进行处理 + (Qt.Key_ParenLeft, Qt.NoModifier): self.on_left_parenthesis, + (Qt.Key_BracketLeft, Qt.NoModifier): self.on_left_parenthesis, + (Qt.Key_BraceLeft, Qt.NoModifier): self.on_left_parenthesis, + # 右括号采用同一个回调进行处理 + (Qt.Key_ParenRight, Qt.NoModifier): self.on_right_parenthesis, + (Qt.Key_BracketRight, Qt.NoModifier): self.on_right_parenthesis, + (Qt.Key_BraceRight, Qt.NoModifier): self.on_right_parenthesis, + } + def keyPressEvent(self, event: QKeyEvent) -> None: - k = event.key() - if k == Qt.Key_Tab: - self.on_tab() - elif k == Qt.Key_Backtab: - self.on_back_tab() - elif k == Qt.Key_Slash and event.modifiers() == Qt.ControlModifier: - self.comment() - elif k == Qt.Key_Return: - if self.textCursor().atBlockEnd(): - self.on_return_pressed() - event.accept() - elif k == Qt.Key_Backspace: - self.on_backspace(event) - event.accept() - elif k in (Qt.Key_ParenLeft, Qt.Key_BracketLeft, Qt.Key_BraceLeft): - cursor = self.textCursor() - cursor.beginEditBlock() - string = {Qt.Key_ParenLeft: '()', Qt.Key_BracketLeft: '[]', Qt.Key_BraceLeft: '{}'}[k] - cursor.insertText(string) - cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.MoveAnchor, 1) - cursor.endEditBlock() - self.setTextCursor(cursor) - event.accept() - elif k in (Qt.Key_ParenRight, Qt.Key_BracketRight, Qt.Key_BraceRight): - cursor = self.textCursor() - code = self.toPlainText() - cursor.beginEditBlock() - position = cursor.position() - left, right = { - Qt.Key_ParenRight: ('(', ')'), Qt.Key_BracketRight: ('[', ']'), Qt.Key_BraceRight: ('{', '}')}[k] - analyzer = GrammarAnalyzer() - analyzer.feed(code) - length = len(code) - if position == length or analyzer.is_not_matched(position, left) or code[position] != right: - cursor.insertText(right) - else: - cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.MoveAnchor, 1) - cursor.endEditBlock() - self.setTextCursor(cursor) - event.accept() + """处理按键事件 + + TODO 将按键处理逻辑写成一个独立的教程,然后在这边加引用 + + 按键处理的基本逻辑是,一个按键仅使用一个处理程序进行处理。 + 即,如果在按键映射中找到了相应的按键的处理函数,则用该函数进行处理,而如果没有找到,则使用super进行处理。 + + 每个按键的处理函数都应使用如下的格式进行定义: + + def on_key_tab_pressed(self, event: QKeyEvent) -> None: + do_some_thing() + event.accept() # 中止事件的传递,不调用的话事件将传交给父组件再次处理 + + 按键处理函数不需要返回值。 + + 每个按键的处理函数都需要自行判断是否需要调用event.accept(),以提供足够的灵活性。 + + 按键处理函数与默认的super().keyPressEvent()是互斥的。 + 如果仍需要使用默认的keyPressEvent进行处理,则可以在自定义的按键处理函数中进行如下调用: super().keyPressEvent(event) - def on_backspace(self, _: QKeyEvent): + 按键处理有两种方式:可以通过Operation进行定义,在BaseEditor中有描述,也可以在keyPressEvent内进行定义。 + 具体的性能没有进行过查证,不过直观上看,使用keyPressEvent在性能上会存在优势。 + + """ + callback = self.key_press_mapping.get((event.key(), int(event.modifiers())), None) + if callback is not None: + callback(event) + else: + super().keyPressEvent(event) + + def on_left_parenthesis(self, event: QKeyEvent): + cursor = self.textCursor() + cursor.beginEditBlock() + string = {Qt.Key_ParenLeft: '()', Qt.Key_BracketLeft: '[]', Qt.Key_BraceLeft: '{}'}[event.key()] + cursor.insertText(string) + cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.MoveAnchor, 1) + cursor.endEditBlock() + self.setTextCursor(cursor) + event.accept() + + def on_right_parenthesis(self, event: QKeyEvent): + cursor = self.textCursor() + code = self.toPlainText() + cursor.beginEditBlock() + position = cursor.position() + left, right = { + Qt.Key_ParenRight: ('(', ')'), Qt.Key_BracketRight: ('[', ']'), Qt.Key_BraceRight: ('{', '}')}[k] + analyzer = GrammarAnalyzer() + analyzer.feed(code) + length = len(code) + if position == length or analyzer.is_not_matched(position, left) or code[position] != right: + cursor.insertText(right) + else: + cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.MoveAnchor, 1) + cursor.endEditBlock() + self.setTextCursor(cursor) + event.accept() + + def on_backspace(self, event: QKeyEvent): cursor: QTextCursor = self.textCursor() cursor.beginEditBlock() previous_text = cursor.block().text()[:cursor.positionInBlock()] @@ -377,6 +421,7 @@ class PMBaseCodeEdit(QPlainTextEdit): else: cursor.deletePreviousChar() cursor.endEditBlock() + event.accept() @contextlib.contextmanager def editing_block_cursor(self): @@ -385,11 +430,14 @@ class PMBaseCodeEdit(QPlainTextEdit): yield cursor cursor.endEditBlock() - def on_return_pressed(self): + def on_return_pressed(self, event: QKeyEvent): """按回车换行的方法 TODO 使用parso进行解析并更新 """ + if not self.textCursor().atBlockEnd(): + super().keyPressEvent(event) + return with self.editing_block_cursor() as cursor: text = cursor.block().text() text, indent = get_indent(text) @@ -397,8 +445,9 @@ class PMBaseCodeEdit(QPlainTextEdit): cursor.insertText('\n' + ' ' * (indent + 4)) else: cursor.insertText('\n' + ' ' * indent) + event.accept() - def comment(self): + def comment(self, _: QKeyEvent): cursor = self.textCursor() cursor.beginEditBlock() if cursor.hasSelection(): @@ -450,7 +499,7 @@ class PMBaseCodeEdit(QPlainTextEdit): cursor.endEditBlock() - def on_back_tab(self): + def on_back_tab(self, _: QKeyEvent): cursor = self.textCursor() if cursor.hasSelection(): self.edit_unindent() @@ -466,7 +515,7 @@ class PMBaseCodeEdit(QPlainTextEdit): # print('cursor.selected',cursor.selectedText()) cursor.removeSelectedText() - def on_tab(self): + def on_tab(self, _: QKeyEvent): with self.editing_block_cursor() as cursor: if cursor.hasSelection(): self.edit_indent() -- Gitee From 419640e0d5e87c60ccf866c9b02a2b568dc5c2fe Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sun, 8 Aug 2021 01:08:17 +0800 Subject: [PATCH 100/108] =?UTF-8?q?code=5Feditor:=20=E5=BF=AB=E6=8D=B7?= =?UTF-8?q?=E9=94=AE=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/text_edit/base_text_edit.py | 53 +++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) 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 1c3b0eb6..6324b829 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -325,7 +325,7 @@ class PMBaseCodeEdit(QPlainTextEdit): 值就是回调函数。 """ - return { + mapping = { (Qt.Key_Tab, Qt.NoModifier): self.on_tab, (Qt.Key_Backtab, Qt.NoModifier): self.on_back_tab, # TODO 迁移至Operation体系 @@ -342,6 +342,10 @@ class PMBaseCodeEdit(QPlainTextEdit): (Qt.Key_BracketRight, Qt.NoModifier): self.on_right_parenthesis, (Qt.Key_BraceRight, Qt.NoModifier): self.on_right_parenthesis, } + mapping = {(int(k[0]), int(k[1])): v for k, v in mapping.items()} + from pprint import pprint + pprint(mapping) + return mapping def keyPressEvent(self, event: QKeyEvent) -> None: """处理按键事件 @@ -370,7 +374,20 @@ class PMBaseCodeEdit(QPlainTextEdit): 具体的性能没有进行过查证,不过直观上看,使用keyPressEvent在性能上会存在优势。 """ - callback = self.key_press_mapping.get((event.key(), int(event.modifiers())), None) + # TODO 按键处理逻辑仍存在bug,应当分为字符映射和键盘映射两种情况进行处理 + # 即分别通过event.text()和event.key()+event.modifier()进行处理 + key = (int(event.key()), int(event.modifiers())) + callback = self.key_press_mapping.get(key, None) + print('-' * 100) + print(f'alt : {bin(Qt.AltModifier):>30}') + print(f'alt : {bin(Qt.Key_Alt):>30}') + print(f'ctrl : {bin(Qt.ControlModifier):>30}') + print(f'ctrl : {bin(Qt.Key_Control):>30}') + print(f'shift: {bin(Qt.ShiftModifier):>30}') + print(f'shift: {bin(Qt.Key_Shift):>30}') + print(f'unkwn: {bin(Qt.Key_unknown):>30}') + print(f'key : {bin(event.key()):>30}') + print(f'modif: {bin(event.modifiers()):>30}') if callback is not None: callback(event) else: @@ -386,22 +403,26 @@ class PMBaseCodeEdit(QPlainTextEdit): self.setTextCursor(cursor) event.accept() + @property + def code(self): + return self.toPlainText() + def on_right_parenthesis(self, event: QKeyEvent): - cursor = self.textCursor() - code = self.toPlainText() - cursor.beginEditBlock() - position = cursor.position() left, right = { - Qt.Key_ParenRight: ('(', ')'), Qt.Key_BracketRight: ('[', ']'), Qt.Key_BraceRight: ('{', '}')}[k] - analyzer = GrammarAnalyzer() - analyzer.feed(code) - length = len(code) - if position == length or analyzer.is_not_matched(position, left) or code[position] != right: - cursor.insertText(right) - else: - cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.MoveAnchor, 1) - cursor.endEditBlock() - self.setTextCursor(cursor) + Qt.Key_ParenRight: ('(', ')'), + Qt.Key_BracketRight: ('[', ']'), + Qt.Key_BraceRight: ('{', '}'), + }[event.key()] + code = self.code + with self.editing_block_cursor() as cursor: + position = cursor.position() + analyzer = GrammarAnalyzer() + analyzer.feed(code) + length = len(code) + if position == length or analyzer.is_not_matched(position, left) or code[position] != right: + cursor.insertText(right) + else: + cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.MoveAnchor, 1) event.accept() def on_backspace(self, event: QKeyEvent): -- Gitee From cbfcab1ada084dac1de1f46dab254da2b9970c09 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sun, 8 Aug 2021 11:08:27 +0800 Subject: [PATCH 101/108] =?UTF-8?q?code=5Feditor:=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=94=AE=E7=9A=84=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/text_edit/base_text_edit.py | 55 +++++++++---------- 1 file changed, 25 insertions(+), 30 deletions(-) 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 6324b829..688bdc8c 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -321,30 +321,26 @@ class PMBaseCodeEdit(QPlainTextEdit): def key_press_mapping(self) -> Dict[Tuple[Any, Any], Callable[[QKeyEvent], None]]: """将按键分配给各个函数的映射。 - 键是一对数值,分别是按键及Modifier,也就是Ctrl、ALt等辅助键。 + 键支持以下多种情况: + + 1. str(event.text()), 例如:'(', '?' + 2. int(event.key()), 例如:Qt.Key_Backspace + 3. (int(event.key()), int(event.modifiers())),例如:(Qt.Key_Slash, Qt.ControlModifier) 值就是回调函数。 """ mapping = { - (Qt.Key_Tab, Qt.NoModifier): self.on_tab, - (Qt.Key_Backtab, Qt.NoModifier): self.on_back_tab, + Qt.Key_Tab: self.on_tab, + Qt.Key_Backtab: self.on_back_tab, # TODO 迁移至Operation体系 - # 这个就比较适合使用Operation来处理,而不是定义在Key中。 + # 这个就比较适合使用Operation来处理,因为可以显示在右键菜单中,而定义在key中就没有了这个优势。 (Qt.Key_Slash, Qt.ControlModifier): self.comment, - (Qt.Key_Return, Qt.NoModifier): self.on_return_pressed, - (Qt.Key_Backspace, Qt.NoModifier): self.on_backspace, - # 左括号采用同一个回调进行处理 - (Qt.Key_ParenLeft, Qt.NoModifier): self.on_left_parenthesis, - (Qt.Key_BracketLeft, Qt.NoModifier): self.on_left_parenthesis, - (Qt.Key_BraceLeft, Qt.NoModifier): self.on_left_parenthesis, - # 右括号采用同一个回调进行处理 - (Qt.Key_ParenRight, Qt.NoModifier): self.on_right_parenthesis, - (Qt.Key_BracketRight, Qt.NoModifier): self.on_right_parenthesis, - (Qt.Key_BraceRight, Qt.NoModifier): self.on_right_parenthesis, + Qt.Key_Return: self.on_return_pressed, + Qt.Key_Backspace: self.on_backspace, + # 左括号、右括号分别采用同一个回调进行处理 + '(': self.on_left_parenthesis, '[': self.on_left_parenthesis, '{': self.on_left_parenthesis, + ')': self.on_right_parenthesis, ']': self.on_right_parenthesis, '}': self.on_right_parenthesis, } - mapping = {(int(k[0]), int(k[1])): v for k, v in mapping.items()} - from pprint import pprint - pprint(mapping) return mapping def keyPressEvent(self, event: QKeyEvent) -> None: @@ -376,19 +372,13 @@ class PMBaseCodeEdit(QPlainTextEdit): """ # TODO 按键处理逻辑仍存在bug,应当分为字符映射和键盘映射两种情况进行处理 # 即分别通过event.text()和event.key()+event.modifier()进行处理 - key = (int(event.key()), int(event.modifiers())) - callback = self.key_press_mapping.get(key, None) - print('-' * 100) - print(f'alt : {bin(Qt.AltModifier):>30}') - print(f'alt : {bin(Qt.Key_Alt):>30}') - print(f'ctrl : {bin(Qt.ControlModifier):>30}') - print(f'ctrl : {bin(Qt.Key_Control):>30}') - print(f'shift: {bin(Qt.ShiftModifier):>30}') - print(f'shift: {bin(Qt.Key_Shift):>30}') - print(f'unkwn: {bin(Qt.Key_unknown):>30}') - print(f'key : {bin(event.key()):>30}') - print(f'modif: {bin(event.modifiers()):>30}') - if callback is not None: + 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: + callback(event) + elif no_modifier and (callback := self.key_press_mapping.get(key, None)) is not None: + callback(event) + elif (callback := self.key_press_mapping.get((key, modifiers), None)) is not None: callback(event) else: super().keyPressEvent(event) @@ -408,6 +398,7 @@ class PMBaseCodeEdit(QPlainTextEdit): return self.toPlainText() def on_right_parenthesis(self, event: QKeyEvent): + print(f'received {event.text()}') left, right = { Qt.Key_ParenRight: ('(', ')'), Qt.Key_BracketRight: ('[', ']'), @@ -419,9 +410,12 @@ class PMBaseCodeEdit(QPlainTextEdit): analyzer = GrammarAnalyzer() analyzer.feed(code) length = len(code) + print(f'{position == length}, {analyzer.is_not_matched(position, left)}, {code[position]}') if position == length or analyzer.is_not_matched(position, left) or code[position] != right: + print('first') cursor.insertText(right) else: + print('second') cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.MoveAnchor, 1) event.accept() @@ -450,6 +444,7 @@ class PMBaseCodeEdit(QPlainTextEdit): cursor.beginEditBlock() yield cursor cursor.endEditBlock() + self.setTextCursor(cursor) def on_return_pressed(self, event: QKeyEvent): """按回车换行的方法 -- Gitee From 11e602dedca0c4bee711d154689bc9593b3141d0 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sun, 8 Aug 2021 11:11:20 +0800 Subject: [PATCH 102/108] =?UTF-8?q?code=5Feditor:=20=E9=87=8D=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E5=90=84=E4=B8=AA=E7=B1=BB=E5=90=8D=E4=B8=BAPM?= =?UTF-8?q?=E5=BC=80=E5=A4=B4=EF=BC=8C=E4=BB=A5=E5=AE=9E=E7=8E=B0=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E5=AE=B6=E7=9A=84=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/dialogs/find_dialog.py | 4 ++-- .../widgets/dialogs/goto_line_dialog.py | 4 ++-- .../widgets/editors/base_editor.py | 8 +++---- .../widgets/editors/markdown_editor.py | 6 ++--- .../widgets/editors/python_editor.py | 10 ++++---- packages/code_editor/widgets/tab_widget.py | 24 +++++++++---------- .../widgets/text_edit/base_text_edit.py | 4 ++-- .../test_gui/test_python_editor.py | 6 ++--- 8 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/code_editor/widgets/dialogs/find_dialog.py b/packages/code_editor/widgets/dialogs/find_dialog.py index a61bd5b1..38b96105 100644 --- a/packages/code_editor/widgets/dialogs/find_dialog.py +++ b/packages/code_editor/widgets/dialogs/find_dialog.py @@ -6,13 +6,13 @@ from pmgwidgets import PMGPanel if TYPE_CHECKING: from ...widgets.text_edit.base_text_edit import PMBaseCodeEdit - from ..editors.base_editor import BaseEditor + from ..editors.base_editor import PMBaseEditor class FindDialog(QDialog): tr: Callable[[str], str] - def __init__(self, parent: 'BaseEditor' = None): + def __init__(self, parent: 'PMBaseEditor' = None): super(FindDialog, self).__init__(parent) self.text_editor = parent self.text_edit: 'PMBaseCodeEdit' = parent.text_edit diff --git a/packages/code_editor/widgets/dialogs/goto_line_dialog.py b/packages/code_editor/widgets/dialogs/goto_line_dialog.py index 18eca009..e7960016 100644 --- a/packages/code_editor/widgets/dialogs/goto_line_dialog.py +++ b/packages/code_editor/widgets/dialogs/goto_line_dialog.py @@ -5,11 +5,11 @@ from PySide2.QtWidgets import QDialog, QMessageBox from ..ui.gotoline import Ui_DialogGoto -class GotoLineDialog(QDialog, Ui_DialogGoto): +class PMGotoLineDialog(QDialog, Ui_DialogGoto): tr: Callable[[str], str] def __init__(self, parent=None): - super(GotoLineDialog, self).__init__(parent) + super(PMGotoLineDialog, self).__init__(parent) self.current_line = -1 self.max_row_count = 0 self.setupUi(self) diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index 2d2bef97..aab5153d 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -38,7 +38,7 @@ from PySide2.QtWidgets import QWidget, QMessageBox, QLabel, QVBoxLayout, QFileDi from features.extensions.extensionlib.extension_lib import ExtensionLib from ..dialogs.find_dialog import FindDialog -from ..dialogs.goto_line_dialog import GotoLineDialog +from ..dialogs.goto_line_dialog import PMGotoLineDialog from ..text_edit.base_text_edit import PMBaseCodeEdit from ...utils.base_object import CodeEditorBaseObject from ...utils.operation import Operation @@ -47,7 +47,7 @@ from ...utils.utils import decode logger = logging.getLogger(__name__) -class BaseEditor(CodeEditorBaseObject, QWidget): +class PMBaseEditor(CodeEditorBaseObject, QWidget): """ 编辑器的各种操件应当由这个类及其子类进行管理,包括代码重构、代码缩进等内容。 快捷键也应当定义在这个类中。 @@ -66,7 +66,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # 子控件的类型提示 find_dialog: 'FindDialog' - goto_line_dialog: 'GotoLineDialog' + goto_line_dialog: 'PMGotoLineDialog' find_dialog: 'FindDialog' extension_lib: 'ExtensionLib' @@ -98,7 +98,7 @@ class BaseEditor(CodeEditorBaseObject, QWidget): # 设置各个对话框 self.find_dialog = FindDialog(parent=self) - self.goto_line_dialog = GotoLineDialog(parent=self) + self.goto_line_dialog = PMGotoLineDialog(parent=self) self.last_save_time = 0 self._path = '' diff --git a/packages/code_editor/widgets/editors/markdown_editor.py b/packages/code_editor/widgets/editors/markdown_editor.py index 51313997..03c6226a 100644 --- a/packages/code_editor/widgets/editors/markdown_editor.py +++ b/packages/code_editor/widgets/editors/markdown_editor.py @@ -4,12 +4,12 @@ import time from PySide2.QtWidgets import QHBoxLayout, QMessageBox from packages.qt_vditor.client import Window -from .base_editor import BaseEditor +from .base_editor import PMBaseEditor -class PMMarkdownEditor(BaseEditor): +class PMMarkdownEditorPM(PMBaseEditor): def __init__(self, parent=None): - super(PMMarkdownEditor, self).__init__(parent=parent) + super(PMMarkdownEditorPM, 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/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index 1b9db73b..eeceb1b7 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -43,7 +43,7 @@ from yapf.yapflib import py3compat, yapf_api from pmgwidgets import in_unit_test, PMGOneShotThreadRunner, run_python_file_in_terminal, parse_simplified_pmgjson, \ PMGPanelDialog -from .base_editor import BaseEditor +from .base_editor import PMBaseEditor from ..text_edit.python_text_edit import PMPythonCodeEdit from ...utils.highlighter.python_highlighter import PythonHighlighter @@ -51,7 +51,7 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) -class PMPythonEditor(BaseEditor): +class PMPythonEditorPM(PMBaseEditor): """ 自定义编辑器控件 """ @@ -67,7 +67,7 @@ class PMPythonEditor(BaseEditor): tr: Callable[[str], str] def __init__(self, parent=None): - super(PMPythonEditor, self).__init__(parent) # , comment_string='# ') + super(PMPythonEditorPM, self).__init__(parent) # , comment_string='# ') self.browser_id = None self._parent = parent self.last_hint = '' @@ -90,7 +90,7 @@ class PMPythonEditor(BaseEditor): def _init_actions(self) -> None: """初始化事件""" - super(PMPythonEditor, self)._init_actions() + super(PMPythonEditorPM, self)._init_actions() self._action_help = QAction(self.tr('Function Help'), self.text_edit) self._shortcut_help = QShortcut(QKeySequence('F1'), self.text_edit, context=Qt.WidgetShortcut) self._action_help.setShortcut(QKeySequence('F1')) @@ -108,7 +108,7 @@ class PMPythonEditor(BaseEditor): # noinspection PyUnresolvedReferences def _init_signals(self) -> None: """初始化信号绑定。""" - super(PMPythonEditor, self)._init_signals() + super(PMPythonEditorPM, self)._init_signals() self._shortcut_help.activated.connect(self.get_help) self._action_help.triggered.connect(self.get_help) diff --git a/packages/code_editor/widgets/tab_widget.py b/packages/code_editor/widgets/tab_widget.py index 890db8c1..41bbeaa9 100644 --- a/packages/code_editor/widgets/tab_widget.py +++ b/packages/code_editor/widgets/tab_widget.py @@ -10,8 +10,8 @@ from PySide2.QtWidgets import QTabWidget, QSizePolicy, QMessageBox, QFileDialog, import pmgwidgets from pmgwidgets import PMDockObject, UndoManager, PMGFileSystemWatchdog, in_unit_test -from .editors.markdown_editor import PMMarkdownEditor -from .editors.python_editor import PMPythonEditor +from .editors.markdown_editor import PMMarkdownEditorPM +from .editors.python_editor import PMPythonEditorPM from .ui.findinpath import FindInPathWidget from ..utils.code_checker.base_code_checker import CodeCheckWorker from ..utils.highlighter.python_highlighter import PythonHighlighter @@ -189,7 +189,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): return self._keywords = list(keywords) - def get_current_editor(self) -> Optional['PMPythonEditor']: + def get_current_editor(self) -> Optional['PMPythonEditorPM']: """ get current editor Returns: @@ -293,13 +293,13 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): widget: 'PMBaseEditor' = None if path.endswith('.py') or path == os.path.join(QDir.tempPath(), 'Untitled-%d' % self._index).replace(os.sep, '/'): - widget = PMPythonEditor(parent=self) + widget = PMPythonEditorPM(parent=self) # elif path.endswith(('.c', '.cpp', '.h')): # widget = PMCPPEditor(parent=self) # elif path.endswith('.pyx'): # widget = PMCythonEditor(parent=self) elif path.endswith('.md'): - widget = PMMarkdownEditor(parent=self) + widget = PMMarkdownEditorPM(parent=self) else: QMessageBox.warning(self, self.tr('Warning'), self.tr('Editor does not support file:\n%s') % path) @@ -307,7 +307,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): widget.set_lib(self.extension_lib) widget.load_file(path) widget.windowTitleChanged.connect(self.slot_set_tab_text) - if isinstance(widget, PMPythonEditor): + if isinstance(widget, PMPythonEditorPM): try: widget.signal_focused_in.connect(self.slot_focused_in) widget.signal_new_requested.connect(lambda path, mode: self.slot_new_script(path)) @@ -481,7 +481,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): if not self._thread_check: return widget = self.currentWidget() - if not isinstance(widget, PMPythonEditor): + if not isinstance(widget, PMPythonEditorPM): # 目前暂时支持python代码检测 return code = self.get_current_text() # .strip() is not needed because there should be a empty line at the end of file @@ -510,7 +510,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): :return: """ - if isinstance(self.currentWidget(), PMPythonEditor): + if isinstance(self.currentWidget(), PMPythonEditorPM): if not code: code = self.get_current_text(True) if not code: @@ -520,7 +520,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): if hint == '': hint = self.tr( 'Run: %s') % self.get_current_filename() - elif isinstance(self.currentWidget(), PMMarkdownEditor): + elif isinstance(self.currentWidget(), PMMarkdownEditorPM): code = self.currentWidget().get_code() code = code.strip() if hint == '': @@ -572,11 +572,11 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): 在终端中运行 :return: """ - editor: 'PMPythonEditor' = self.currentWidget() + editor: 'PMPythonEditorPM' = self.currentWidget() editor.slot_run_in_terminal() def slot_run_isolated(self): - editor: 'PMPythonEditor' = self.currentWidget() + editor: 'PMPythonEditorPM' = self.currentWidget() path = editor.path() self.extension_lib.get_interface('applications_toolbar').create_python_file_process(path, self._current_executable) @@ -679,7 +679,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): Returns: """ - if isinstance(self.get_current_editor(), PMPythonEditor): + if isinstance(self.get_current_editor(), PMPythonEditorPM): path = self.get_current_editor().path() self.extension_lib.get_interface('applications_toolbar').create_instant_boot_python_file_process(path) else: 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 688bdc8c..841a2f4e 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -23,7 +23,7 @@ from ...utils.highlighter.python_highlighter import PythonHighlighter if TYPE_CHECKING: from ...utils.highlighter.base_highlighter import BaseHighlighter - from ..editors.python_editor import PMPythonEditor + from ..editors.python_editor import PMPythonEditorPM from ...utils.auto_complete_thread.base_auto_complete import BaseAutoCompleteThread logger = logging.getLogger(__name__) @@ -60,7 +60,7 @@ class PMBaseCodeEdit(QPlainTextEdit): textCursor: Callable[[], QTextCursor] # 其他类型提示 - doc_tab_widget: 'PMPythonEditor' + doc_tab_widget: 'PMPythonEditorPM' highlighter: 'PythonHighlighter' # 定义一系列的更新事件,不过只定义了这一个,提供了接口,其他的可以照例添加 diff --git a/tests/test_code_editor/test_gui/test_python_editor.py b/tests/test_code_editor/test_gui/test_python_editor.py index 5d10fcff..abe97bfb 100644 --- a/tests/test_code_editor/test_gui/test_python_editor.py +++ b/tests/test_code_editor/test_gui/test_python_editor.py @@ -1,10 +1,10 @@ from PySide2.QtCore import Qt -from packages.code_editor.widgets.editors.python_editor import PMPythonEditor +from packages.code_editor.widgets.editors.python_editor import PMPythonEditorPM def test_format_code(qtbot): - window = PMPythonEditor() + window = PMPythonEditorPM() qtbot.addWidget(window) window.show() qtbot.waitForWindowShown(window) @@ -14,7 +14,7 @@ def test_format_code(qtbot): def test_auto_completion(qtbot): - window = PMPythonEditor() + window = PMPythonEditorPM() qtbot.addWidget(window) window.show() qtbot.waitForWindowShown(window) -- Gitee From 6776af4d1ae41d033eb8e17ca8106c8fc3ee2747 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sun, 8 Aug 2021 11:52:15 +0800 Subject: [PATCH 103/108] =?UTF-8?q?code=5Feditor:=20=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E7=BF=BB=E8=AF=91=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/assets/languages/en.ts | 366 ++++++++++++++++++ .../code_editor/assets/languages/zh_CN.ts | 366 ++++++++++++++++++ .../code_editor/assets/languages/zh_TW.ts | 366 ++++++++++++++++++ .../assets/translations/qt_zh_CN.ts | 6 +- .../code_editor/scripts/translation/README.md | 15 + .../scripts/translation/code_editor.pro | 51 +++ .../scripts/translation/create_ts_files.bat | 2 + .../translation/get_all_python_files.py | 22 ++ .../widgets/dialogs/find_dialog.py | 4 +- .../widgets/editors/base_editor.py | 8 +- 10 files changed, 1197 insertions(+), 9 deletions(-) create mode 100644 packages/code_editor/assets/languages/en.ts create mode 100644 packages/code_editor/assets/languages/zh_CN.ts create mode 100644 packages/code_editor/assets/languages/zh_TW.ts create mode 100644 packages/code_editor/scripts/translation/README.md create mode 100644 packages/code_editor/scripts/translation/code_editor.pro create mode 100644 packages/code_editor/scripts/translation/create_ts_files.bat create mode 100644 packages/code_editor/scripts/translation/get_all_python_files.py diff --git a/packages/code_editor/assets/languages/en.ts b/packages/code_editor/assets/languages/en.ts new file mode 100644 index 00000000..f1b1f0d4 --- /dev/null +++ b/packages/code_editor/assets/languages/en.ts @@ -0,0 +1,366 @@ + + + + FindInPathWidget + + + Case + + + + + Whole Word + + + + + Find + + + + + Find In Path + + + + + line + + + + + PMBaseEditor + + + Format Code + + + + + Run Code + + + + + Run Selected Code + + + + + save.svg + + + + + Find + + + + + Replace + + + + + Find In Path + + + + + AutoComp + + + + + Goto Line + + + + + Add Breakpoint + + + + + Remove Breakpoint + + + + + View BreakPoints + + + + + Save + + + + + Save file "{0}"? + + + + + Save file + + + + + 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 + + + + + PMDebugConsoleTabWidget + + + Debugger + + + + + Terminate + + + + + Process is running, Would you like to terminate this process? + + + + + PMEditorToolbar + + + New Script + + + + + Open Script + + + + + Save + + + + + Find + + + + + Toggle Comment + + + + + Goto Line + + + + + Indent + + + + + Dedent + + + + + IPython + + + + + Separately + + + + + Terminal + + + + + Instant Boot + + + + + Run script with common module preloaded to shorten interpterter startup-time. + + + + + Editor + + + + + PMFindDialog + + + Text to Find + + + + + Text to Replace + + + + + Wrap + + + + + Regex + + + + + Case Sensitive + + + + + Whole Word + + + + + Up + + + + + Down + + + + + Replace + + + + + Replace All + + + + + PMGotoLineDialog + + + Input Value Error + + + + + Cannot convert '%s' to integer. + + + + + Line Number {line} out of range! + + + + + PMPythonEditorPM + + + Function Help + + + + + Help In Console + + + + + Go to Definition + + + + + Help + + + + + Cannot get name. +Maybe There is: +1、Syntax error in your code. +2、No word under text cursor. + + + + + Error + + + + diff --git a/packages/code_editor/assets/languages/zh_CN.ts b/packages/code_editor/assets/languages/zh_CN.ts new file mode 100644 index 00000000..0b865e7d --- /dev/null +++ b/packages/code_editor/assets/languages/zh_CN.ts @@ -0,0 +1,366 @@ + + + + FindInPathWidget + + + Case + + + + + Whole Word + + + + + Find + + + + + Find In Path + + + + + line + + + + + PMBaseEditor + + + Format Code + 代码格式化 + + + + Run Code + 运行代码 + + + + Run Selected Code + 运行选中的代码 + + + + save.svg + + + + + Find + + + + + Replace + + + + + Find In Path + + + + + AutoComp + + + + + Goto Line + + + + + Add Breakpoint + + + + + Remove Breakpoint + + + + + View BreakPoints + + + + + Save + + + + + Save file "{0}"? + + + + + Save file + + + + + 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 + + + + + PMDebugConsoleTabWidget + + + Debugger + + + + + Terminate + + + + + Process is running, Would you like to terminate this process? + + + + + PMEditorToolbar + + + New Script + + + + + Open Script + + + + + Save + + + + + Find + + + + + Toggle Comment + + + + + Goto Line + + + + + Indent + + + + + Dedent + + + + + IPython + + + + + Separately + + + + + Terminal + + + + + Instant Boot + + + + + Run script with common module preloaded to shorten interpterter startup-time. + + + + + Editor + + + + + PMFindDialog + + + Text to Find + + + + + Text to Replace + + + + + Wrap + + + + + Regex + + + + + Case Sensitive + + + + + Whole Word + + + + + Up + + + + + Down + + + + + Replace + + + + + Replace All + + + + + PMGotoLineDialog + + + Input Value Error + + + + + Cannot convert '%s' to integer. + + + + + Line Number {line} out of range! + + + + + PMPythonEditorPM + + + Function Help + + + + + Help In Console + + + + + Go to Definition + + + + + Help + + + + + Error + + + + + Cannot get name. +Maybe There is: +1、Syntax error in your code. +2、No word under text cursor. + + + + diff --git a/packages/code_editor/assets/languages/zh_TW.ts b/packages/code_editor/assets/languages/zh_TW.ts new file mode 100644 index 00000000..f1b1f0d4 --- /dev/null +++ b/packages/code_editor/assets/languages/zh_TW.ts @@ -0,0 +1,366 @@ + + + + FindInPathWidget + + + Case + + + + + Whole Word + + + + + Find + + + + + Find In Path + + + + + line + + + + + PMBaseEditor + + + Format Code + + + + + Run Code + + + + + Run Selected Code + + + + + save.svg + + + + + Find + + + + + Replace + + + + + Find In Path + + + + + AutoComp + + + + + Goto Line + + + + + Add Breakpoint + + + + + Remove Breakpoint + + + + + View BreakPoints + + + + + Save + + + + + Save file "{0}"? + + + + + Save file + + + + + 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 + + + + + PMDebugConsoleTabWidget + + + Debugger + + + + + Terminate + + + + + Process is running, Would you like to terminate this process? + + + + + PMEditorToolbar + + + New Script + + + + + Open Script + + + + + Save + + + + + Find + + + + + Toggle Comment + + + + + Goto Line + + + + + Indent + + + + + Dedent + + + + + IPython + + + + + Separately + + + + + Terminal + + + + + Instant Boot + + + + + Run script with common module preloaded to shorten interpterter startup-time. + + + + + Editor + + + + + PMFindDialog + + + Text to Find + + + + + Text to Replace + + + + + Wrap + + + + + Regex + + + + + Case Sensitive + + + + + Whole Word + + + + + Up + + + + + Down + + + + + Replace + + + + + Replace All + + + + + PMGotoLineDialog + + + Input Value Error + + + + + Cannot convert '%s' to integer. + + + + + Line Number {line} out of range! + + + + + PMPythonEditorPM + + + Function Help + + + + + Help In Console + + + + + Go to Definition + + + + + Help + + + + + Cannot get name. +Maybe There is: +1、Syntax error in your code. +2、No word under text cursor. + + + + + Error + + + + diff --git a/packages/code_editor/assets/translations/qt_zh_CN.ts b/packages/code_editor/assets/translations/qt_zh_CN.ts index dd08a401..42d01c2d 100644 --- a/packages/code_editor/assets/translations/qt_zh_CN.ts +++ b/packages/code_editor/assets/translations/qt_zh_CN.ts @@ -2,7 +2,7 @@ - DialogGoto + PMGotoLineDialog Go to Line/Column @@ -15,7 +15,7 @@ - FindDialog + PMFindDialog Text to Find @@ -124,7 +124,7 @@ - PMAbstractEditor + PMBaseEditor Save diff --git a/packages/code_editor/scripts/translation/README.md b/packages/code_editor/scripts/translation/README.md new file mode 100644 index 00000000..f54f5f1e --- /dev/null +++ b/packages/code_editor/scripts/translation/README.md @@ -0,0 +1,15 @@ +# 代码编辑器的国际化翻译 + +出于开发时的简洁性,使用英文进行开发,而后需要译回中文。 + +主要包括以下脚本: + +`get_all_python_files.py`,可以获取所有的code_editor中的python文件, +将其输出结果复制到`code_editor.pro`里面,就可以选择需要的python文件了。 + +目前没有`ui`文件,因此其相关内容暂时没做。 + +执行create_ts_files.bat,可以创建(或更新?)ts文件。 + +使用linguist编辑ts文件,然后生成qm文件。 +这两步我是借助了PyCharm的External Tools功能实现的。 \ No newline at end of file diff --git a/packages/code_editor/scripts/translation/code_editor.pro b/packages/code_editor/scripts/translation/code_editor.pro new file mode 100644 index 00000000..ee4728f4 --- /dev/null +++ b/packages/code_editor/scripts/translation/code_editor.pro @@ -0,0 +1,51 @@ +SOURCES = ..\..\main.py\ + ..\..\settings.py\ + ..\..\__init__.py\ + ..\..\utils\base_object.py\ + ..\..\utils\font_config.py\ + ..\..\utils\operation.py\ + ..\..\utils\utils.py\ + ..\..\utils\__init__.py\ + ..\..\utils\auto_complete_thread\base_auto_complete.py\ + ..\..\utils\auto_complete_thread\python_auto_complete.py\ + ..\..\utils\auto_complete_thread\__init__.py\ + ..\..\utils\code_checker\base_code_checker.py\ + ..\..\utils\code_checker\__init__.py\ + ..\..\utils\grammar_analyzer\get_indent.py\ + ..\..\utils\grammar_analyzer\grammar_analyzer.py\ + ..\..\utils\grammar_analyzer\__init__.py\ + ..\..\utils\highlighter\base_highlighter.py\ + ..\..\utils\highlighter\python_highlighter.py\ + ..\..\utils\highlighter\__init__.py\ + ..\..\widgets\debugger.py\ + ..\..\widgets\tab_widget.py\ + ..\..\widgets\toolbar.py\ + ..\..\widgets\__init__.py\ + ..\..\widgets\auto_complete_dropdown\base_auto_complete_dropdown.py\ + ..\..\widgets\auto_complete_dropdown\__init__.py\ + ..\..\widgets\dialogs\find_dialog.py\ + ..\..\widgets\dialogs\goto_line_dialog.py\ + ..\..\widgets\dialogs\__init__.py\ + ..\..\widgets\editors\base_editor.py\ + ..\..\widgets\editors\markdown_editor.py\ + ..\..\widgets\editors\python_editor.py\ + ..\..\widgets\editors\__init__.py\ + ..\..\widgets\text_edit\base_text_edit.py\ + ..\..\widgets\text_edit\line_number_area.py\ + ..\..\widgets\text_edit\python_text_edit.py\ + ..\..\widgets\text_edit\__init__.py\ + ..\..\widgets\ui\findinpath.py\ + ..\..\widgets\ui\formeditor.py\ + ..\..\widgets\ui\gotoline.py\ + ..\..\widgets\ui\ui_formeditor.py\ + ..\..\widgets\ui\ui_gotoline.py\ + ..\..\widgets\ui\__init__.py +TRANSLATIONS = ..\..\assets\languages\en.ts \ + ..\..\assets\languages\zh_CN.ts \ + ..\..\assets\languages\zh_TW.ts + +CODECFORTR = UTF-8 +CODECFORSRC = UTF-8 + +# pylupdate5.exe pyminer.pro +# linguist.exe languages\en\en.ts languages\zh_CN\zh_CN.ts languages\zh_TW\zh_TW.ts \ No newline at end of file diff --git a/packages/code_editor/scripts/translation/create_ts_files.bat b/packages/code_editor/scripts/translation/create_ts_files.bat new file mode 100644 index 00000000..33d45873 --- /dev/null +++ b/packages/code_editor/scripts/translation/create_ts_files.bat @@ -0,0 +1,2 @@ +call "../../../../venv/Scripts/activate.bat" +call "pyside2-lupdate" code_editor.pro \ No newline at end of file diff --git a/packages/code_editor/scripts/translation/get_all_python_files.py b/packages/code_editor/scripts/translation/get_all_python_files.py new file mode 100644 index 00000000..f292ef82 --- /dev/null +++ b/packages/code_editor/scripts/translation/get_all_python_files.py @@ -0,0 +1,22 @@ +import os +from pathlib import Path + + +def main(): + base_path = Path(__file__).parent.parent.parent.absolute() + python_files = [] + for root, sub_dirs, files in os.walk(base_path): + root = Path(root) + if any(p in root.parts for p in ('__pycache__', 'assets', 'scripts')): + continue + for file in files: + file = root / file + if file.suffix != '.py': + continue + file = os.path.join('..', '..', file.relative_to(base_path)) + python_files.append(file) + print('\\\n '.join(python_files)) + + +if __name__ == '__main__': + main() diff --git a/packages/code_editor/widgets/dialogs/find_dialog.py b/packages/code_editor/widgets/dialogs/find_dialog.py index 38b96105..d688be98 100644 --- a/packages/code_editor/widgets/dialogs/find_dialog.py +++ b/packages/code_editor/widgets/dialogs/find_dialog.py @@ -9,11 +9,11 @@ if TYPE_CHECKING: from ..editors.base_editor import PMBaseEditor -class FindDialog(QDialog): +class PMFindDialog(QDialog): tr: Callable[[str], str] def __init__(self, parent: 'PMBaseEditor' = None): - super(FindDialog, self).__init__(parent) + super(PMFindDialog, self).__init__(parent) self.text_editor = parent self.text_edit: 'PMBaseCodeEdit' = parent.text_edit views = [ diff --git a/packages/code_editor/widgets/editors/base_editor.py b/packages/code_editor/widgets/editors/base_editor.py index aab5153d..926f8a04 100644 --- a/packages/code_editor/widgets/editors/base_editor.py +++ b/packages/code_editor/widgets/editors/base_editor.py @@ -37,7 +37,7 @@ from PySide2.QtWidgets import QWidget, QMessageBox, QLabel, QVBoxLayout, QFileDi QMenu from features.extensions.extensionlib.extension_lib import ExtensionLib -from ..dialogs.find_dialog import FindDialog +from ..dialogs.find_dialog import PMFindDialog from ..dialogs.goto_line_dialog import PMGotoLineDialog from ..text_edit.base_text_edit import PMBaseCodeEdit from ...utils.base_object import CodeEditorBaseObject @@ -65,9 +65,9 @@ class PMBaseEditor(CodeEditorBaseObject, QWidget): signal_request_find_in_path: SignalInstance = Signal(str) # 子控件的类型提示 - find_dialog: 'FindDialog' + find_dialog: 'PMFindDialog' goto_line_dialog: 'PMGotoLineDialog' - find_dialog: 'FindDialog' + find_dialog: 'PMFindDialog' extension_lib: 'ExtensionLib' # 为PySide2内置函数添加代码提示 @@ -97,7 +97,7 @@ class PMBaseEditor(CodeEditorBaseObject, QWidget): self.layout().addWidget(self.status_bar) # 设置各个对话框 - self.find_dialog = FindDialog(parent=self) + self.find_dialog = PMFindDialog(parent=self) self.goto_line_dialog = PMGotoLineDialog(parent=self) self.last_save_time = 0 -- Gitee From 4d6ed3adabf0ad8aeafbda0638ccf85b5fb02e7e Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sun, 8 Aug 2021 11:52:37 +0800 Subject: [PATCH 104/108] =?UTF-8?q?code=5Feditor:=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E7=B1=BB=E5=90=8D=E7=9A=84=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/editors/python_editor.py | 8 ++++---- packages/code_editor/widgets/tab_widget.py | 18 +++++++++--------- .../widgets/text_edit/base_text_edit.py | 4 ++-- .../test_gui/test_python_editor.py | 6 +++--- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/code_editor/widgets/editors/python_editor.py b/packages/code_editor/widgets/editors/python_editor.py index eeceb1b7..87625d8b 100644 --- a/packages/code_editor/widgets/editors/python_editor.py +++ b/packages/code_editor/widgets/editors/python_editor.py @@ -51,7 +51,7 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) -class PMPythonEditorPM(PMBaseEditor): +class PMPythonEditor(PMBaseEditor): """ 自定义编辑器控件 """ @@ -67,7 +67,7 @@ class PMPythonEditorPM(PMBaseEditor): tr: Callable[[str], str] def __init__(self, parent=None): - super(PMPythonEditorPM, self).__init__(parent) # , comment_string='# ') + super(PMPythonEditor, self).__init__(parent) # , comment_string='# ') self.browser_id = None self._parent = parent self.last_hint = '' @@ -90,7 +90,7 @@ class PMPythonEditorPM(PMBaseEditor): def _init_actions(self) -> None: """初始化事件""" - super(PMPythonEditorPM, self)._init_actions() + super(PMPythonEditor, self)._init_actions() self._action_help = QAction(self.tr('Function Help'), self.text_edit) self._shortcut_help = QShortcut(QKeySequence('F1'), self.text_edit, context=Qt.WidgetShortcut) self._action_help.setShortcut(QKeySequence('F1')) @@ -108,7 +108,7 @@ class PMPythonEditorPM(PMBaseEditor): # noinspection PyUnresolvedReferences def _init_signals(self) -> None: """初始化信号绑定。""" - super(PMPythonEditorPM, self)._init_signals() + super(PMPythonEditor, self)._init_signals() self._shortcut_help.activated.connect(self.get_help) self._action_help.triggered.connect(self.get_help) diff --git a/packages/code_editor/widgets/tab_widget.py b/packages/code_editor/widgets/tab_widget.py index 41bbeaa9..90fc7fe3 100644 --- a/packages/code_editor/widgets/tab_widget.py +++ b/packages/code_editor/widgets/tab_widget.py @@ -11,7 +11,7 @@ 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 PMPythonEditorPM +from .editors.python_editor import PMPythonEditor from .ui.findinpath import FindInPathWidget from ..utils.code_checker.base_code_checker import CodeCheckWorker from ..utils.highlighter.python_highlighter import PythonHighlighter @@ -189,7 +189,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): return self._keywords = list(keywords) - def get_current_editor(self) -> Optional['PMPythonEditorPM']: + def get_current_editor(self) -> Optional['PMPythonEditor']: """ get current editor Returns: @@ -293,7 +293,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): widget: 'PMBaseEditor' = None if path.endswith('.py') or path == os.path.join(QDir.tempPath(), 'Untitled-%d' % self._index).replace(os.sep, '/'): - widget = PMPythonEditorPM(parent=self) + widget = PMPythonEditor(parent=self) # elif path.endswith(('.c', '.cpp', '.h')): # widget = PMCPPEditor(parent=self) # elif path.endswith('.pyx'): @@ -307,7 +307,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): widget.set_lib(self.extension_lib) widget.load_file(path) widget.windowTitleChanged.connect(self.slot_set_tab_text) - if isinstance(widget, PMPythonEditorPM): + if isinstance(widget, PMPythonEditor): try: widget.signal_focused_in.connect(self.slot_focused_in) widget.signal_new_requested.connect(lambda path, mode: self.slot_new_script(path)) @@ -481,7 +481,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): if not self._thread_check: return widget = self.currentWidget() - if not isinstance(widget, PMPythonEditorPM): + if not isinstance(widget, PMPythonEditor): # 目前暂时支持python代码检测 return code = self.get_current_text() # .strip() is not needed because there should be a empty line at the end of file @@ -510,7 +510,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): :return: """ - if isinstance(self.currentWidget(), PMPythonEditorPM): + if isinstance(self.currentWidget(), PMPythonEditor): if not code: code = self.get_current_text(True) if not code: @@ -572,11 +572,11 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): 在终端中运行 :return: """ - editor: 'PMPythonEditorPM' = self.currentWidget() + editor: 'PMPythonEditor' = self.currentWidget() editor.slot_run_in_terminal() def slot_run_isolated(self): - editor: 'PMPythonEditorPM' = self.currentWidget() + editor: 'PMPythonEditor' = self.currentWidget() path = editor.path() self.extension_lib.get_interface('applications_toolbar').create_python_file_process(path, self._current_executable) @@ -679,7 +679,7 @@ class PMCodeEditTabWidget(QTabWidget, PMDockObject): Returns: """ - if isinstance(self.get_current_editor(), PMPythonEditorPM): + 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: 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 841a2f4e..688bdc8c 100644 --- a/packages/code_editor/widgets/text_edit/base_text_edit.py +++ b/packages/code_editor/widgets/text_edit/base_text_edit.py @@ -23,7 +23,7 @@ from ...utils.highlighter.python_highlighter import PythonHighlighter if TYPE_CHECKING: from ...utils.highlighter.base_highlighter import BaseHighlighter - from ..editors.python_editor import PMPythonEditorPM + from ..editors.python_editor import PMPythonEditor from ...utils.auto_complete_thread.base_auto_complete import BaseAutoCompleteThread logger = logging.getLogger(__name__) @@ -60,7 +60,7 @@ class PMBaseCodeEdit(QPlainTextEdit): textCursor: Callable[[], QTextCursor] # 其他类型提示 - doc_tab_widget: 'PMPythonEditorPM' + doc_tab_widget: 'PMPythonEditor' highlighter: 'PythonHighlighter' # 定义一系列的更新事件,不过只定义了这一个,提供了接口,其他的可以照例添加 diff --git a/tests/test_code_editor/test_gui/test_python_editor.py b/tests/test_code_editor/test_gui/test_python_editor.py index abe97bfb..5d10fcff 100644 --- a/tests/test_code_editor/test_gui/test_python_editor.py +++ b/tests/test_code_editor/test_gui/test_python_editor.py @@ -1,10 +1,10 @@ from PySide2.QtCore import Qt -from packages.code_editor.widgets.editors.python_editor import PMPythonEditorPM +from packages.code_editor.widgets.editors.python_editor import PMPythonEditor def test_format_code(qtbot): - window = PMPythonEditorPM() + window = PMPythonEditor() qtbot.addWidget(window) window.show() qtbot.waitForWindowShown(window) @@ -14,7 +14,7 @@ def test_format_code(qtbot): def test_auto_completion(qtbot): - window = PMPythonEditorPM() + window = PMPythonEditor() qtbot.addWidget(window) window.show() qtbot.waitForWindowShown(window) -- Gitee From 4ceeda7885980c59ffbc6b51cbab0b78918984e6 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sun, 8 Aug 2021 11:53:28 +0800 Subject: [PATCH 105/108] =?UTF-8?q?code=5Feditor:=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/assets/languages/en.ts | 5 ++++- packages/code_editor/assets/languages/zh_CN.ts | 14 +++++++------- packages/code_editor/assets/languages/zh_TW.ts | 5 ++++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/code_editor/assets/languages/en.ts b/packages/code_editor/assets/languages/en.ts index f1b1f0d4..f189c831 100644 --- a/packages/code_editor/assets/languages/en.ts +++ b/packages/code_editor/assets/languages/en.ts @@ -328,7 +328,7 @@ - PMPythonEditorPM + PMPythonEditor Function Help @@ -363,4 +363,7 @@ Maybe There is: + + PMPythonEditorPM + diff --git a/packages/code_editor/assets/languages/zh_CN.ts b/packages/code_editor/assets/languages/zh_CN.ts index 0b865e7d..c11ed114 100644 --- a/packages/code_editor/assets/languages/zh_CN.ts +++ b/packages/code_editor/assets/languages/zh_CN.ts @@ -327,8 +327,8 @@ - - PMPythonEditorPM + + PMPythonEditor Function Help @@ -349,11 +349,6 @@ Help - - - Error - - Cannot get name. @@ -362,5 +357,10 @@ Maybe There is: 2、No word under text cursor. + + + Error + + diff --git a/packages/code_editor/assets/languages/zh_TW.ts b/packages/code_editor/assets/languages/zh_TW.ts index f1b1f0d4..f189c831 100644 --- a/packages/code_editor/assets/languages/zh_TW.ts +++ b/packages/code_editor/assets/languages/zh_TW.ts @@ -328,7 +328,7 @@ - PMPythonEditorPM + PMPythonEditor Function Help @@ -363,4 +363,7 @@ Maybe There is: + + PMPythonEditorPM + -- Gitee From c38f7ff4ca54edd650a64bf222624dc8c222e300 Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sun, 8 Aug 2021 12:32:05 +0800 Subject: [PATCH 106/108] =?UTF-8?q?code=5Feditor:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86=E6=9B=B4=E6=96=B0=E6=89=80=E6=9C=89=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E7=9A=84=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/assets/languages/en.ts | 3 -- .../code_editor/assets/languages/zh_TW.ts | 3 -- .../scripts/translation/create_ts_files.bat | 2 -- .../scripts/translation/create_ts_files.py | 15 ++++++++++ .../scripts/translation/release.py | 30 +++++++++++++++++++ 5 files changed, 45 insertions(+), 8 deletions(-) delete mode 100644 packages/code_editor/scripts/translation/create_ts_files.bat create mode 100644 packages/code_editor/scripts/translation/create_ts_files.py create mode 100644 packages/code_editor/scripts/translation/release.py diff --git a/packages/code_editor/assets/languages/en.ts b/packages/code_editor/assets/languages/en.ts index f189c831..0a874eae 100644 --- a/packages/code_editor/assets/languages/en.ts +++ b/packages/code_editor/assets/languages/en.ts @@ -363,7 +363,4 @@ Maybe There is: - - PMPythonEditorPM - diff --git a/packages/code_editor/assets/languages/zh_TW.ts b/packages/code_editor/assets/languages/zh_TW.ts index f189c831..0a874eae 100644 --- a/packages/code_editor/assets/languages/zh_TW.ts +++ b/packages/code_editor/assets/languages/zh_TW.ts @@ -363,7 +363,4 @@ Maybe There is: - - PMPythonEditorPM - diff --git a/packages/code_editor/scripts/translation/create_ts_files.bat b/packages/code_editor/scripts/translation/create_ts_files.bat deleted file mode 100644 index 33d45873..00000000 --- a/packages/code_editor/scripts/translation/create_ts_files.bat +++ /dev/null @@ -1,2 +0,0 @@ -call "../../../../venv/Scripts/activate.bat" -call "pyside2-lupdate" code_editor.pro \ No newline at end of file diff --git a/packages/code_editor/scripts/translation/create_ts_files.py b/packages/code_editor/scripts/translation/create_ts_files.py new file mode 100644 index 00000000..2a76e6a4 --- /dev/null +++ b/packages/code_editor/scripts/translation/create_ts_files.py @@ -0,0 +1,15 @@ +import os +from pathlib import Path + +import PySide2 + + +def main(): + exe = Path(PySide2.__file__).parent / 'pyside2-lupdate.exe' + pro = Path(__file__).absolute().parent / 'code_editor.pro' + content = os.popen(f'"{exe}" "{pro}"').read() + print(content) + + +if __name__ == '__main__': + main() diff --git a/packages/code_editor/scripts/translation/release.py b/packages/code_editor/scripts/translation/release.py new file mode 100644 index 00000000..d92c5f14 --- /dev/null +++ b/packages/code_editor/scripts/translation/release.py @@ -0,0 +1,30 @@ +import io +import os +import subprocess +from io import BytesIO +from pathlib import Path + +import PySide2 +import chardet + + +def main(): + base_path = Path(__file__).absolute().parent.parent.parent + print(base_path) + exe = Path(PySide2.__file__).parent / 'lrelease.exe' + ts_dir = base_path / 'assets' / 'languages' + for file in ts_dir.glob('*.ts'): + output = file.parent / f'{file.stem}.qm' + result = subprocess.run(args=[exe, f'{file}', '-qm', output], capture_output=True) + if stdout := result.stdout: + print('Out: ') + stdout = stdout.decode(chardet.detect(result.stdout)['encoding']) + print(stdout) + if stderr := result.stderr: + print('Error: ') + stderr = stderr.decode(chardet.detect(result.stderr)['encoding']) + print(stderr) + + +if __name__ == '__main__': + main() -- Gitee From 2a2e43511de25d7c10a5487ce6a0f7682761b25d Mon Sep 17 00:00:00 2001 From: wolfpan Date: Sun, 8 Aug 2021 12:32:12 +0800 Subject: [PATCH 107/108] =?UTF-8?q?code=5Feditor:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86=E6=9B=B4=E6=96=B0=E6=89=80=E6=9C=89=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E7=9A=84=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/assets/languages/en.qm | 1 + packages/code_editor/assets/languages/zh_CN.qm | Bin 0 -> 227 bytes packages/code_editor/assets/languages/zh_TW.qm | 1 + 3 files changed, 2 insertions(+) create mode 100644 packages/code_editor/assets/languages/en.qm create mode 100644 packages/code_editor/assets/languages/zh_CN.qm create mode 100644 packages/code_editor/assets/languages/zh_TW.qm diff --git a/packages/code_editor/assets/languages/en.qm b/packages/code_editor/assets/languages/en.qm new file mode 100644 index 00000000..be651eed --- /dev/null +++ b/packages/code_editor/assets/languages/en.qm @@ -0,0 +1 @@ +fC6yh>=Lzt2N-R!wP01|D xFJc7g;^@E7;RDhM*TWH1nukXNA4tOlPCwnU78GrQAZ@{^IjPAdsVO-00|3_KIQswq literal 0 HcmV?d00001 diff --git a/packages/code_editor/assets/languages/zh_TW.qm b/packages/code_editor/assets/languages/zh_TW.qm new file mode 100644 index 00000000..be651eed --- /dev/null +++ b/packages/code_editor/assets/languages/zh_TW.qm @@ -0,0 +1 @@ + Date: Sun, 8 Aug 2021 12:32:49 +0800 Subject: [PATCH 108/108] =?UTF-8?q?code=5Feditor:=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86=E6=9B=B4=E6=96=B0=E6=89=80=E6=9C=89=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E7=9A=84=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/code_editor/scripts/translation/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/code_editor/scripts/translation/README.md b/packages/code_editor/scripts/translation/README.md index f54f5f1e..e05b9815 100644 --- a/packages/code_editor/scripts/translation/README.md +++ b/packages/code_editor/scripts/translation/README.md @@ -11,5 +11,6 @@ 执行create_ts_files.bat,可以创建(或更新?)ts文件。 -使用linguist编辑ts文件,然后生成qm文件。 -这两步我是借助了PyCharm的External Tools功能实现的。 \ No newline at end of file +使用linguist编辑ts文件,我是借助了PyCharm的External Tools功能实现的。 + +最后编译ts文件至qm文件,使用`release.py`。 \ No newline at end of file -- Gitee