je-editor 0.0.104__py3-none-any.whl → 0.0.228__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- je_editor/__init__.py +26 -21
- je_editor/__main__.py +1 -1
- je_editor/code_scan/ruff_thread.py +58 -0
- je_editor/code_scan/watchdog_implement.py +56 -0
- je_editor/code_scan/watchdog_thread.py +78 -0
- je_editor/git_client/commit_graph.py +77 -0
- je_editor/git_client/git_action.py +175 -0
- je_editor/git_client/git_cli.py +66 -0
- je_editor/pyside_ui/browser/browser_download_window.py +75 -0
- je_editor/pyside_ui/browser/browser_serach_lineedit.py +51 -0
- je_editor/pyside_ui/browser/browser_view.py +87 -0
- je_editor/pyside_ui/browser/browser_widget.py +103 -0
- je_editor/pyside_ui/browser/main_browser_widget.py +85 -0
- je_editor/pyside_ui/code/auto_save/auto_save_manager.py +60 -0
- je_editor/pyside_ui/code/auto_save/auto_save_thread.py +59 -0
- je_editor/pyside_ui/code/code_format/pep8_format.py +130 -0
- je_editor/pyside_ui/code/code_process/code_exec.py +267 -0
- je_editor/pyside_ui/code/plaintext_code_edit/code_edit_plaintext.py +412 -0
- je_editor/pyside_ui/code/running_process_manager.py +48 -0
- je_editor/pyside_ui/code/shell_process/shell_exec.py +236 -0
- je_editor/pyside_ui/code/syntax/python_syntax.py +99 -0
- je_editor/pyside_ui/code/syntax/syntax_setting.py +95 -0
- je_editor/pyside_ui/code/textedit_code_result/code_record.py +75 -0
- je_editor/pyside_ui/code/variable_inspector/inspector_gui.py +172 -0
- je_editor/pyside_ui/dialog/ai_dialog/set_ai_dialog.py +71 -0
- je_editor/pyside_ui/dialog/file_dialog/create_file_dialog.py +68 -0
- je_editor/pyside_ui/dialog/file_dialog/open_file_dialog.py +111 -0
- je_editor/pyside_ui/dialog/file_dialog/save_file_dialog.py +67 -0
- je_editor/pyside_ui/dialog/search_ui/search_error_box.py +49 -0
- je_editor/pyside_ui/dialog/search_ui/search_text_box.py +49 -0
- je_editor/pyside_ui/git_ui/code_diff_compare/code_diff_viewer_widget.py +90 -0
- je_editor/pyside_ui/git_ui/code_diff_compare/line_number_code_viewer.py +141 -0
- je_editor/pyside_ui/git_ui/code_diff_compare/multi_file_diff_viewer.py +88 -0
- je_editor/pyside_ui/git_ui/code_diff_compare/side_by_side_diff_widget.py +284 -0
- je_editor/pyside_ui/git_ui/git_client/commit_table.py +65 -0
- je_editor/pyside_ui/git_ui/git_client/git_branch_tree_widget.py +156 -0
- je_editor/pyside_ui/git_ui/git_client/git_client_gui.py +799 -0
- je_editor/pyside_ui/git_ui/git_client/graph_view.py +218 -0
- je_editor/pyside_ui/main_ui/ai_widget/ai_config.py +34 -0
- je_editor/pyside_ui/main_ui/ai_widget/ask_thread.py +36 -0
- je_editor/pyside_ui/main_ui/ai_widget/chat_ui.py +147 -0
- je_editor/pyside_ui/main_ui/ai_widget/langchain_interface.py +84 -0
- je_editor/pyside_ui/main_ui/console_widget/console_gui.py +162 -0
- je_editor/pyside_ui/main_ui/console_widget/qprocess_adapter.py +84 -0
- je_editor/pyside_ui/main_ui/dock/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/dock/destroy_dock.py +50 -0
- je_editor/pyside_ui/main_ui/editor/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/editor/editor_widget.py +301 -0
- je_editor/pyside_ui/main_ui/editor/editor_widget_dock.py +70 -0
- je_editor/pyside_ui/main_ui/editor/process_input.py +101 -0
- je_editor/pyside_ui/main_ui/ipython_widget/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/ipython_widget/rich_jupyter.py +78 -0
- je_editor/pyside_ui/main_ui/main_editor.py +369 -0
- je_editor/pyside_ui/main_ui/menu/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/menu/check_style_menu/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/menu/check_style_menu/build_check_style_menu.py +104 -0
- je_editor/pyside_ui/main_ui/menu/dock_menu/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/menu/dock_menu/build_dock_menu.py +208 -0
- je_editor/pyside_ui/main_ui/menu/file_menu/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/menu/file_menu/build_file_menu.py +186 -0
- je_editor/pyside_ui/main_ui/menu/help_menu/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/menu/help_menu/build_help_menu.py +100 -0
- je_editor/pyside_ui/main_ui/menu/language_menu/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/menu/language_menu/build_language_server.py +89 -0
- je_editor/pyside_ui/main_ui/menu/python_env_menu/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/menu/python_env_menu/build_venv_menu.py +238 -0
- je_editor/pyside_ui/main_ui/menu/run_menu/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/menu/run_menu/build_run_menu.py +160 -0
- je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_debug_menu.py +109 -0
- je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_program_menu.py +101 -0
- je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_shell_menu.py +98 -0
- je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/utils.py +41 -0
- je_editor/pyside_ui/main_ui/menu/set_menu_bar.py +63 -0
- je_editor/pyside_ui/main_ui/menu/style_menu/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/menu/style_menu/build_style_menu.py +73 -0
- je_editor/pyside_ui/main_ui/menu/tab_menu/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/menu/tab_menu/build_tab_menu.py +275 -0
- je_editor/pyside_ui/main_ui/menu/text_menu/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/menu/text_menu/build_text_menu.py +135 -0
- je_editor/pyside_ui/main_ui/save_settings/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/save_settings/setting_utils.py +33 -0
- je_editor/pyside_ui/main_ui/save_settings/user_color_setting_file.py +103 -0
- je_editor/pyside_ui/main_ui/save_settings/user_setting_file.py +58 -0
- je_editor/pyside_ui/main_ui/system_tray/__init__.py +0 -0
- je_editor/pyside_ui/main_ui/system_tray/extend_system_tray.py +90 -0
- je_editor/start_editor.py +32 -8
- je_editor/utils/encodings/python_encodings.py +100 -97
- je_editor/utils/exception/exception_tags.py +11 -11
- je_editor/utils/file/open/open_file.py +38 -22
- je_editor/utils/file/save/save_file.py +40 -16
- je_editor/utils/json/json_file.py +36 -15
- je_editor/utils/json_format/json_process.py +38 -2
- je_editor/utils/logging/__init__.py +0 -0
- je_editor/utils/logging/loggin_instance.py +57 -0
- je_editor/utils/multi_language/__init__.py +0 -0
- je_editor/utils/multi_language/english.py +221 -0
- je_editor/utils/multi_language/multi_language_wrapper.py +54 -0
- je_editor/utils/multi_language/traditional_chinese.py +214 -0
- je_editor/utils/redirect_manager/redirect_manager_class.py +67 -25
- je_editor/utils/venv_check/__init__.py +0 -0
- je_editor/utils/venv_check/check_venv.py +51 -0
- je_editor-0.0.228.dist-info/METADATA +99 -0
- je_editor-0.0.228.dist-info/RECORD +140 -0
- {je_editor-0.0.104.dist-info → je_editor-0.0.228.dist-info}/WHEEL +1 -1
- {je_editor-0.0.104.dist-info → je_editor-0.0.228.dist-info/licenses}/LICENSE +1 -1
- je_editor/pyside_ui/auto_save/auto_save_thread.py +0 -34
- je_editor/pyside_ui/code_editor/code_edit_plaintext.py +0 -143
- je_editor/pyside_ui/code_process/code_exec.py +0 -190
- je_editor/pyside_ui/code_result/code_record.py +0 -39
- je_editor/pyside_ui/colors/global_color.py +0 -4
- je_editor/pyside_ui/file_dialog/open_file_dialog.py +0 -27
- je_editor/pyside_ui/file_dialog/save_file_dialog.py +0 -24
- je_editor/pyside_ui/main_ui/editor_main_ui/main_editor.py +0 -183
- je_editor/pyside_ui/main_ui_setting/ui_setting.py +0 -36
- je_editor/pyside_ui/menu/menu_bar/check_style_menu/build_check_style_menu.py +0 -44
- je_editor/pyside_ui/menu/menu_bar/file_menu/build_file_menu.py +0 -30
- je_editor/pyside_ui/menu/menu_bar/help_menu/build_help_menu.py +0 -39
- je_editor/pyside_ui/menu/menu_bar/run_menu/build_run_menu.py +0 -102
- je_editor/pyside_ui/menu/menu_bar/set_menu_bar.py +0 -24
- je_editor/pyside_ui/menu/menu_bar/venv_menu/build_venv_menu.py +0 -74
- je_editor/pyside_ui/search_ui/search_error_box.py +0 -20
- je_editor/pyside_ui/search_ui/search_text_box.py +0 -20
- je_editor/pyside_ui/shell_process/shell_exec.py +0 -157
- je_editor/pyside_ui/syntax/python_syntax.py +0 -99
- je_editor/pyside_ui/treeview/project_treeview/set_project_treeview.py +0 -47
- je_editor/pyside_ui/user_setting/user_setting_file.py +0 -23
- je_editor-0.0.104.dist-info/METADATA +0 -84
- je_editor-0.0.104.dist-info/RECORD +0 -69
- /je_editor/{pyside_ui/auto_save → code_scan}/__init__.py +0 -0
- /je_editor/{pyside_ui/code_editor → git_client}/__init__.py +0 -0
- /je_editor/pyside_ui/{code_process → browser}/__init__.py +0 -0
- /je_editor/pyside_ui/{code_result → code}/__init__.py +0 -0
- /je_editor/pyside_ui/{colors → code/auto_save}/__init__.py +0 -0
- /je_editor/pyside_ui/{file_dialog → code/code_format}/__init__.py +0 -0
- /je_editor/pyside_ui/{main_ui/editor_main_ui → code/code_process}/__init__.py +0 -0
- /je_editor/pyside_ui/{main_ui_setting → code/plaintext_code_edit}/__init__.py +0 -0
- /je_editor/pyside_ui/{menu → code/shell_process}/__init__.py +0 -0
- /je_editor/pyside_ui/{menu/menu_bar → code/syntax}/__init__.py +0 -0
- /je_editor/pyside_ui/{menu/menu_bar/check_style_menu → code/textedit_code_result}/__init__.py +0 -0
- /je_editor/pyside_ui/{menu/menu_bar/file_menu → code/variable_inspector}/__init__.py +0 -0
- /je_editor/pyside_ui/{menu/menu_bar/help_menu → dialog}/__init__.py +0 -0
- /je_editor/pyside_ui/{menu/menu_bar/run_menu → dialog/ai_dialog}/__init__.py +0 -0
- /je_editor/pyside_ui/{menu/menu_bar/venv_menu → dialog/file_dialog}/__init__.py +0 -0
- /je_editor/pyside_ui/{search_ui → dialog/search_ui}/__init__.py +0 -0
- /je_editor/pyside_ui/{shell_process → git_ui}/__init__.py +0 -0
- /je_editor/pyside_ui/{syntax → git_ui/code_diff_compare}/__init__.py +0 -0
- /je_editor/pyside_ui/{treeview → git_ui/git_client}/__init__.py +0 -0
- /je_editor/pyside_ui/{treeview/project_treeview → main_ui/ai_widget}/__init__.py +0 -0
- /je_editor/pyside_ui/{user_setting → main_ui/console_widget}/__init__.py +0 -0
- {je_editor-0.0.104.dist-info → je_editor-0.0.228.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
from __future__ import annotations # 支援延遲型別註解 (Python 3.7+)
|
|
2
|
+
|
|
3
|
+
import queue # 佇列,用於執行緒間傳遞訊息
|
|
4
|
+
import subprocess # 建立與管理子程序 (執行外部程式)
|
|
5
|
+
import sys # 系統相關資訊 (平台、參數等)
|
|
6
|
+
from pathlib import Path # 處理檔案與路徑
|
|
7
|
+
from threading import Thread # 執行緒,用於非同步處理
|
|
8
|
+
from typing import Union # 型別提示:允許多種型別
|
|
9
|
+
|
|
10
|
+
from PySide6.QtCore import QTimer # Qt 計時器,用於定時觸發事件
|
|
11
|
+
from PySide6.QtGui import QTextCharFormat # 設定文字格式 (顏色、字型等)
|
|
12
|
+
from PySide6.QtWidgets import QTextEdit # 文字編輯器元件
|
|
13
|
+
|
|
14
|
+
# 專案內部模組
|
|
15
|
+
from je_editor.pyside_ui.code.running_process_manager import run_instance_manager
|
|
16
|
+
from je_editor.pyside_ui.main_ui.editor.editor_widget import EditorWidget
|
|
17
|
+
from je_editor.pyside_ui.main_ui.save_settings.user_color_setting_file import actually_color_dict
|
|
18
|
+
from je_editor.utils.exception.exception_tags import je_editor_init_error
|
|
19
|
+
from je_editor.utils.exception.exceptions import JEditorException
|
|
20
|
+
from je_editor.utils.logging.loggin_instance import jeditor_logger
|
|
21
|
+
from je_editor.utils.venv_check.check_venv import check_and_choose_venv
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Support postponed evaluation of type annotations
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ExecManager(object):
|
|
28
|
+
"""
|
|
29
|
+
程式執行管理器
|
|
30
|
+
Execution manager for running code inside the editor.
|
|
31
|
+
負責:
|
|
32
|
+
- 建立子程序 (subprocess)
|
|
33
|
+
- 讀取標準輸出與錯誤輸出
|
|
34
|
+
- 將結果顯示在 QTextEdit
|
|
35
|
+
- 管理計時器與執行緒
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
main_window: Union[EditorWidget, None] = None, # 主視窗 (可為 None)
|
|
41
|
+
program_language: str = "python", # 預設程式語言
|
|
42
|
+
program_encoding: str = "utf-8", # 預設編碼
|
|
43
|
+
program_buffer: int = 1024, # 緩衝區大小
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
初始化執行管理器
|
|
47
|
+
Initialize ExecManager
|
|
48
|
+
"""
|
|
49
|
+
jeditor_logger.info(f"Init ExecManager "
|
|
50
|
+
f"main_window: {main_window} "
|
|
51
|
+
f"program_language: {program_language} "
|
|
52
|
+
f"program_encoding: {program_encoding} "
|
|
53
|
+
f"program_buffer: {program_buffer}")
|
|
54
|
+
# 初始化屬性
|
|
55
|
+
self.read_program_error_output_from_thread = None # 錯誤輸出讀取執行緒
|
|
56
|
+
self.read_program_output_from_thread = None # 標準輸出讀取執行緒
|
|
57
|
+
self.main_window: EditorWidget = main_window # 主視窗
|
|
58
|
+
self.compiler_path = None # 編譯器/直譯器路徑
|
|
59
|
+
self.code_result: Union[QTextEdit, None] = None # 顯示程式輸出的文字框
|
|
60
|
+
self.code_result_cursor: Union[QTextEdit.textCursor, None] = None
|
|
61
|
+
self.timer: Union[QTimer, None] = None # 定時器
|
|
62
|
+
self.still_run_program = True # 程式是否仍在執行
|
|
63
|
+
self.process: Union[subprocess.Popen, None] = None # 子程序
|
|
64
|
+
self.run_output_queue = queue.Queue() # 標準輸出佇列
|
|
65
|
+
self.run_error_queue = queue.Queue() # 錯誤輸出佇列
|
|
66
|
+
self.program_language = program_language
|
|
67
|
+
self.program_encoding = program_encoding
|
|
68
|
+
self.program_buffer = program_buffer
|
|
69
|
+
self.renew_path() # 設定 Python 直譯器路徑
|
|
70
|
+
run_instance_manager.instance_list.append(self) # 註冊到全域執行管理器
|
|
71
|
+
|
|
72
|
+
def renew_path(self) -> None:
|
|
73
|
+
"""更新 Python 直譯器路徑 / Renew compiler path"""
|
|
74
|
+
jeditor_logger.info("ExecManager renew_path")
|
|
75
|
+
if self.main_window.python_compiler is None:
|
|
76
|
+
# 如果主視窗沒有指定 Python,則使用虛擬環境
|
|
77
|
+
if sys.platform in ["win32", "cygwin", "msys"]:
|
|
78
|
+
venv_path = Path(str(Path.cwd()) + "/venv/Scripts")
|
|
79
|
+
else:
|
|
80
|
+
venv_path = Path(str(Path.cwd()) + "/venv/bin")
|
|
81
|
+
self.compiler_path = check_and_choose_venv(venv_path)
|
|
82
|
+
else:
|
|
83
|
+
self.compiler_path = self.main_window.python_compiler
|
|
84
|
+
|
|
85
|
+
def later_init(self) -> None:
|
|
86
|
+
"""延遲初始化,設定輸出區與計時器 / Setup code result area and timer"""
|
|
87
|
+
jeditor_logger.info("ExecManager later_init")
|
|
88
|
+
if self.main_window is not None:
|
|
89
|
+
self.code_result: QTextEdit = self.main_window.code_result
|
|
90
|
+
self.timer = QTimer(self.main_window)
|
|
91
|
+
else:
|
|
92
|
+
raise JEditorException(je_editor_init_error)
|
|
93
|
+
|
|
94
|
+
def exec_code(self, exec_file_name, exec_prefix: Union[str, list] = None) -> None:
|
|
95
|
+
"""
|
|
96
|
+
執行指定檔案
|
|
97
|
+
Execute given file
|
|
98
|
+
:param exec_file_name: 要執行的檔案名稱
|
|
99
|
+
:param exec_prefix: 使用者自定義前綴 (例如 python -m)
|
|
100
|
+
"""
|
|
101
|
+
jeditor_logger.info(f"ExecManager exec_code "
|
|
102
|
+
f"exec_file_name: {exec_file_name} "
|
|
103
|
+
f"exec_prefix: {exec_prefix}")
|
|
104
|
+
try:
|
|
105
|
+
self.exit_program() # 確保先結束舊的程式
|
|
106
|
+
self.code_result.setPlainText("") # 清空輸出區
|
|
107
|
+
file_path = Path(exec_file_name)
|
|
108
|
+
reformat_os_file_path = str(file_path.absolute())
|
|
109
|
+
exec_file = reformat_os_file_path
|
|
110
|
+
|
|
111
|
+
# 建立執行參數
|
|
112
|
+
if exec_prefix is None:
|
|
113
|
+
execute_program_param = [self.compiler_path, exec_file]
|
|
114
|
+
else:
|
|
115
|
+
if isinstance(exec_prefix, str):
|
|
116
|
+
execute_program_param = [self.compiler_path, exec_prefix, exec_file]
|
|
117
|
+
else:
|
|
118
|
+
execute_program_param = [self.compiler_path] + exec_prefix + [exec_file]
|
|
119
|
+
|
|
120
|
+
# 非 Windows 平台需轉為字串
|
|
121
|
+
if sys.platform not in ["win32", "cygwin", "msys"]:
|
|
122
|
+
execute_program_param = " ".join(execute_program_param)
|
|
123
|
+
|
|
124
|
+
# 建立子程序
|
|
125
|
+
self.process = subprocess.Popen(
|
|
126
|
+
execute_program_param,
|
|
127
|
+
stdout=subprocess.PIPE,
|
|
128
|
+
stderr=subprocess.PIPE,
|
|
129
|
+
stdin=subprocess.PIPE,
|
|
130
|
+
shell=False
|
|
131
|
+
)
|
|
132
|
+
self.still_run_program = True
|
|
133
|
+
|
|
134
|
+
# 啟動輸出讀取執行緒
|
|
135
|
+
self.read_program_output_from_thread = Thread(
|
|
136
|
+
target=self.read_program_output_from_process,
|
|
137
|
+
daemon=True
|
|
138
|
+
)
|
|
139
|
+
self.read_program_output_from_thread.start()
|
|
140
|
+
|
|
141
|
+
# 啟動錯誤讀取執行緒
|
|
142
|
+
self.read_program_error_output_from_thread = Thread(
|
|
143
|
+
target=self.read_program_error_output_from_process,
|
|
144
|
+
daemon=True
|
|
145
|
+
)
|
|
146
|
+
self.read_program_error_output_from_thread.start()
|
|
147
|
+
|
|
148
|
+
# 顯示執行的檔案路徑
|
|
149
|
+
text_cursor = self.code_result.textCursor()
|
|
150
|
+
text_format = QTextCharFormat()
|
|
151
|
+
text_format.setForeground(actually_color_dict.get("normal_output_color"))
|
|
152
|
+
text_cursor.insertText(self.compiler_path + " " + reformat_os_file_path, text_format)
|
|
153
|
+
text_cursor.insertBlock()
|
|
154
|
+
|
|
155
|
+
# 啟動定時器,每 10ms 更新輸出
|
|
156
|
+
self.timer = QTimer(self.main_window)
|
|
157
|
+
self.timer.setInterval(10)
|
|
158
|
+
self.timer.timeout.connect(self.pull_text)
|
|
159
|
+
self.timer.start()
|
|
160
|
+
|
|
161
|
+
except Exception as error:
|
|
162
|
+
# 發生錯誤時顯示錯誤訊息
|
|
163
|
+
text_cursor = self.code_result.textCursor()
|
|
164
|
+
text_format = QTextCharFormat()
|
|
165
|
+
text_format.setForeground(actually_color_dict.get("normal_output_color"))
|
|
166
|
+
text_cursor.insertText(str(error), text_format)
|
|
167
|
+
text_cursor.insertBlock()
|
|
168
|
+
if self.process is not None:
|
|
169
|
+
self.process.terminate()
|
|
170
|
+
|
|
171
|
+
def full_exit_program(self):
|
|
172
|
+
"""完全結束程式 / Fully exit program"""
|
|
173
|
+
jeditor_logger.info("ExecManager full_exit_program")
|
|
174
|
+
self.timer.stop()
|
|
175
|
+
self.exit_program()
|
|
176
|
+
self.main_window.exec_program = None
|
|
177
|
+
|
|
178
|
+
def pull_text(self) -> None:
|
|
179
|
+
jeditor_logger.info("ExecManager pull_text")
|
|
180
|
+
# 從佇列中取出訊息並顯示到 QTextEdit
|
|
181
|
+
# Pull text from queue and put in code result area
|
|
182
|
+
try:
|
|
183
|
+
# 處理標準輸出
|
|
184
|
+
if not self.run_output_queue.empty():
|
|
185
|
+
output_message = self.run_output_queue.get_nowait()
|
|
186
|
+
output_message = str(output_message).strip()
|
|
187
|
+
if output_message:
|
|
188
|
+
text_cursor = self.code_result.textCursor()
|
|
189
|
+
text_format = QTextCharFormat()
|
|
190
|
+
text_format.setForeground(actually_color_dict.get("normal_output_color"))
|
|
191
|
+
text_cursor.insertText(output_message, text_format)
|
|
192
|
+
text_cursor.insertBlock()
|
|
193
|
+
# 處理錯誤輸出
|
|
194
|
+
if not self.run_error_queue.empty():
|
|
195
|
+
error_message = self.run_error_queue.get_nowait()
|
|
196
|
+
error_message = str(error_message).strip()
|
|
197
|
+
if error_message:
|
|
198
|
+
text_cursor = self.code_result.textCursor()
|
|
199
|
+
text_format = QTextCharFormat()
|
|
200
|
+
text_format.setForeground(actually_color_dict.get("error_output_color"))
|
|
201
|
+
text_cursor.insertText(error_message, text_format)
|
|
202
|
+
text_cursor.insertBlock()
|
|
203
|
+
except queue.Empty:
|
|
204
|
+
# 如果佇列是空的就忽略
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
# 如果子程序已經結束(returncode 不為 None),則完全退出
|
|
208
|
+
if self.process.returncode == 0:
|
|
209
|
+
self.full_exit_program()
|
|
210
|
+
elif self.process.returncode is not None:
|
|
211
|
+
self.full_exit_program()
|
|
212
|
+
|
|
213
|
+
# 如果程式仍在執行,持續檢查狀態
|
|
214
|
+
if self.still_run_program:
|
|
215
|
+
# poll() 不會阻塞,只是更新 returncode
|
|
216
|
+
self.process.poll()
|
|
217
|
+
|
|
218
|
+
# 結束程式:將執行旗標設為 False,清理執行緒、佇列與子程序
|
|
219
|
+
# Exit program: change run flag to false and clean read thread, queue, and process
|
|
220
|
+
def exit_program(self) -> None:
|
|
221
|
+
jeditor_logger.info("ExecManager exit_program")
|
|
222
|
+
self.still_run_program = False
|
|
223
|
+
# 清除讀取執行緒的引用
|
|
224
|
+
if self.read_program_output_from_thread is not None:
|
|
225
|
+
self.read_program_output_from_thread = None
|
|
226
|
+
if self.read_program_error_output_from_thread is not None:
|
|
227
|
+
self.read_program_error_output_from_thread = None
|
|
228
|
+
# 清空佇列
|
|
229
|
+
self.print_and_clear_queue()
|
|
230
|
+
# 如果子程序存在,則終止
|
|
231
|
+
if self.process is not None:
|
|
232
|
+
self.process.terminate()
|
|
233
|
+
text_cursor = self.code_result.textCursor()
|
|
234
|
+
text_format = QTextCharFormat()
|
|
235
|
+
text_format.setForeground(actually_color_dict.get("normal_output_color"))
|
|
236
|
+
text_cursor.insertText(f"Program exit with code {self.process.returncode}", text_format)
|
|
237
|
+
text_cursor.insertBlock()
|
|
238
|
+
self.process = None
|
|
239
|
+
|
|
240
|
+
# 清空輸出與錯誤佇列
|
|
241
|
+
# Pull all remaining strings in queues and reset them
|
|
242
|
+
def print_and_clear_queue(self) -> None:
|
|
243
|
+
jeditor_logger.info("ExecManager print_and_clear_queue")
|
|
244
|
+
self.run_output_queue = queue.Queue()
|
|
245
|
+
self.run_error_queue = queue.Queue()
|
|
246
|
+
|
|
247
|
+
# 從子程序 stdout 持續讀取資料並放入輸出佇列
|
|
248
|
+
# Continuously read from process stdout and put into output queue
|
|
249
|
+
def read_program_output_from_process(self) -> None:
|
|
250
|
+
jeditor_logger.info("ExecManager read_program_output_from_process")
|
|
251
|
+
while self.still_run_program:
|
|
252
|
+
program_output_data: str = self.process.stdout.readline(
|
|
253
|
+
self.program_buffer).decode(self.program_encoding, "replace")
|
|
254
|
+
if self.process:
|
|
255
|
+
self.process.stdout.flush()
|
|
256
|
+
self.run_output_queue.put_nowait(program_output_data)
|
|
257
|
+
|
|
258
|
+
# 從子程序 stderr 持續讀取資料並放入錯誤佇列
|
|
259
|
+
# Continuously read from process stderr and put into error queue
|
|
260
|
+
def read_program_error_output_from_process(self) -> None:
|
|
261
|
+
jeditor_logger.info("ExecManager read_program_error_output_from_process")
|
|
262
|
+
while self.still_run_program:
|
|
263
|
+
program_error_output_data: str = self.process.stderr.readline(
|
|
264
|
+
self.program_buffer).decode(self.program_encoding, "replace")
|
|
265
|
+
if self.process:
|
|
266
|
+
self.process.stderr.flush()
|
|
267
|
+
self.run_error_queue.put_nowait(program_error_output_data)
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from je_editor.utils.logging.loggin_instance import jeditor_logger
|
|
7
|
+
|
|
8
|
+
# 僅在型別檢查時匯入,避免循環引用
|
|
9
|
+
# Only imported for type checking, avoids circular imports
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from je_editor.pyside_ui.main_ui.editor.editor_widget import EditorWidget
|
|
12
|
+
from je_editor.pyside_ui.main_ui.editor.editor_widget_dock import FullEditorWidget
|
|
13
|
+
|
|
14
|
+
from typing import Union, List
|
|
15
|
+
|
|
16
|
+
import jedi # Python 自動補全與靜態分析工具
|
|
17
|
+
from PySide6 import QtGui
|
|
18
|
+
from PySide6.QtCore import Qt, QRect
|
|
19
|
+
from PySide6.QtGui import (
|
|
20
|
+
QPainter, QTextCharFormat, QTextFormat, QKeyEvent, QAction,
|
|
21
|
+
QTextDocument, QTextCursor, QTextOption
|
|
22
|
+
)
|
|
23
|
+
from PySide6.QtWidgets import QPlainTextEdit, QWidget, QTextEdit, QCompleter
|
|
24
|
+
from jedi.api.classes import Completion
|
|
25
|
+
|
|
26
|
+
from je_editor.pyside_ui.code.syntax.python_syntax import PythonHighlighter
|
|
27
|
+
from je_editor.pyside_ui.dialog.search_ui.search_text_box import SearchBox
|
|
28
|
+
from je_editor.pyside_ui.main_ui.save_settings.user_color_setting_file import actually_color_dict
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def venv_check():
|
|
32
|
+
"""檢查當前工作目錄下是否有 venv 資料夾 / Check if venv exists in current working directory"""
|
|
33
|
+
jeditor_logger.info("code_edit_plaintext.py venv check")
|
|
34
|
+
venv_path = Path(str(Path.cwd()) + "/venv")
|
|
35
|
+
return venv_path
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CodeEditor(QPlainTextEdit):
|
|
39
|
+
"""
|
|
40
|
+
自訂的程式碼編輯器,繼承 QPlainTextEdit
|
|
41
|
+
Custom code editor extending QPlainTextEdit
|
|
42
|
+
|
|
43
|
+
功能:
|
|
44
|
+
- 行號顯示 (Line number area)
|
|
45
|
+
- Tab 縮排距離設定
|
|
46
|
+
- Python 語法高亮 (Syntax highlighting)
|
|
47
|
+
- 搜尋功能 (Search box)
|
|
48
|
+
- 自動補全 (Autocomplete with Jedi)
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, main_window: Union[EditorWidget, FullEditorWidget]):
|
|
52
|
+
jeditor_logger.info(f"Init CodeEditor main_window: {main_window}")
|
|
53
|
+
super().__init__()
|
|
54
|
+
|
|
55
|
+
# Jedi 環境,用於 Python 自動補全
|
|
56
|
+
self.env = None
|
|
57
|
+
self.check_env()
|
|
58
|
+
|
|
59
|
+
# 主視窗 (父元件)
|
|
60
|
+
self.main_window = main_window
|
|
61
|
+
self.current_file = main_window.current_file
|
|
62
|
+
|
|
63
|
+
# 定義哪些按鍵不會觸發補全視窗
|
|
64
|
+
self.skip_popup_behavior_list = [
|
|
65
|
+
Qt.Key.Key_Enter, Qt.Key.Key_Return, Qt.Key.Key_Up, Qt.Key.Key_Down,
|
|
66
|
+
Qt.Key.Key_Tab, Qt.Key.Key_Backtab, Qt.Key.Key_Space, Qt.Key.Key_Backspace
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
# 定義哪些按鍵會觸發補全 (A-Z)
|
|
70
|
+
self.need_complete_list = [
|
|
71
|
+
Qt.Key.Key_A, Qt.Key.Key_B, Qt.Key.Key_C, Qt.Key.Key_D, Qt.Key.Key_E, Qt.Key.Key_F,
|
|
72
|
+
Qt.Key.Key_G, Qt.Key.Key_H, Qt.Key.Key_I, Qt.Key.Key_J, Qt.Key.Key_K, Qt.Key.Key_L,
|
|
73
|
+
Qt.Key.Key_M, Qt.Key.Key_N, Qt.Key.Key_O, Qt.Key.Key_P, Qt.Key.Key_Q, Qt.Key.Key_R,
|
|
74
|
+
Qt.Key.Key_S, Qt.Key.Key_T, Qt.Key.Key_U, Qt.Key.Key_V, Qt.Key.Key_W, Qt.Key.Key_X,
|
|
75
|
+
Qt.Key.Key_Y, Qt.Key.Key_Z
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
# 搜尋框 (延遲建立)
|
|
79
|
+
self.search_box = None
|
|
80
|
+
|
|
81
|
+
# 行號區域 (LineNumber 是另一個自訂類別)
|
|
82
|
+
self.line_number: LineNumber = LineNumber(self)
|
|
83
|
+
self.blockCountChanged.connect(self.update_line_number_area_width)
|
|
84
|
+
self.updateRequest.connect(self.update_line_number_area)
|
|
85
|
+
self.update_line_number_area_width(0)
|
|
86
|
+
|
|
87
|
+
# 當文字改變時,重新高亮當前行
|
|
88
|
+
self.textChanged.connect(self.highlight_current_line)
|
|
89
|
+
|
|
90
|
+
# 設定 Tab 寬度 (以字元寬度計算)
|
|
91
|
+
self.setTabStopDistance(
|
|
92
|
+
QtGui.QFontMetricsF(self.font()).horizontalAdvance(" ")
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Python 語法高亮
|
|
96
|
+
self.highlighter = PythonHighlighter(self.document(), main_window=self)
|
|
97
|
+
self.highlight_current_line()
|
|
98
|
+
|
|
99
|
+
# 關閉自動換行,改為單行顯示
|
|
100
|
+
self.setLineWrapMode(self.LineWrapMode.NoWrap)
|
|
101
|
+
self.setWordWrapMode(QTextOption.WrapMode.WrapAnywhere)
|
|
102
|
+
|
|
103
|
+
# 搜尋功能 (Ctrl+F)
|
|
104
|
+
self.search_action = QAction("Search")
|
|
105
|
+
self.search_action.setShortcut("Ctrl+f")
|
|
106
|
+
self.search_action.triggered.connect(self.start_search_dialog)
|
|
107
|
+
self.addAction(self.search_action)
|
|
108
|
+
|
|
109
|
+
# 自動補全初始化
|
|
110
|
+
self.completer: Union[None, QCompleter] = None
|
|
111
|
+
self.set_complete([])
|
|
112
|
+
|
|
113
|
+
def reset_highlighter(self):
|
|
114
|
+
"""重設語法高亮 / Reset syntax highlighter"""
|
|
115
|
+
jeditor_logger.info("CodeEditor reset_highlighter")
|
|
116
|
+
self.highlighter = PythonHighlighter(self.document(), main_window=self)
|
|
117
|
+
self.highlight_current_line()
|
|
118
|
+
|
|
119
|
+
def check_env(self):
|
|
120
|
+
"""檢查虛擬環境並建立 Jedi 環境 / Check venv and create Jedi environment"""
|
|
121
|
+
jeditor_logger.info("CodeEditor check_env")
|
|
122
|
+
path = venv_check()
|
|
123
|
+
if path.exists():
|
|
124
|
+
self.env = jedi.create_environment(str(path))
|
|
125
|
+
|
|
126
|
+
def set_complete(self, list_to_complete: list) -> None:
|
|
127
|
+
"""
|
|
128
|
+
設定自動補全清單
|
|
129
|
+
Set completion list
|
|
130
|
+
"""
|
|
131
|
+
jeditor_logger.info(f"CodeEditor set_complete list_to_complete: {list_to_complete}")
|
|
132
|
+
completer = QCompleter(list_to_complete)
|
|
133
|
+
completer.activated.connect(self.insert_completion)
|
|
134
|
+
completer.setWidget(self)
|
|
135
|
+
completer.setWrapAround(False)
|
|
136
|
+
completer.setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
|
|
137
|
+
completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
|
|
138
|
+
self.completer = completer
|
|
139
|
+
|
|
140
|
+
def insert_completion(self, completion) -> None:
|
|
141
|
+
"""
|
|
142
|
+
插入補全文字
|
|
143
|
+
Insert completion text into editor
|
|
144
|
+
"""
|
|
145
|
+
jeditor_logger.info(f"CodeEditor insert_completion completion: {completion}")
|
|
146
|
+
if self.completer.widget() != self:
|
|
147
|
+
return
|
|
148
|
+
text_cursor = self.textCursor()
|
|
149
|
+
extra = len(completion) - len(self.completer.completionPrefix())
|
|
150
|
+
text_cursor.movePosition(QTextCursor.MoveOperation.Left)
|
|
151
|
+
text_cursor.movePosition(QTextCursor.MoveOperation.EndOfWord)
|
|
152
|
+
text_cursor.insertText(completion[-extra:])
|
|
153
|
+
self.setTextCursor(text_cursor)
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def text_under_cursor(self):
|
|
157
|
+
"""取得游標下的文字 / Get text under cursor"""
|
|
158
|
+
jeditor_logger.info("CodeEditor text_under_cursor")
|
|
159
|
+
text_cursor = self.textCursor()
|
|
160
|
+
text_cursor.select(QTextCursor.SelectionType.WordUnderCursor)
|
|
161
|
+
return text_cursor.selectedText()
|
|
162
|
+
|
|
163
|
+
def focusInEvent(self, e) -> None:
|
|
164
|
+
"""當編輯器獲得焦點時,確保 completer 綁定正確"""
|
|
165
|
+
jeditor_logger.info(f"CodeEditor focusInEvent event: {e}")
|
|
166
|
+
if self.completer:
|
|
167
|
+
self.completer.setWidget(self)
|
|
168
|
+
QPlainTextEdit.focusInEvent(self, e)
|
|
169
|
+
|
|
170
|
+
def complete(self) -> None:
|
|
171
|
+
"""
|
|
172
|
+
使用 Jedi 進行自動補全
|
|
173
|
+
Keyword autocomplete with Jedi
|
|
174
|
+
"""
|
|
175
|
+
jeditor_logger.info("CodeEditor complete")
|
|
176
|
+
prefix = self.text_under_cursor
|
|
177
|
+
if self.env is not None:
|
|
178
|
+
script = jedi.Script(code=self.toPlainText(), environment=self.env)
|
|
179
|
+
else:
|
|
180
|
+
script = jedi.Script(code=self.toPlainText())
|
|
181
|
+
|
|
182
|
+
# 取得補全清單
|
|
183
|
+
jedi_complete_list: List[Completion] = script.complete(
|
|
184
|
+
self.textCursor().blockNumber() + 1,
|
|
185
|
+
len(self.textCursor().document().findBlockByLineNumber(self.textCursor().blockNumber()).text())
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if len(jedi_complete_list) > 0:
|
|
189
|
+
new_complete_list = [complete_text.name for complete_text in jedi_complete_list]
|
|
190
|
+
self.set_complete(new_complete_list)
|
|
191
|
+
|
|
192
|
+
# 顯示補全視窗
|
|
193
|
+
self.completer.setCompletionPrefix(prefix)
|
|
194
|
+
popup = self.completer.popup()
|
|
195
|
+
cursor_rect = self.cursorRect()
|
|
196
|
+
popup.setCurrentIndex(self.completer.completionModel().index(0, 0))
|
|
197
|
+
cursor_rect.setWidth(self.completer.popup().rect().size().width())
|
|
198
|
+
self.completer.complete(cursor_rect)
|
|
199
|
+
|
|
200
|
+
def start_search_dialog(self) -> None:
|
|
201
|
+
"""顯示搜尋框 / Show search box"""
|
|
202
|
+
jeditor_logger.info("CodeEditor start_search_dialog")
|
|
203
|
+
self.search_box = SearchBox()
|
|
204
|
+
self.search_box.search_back_button.clicked.connect(self.find_back_text)
|
|
205
|
+
self.search_box.search_next_button.clicked.connect(
|
|
206
|
+
self.find_next_text
|
|
207
|
+
)
|
|
208
|
+
self.search_box.show()
|
|
209
|
+
|
|
210
|
+
def find_next_text(self) -> None:
|
|
211
|
+
"""
|
|
212
|
+
找到下一個符合的文字
|
|
213
|
+
Find next match text
|
|
214
|
+
"""
|
|
215
|
+
jeditor_logger.info("CodeEditor find_next_text")
|
|
216
|
+
if self.search_box.isVisible():
|
|
217
|
+
text = self.search_box.command_input.text()
|
|
218
|
+
self.find(text)
|
|
219
|
+
|
|
220
|
+
def find_back_text(self) -> None:
|
|
221
|
+
"""
|
|
222
|
+
找到上一個符合的文字
|
|
223
|
+
Find previous match text
|
|
224
|
+
"""
|
|
225
|
+
jeditor_logger.info("CodeEditor find_back_text")
|
|
226
|
+
if self.search_box.isVisible():
|
|
227
|
+
text = self.search_box.command_input.text()
|
|
228
|
+
self.find(text, QTextDocument.FindFlag.FindBackward)
|
|
229
|
+
|
|
230
|
+
def line_number_paint(self, event) -> None:
|
|
231
|
+
"""
|
|
232
|
+
繪製行號區域
|
|
233
|
+
Paint line number area
|
|
234
|
+
"""
|
|
235
|
+
jeditor_logger.info(f"CodeEditor line_number_paint event: {event}")
|
|
236
|
+
painter = QPainter(self.line_number)
|
|
237
|
+
# 填滿背景色
|
|
238
|
+
painter.fillRect(event.rect(), actually_color_dict.get("line_number_background_color"))
|
|
239
|
+
|
|
240
|
+
# 從第一個可見區塊開始
|
|
241
|
+
block = self.firstVisibleBlock()
|
|
242
|
+
block_number = block.blockNumber()
|
|
243
|
+
top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top()
|
|
244
|
+
bottom = top + self.blockBoundingRect(block).height()
|
|
245
|
+
|
|
246
|
+
# 逐行繪製行號
|
|
247
|
+
while block.isValid() and top <= event.rect().bottom():
|
|
248
|
+
if block.isVisible() and bottom >= event.rect().top():
|
|
249
|
+
number = str(block_number + 1)
|
|
250
|
+
painter.setPen(actually_color_dict.get("line_number_color"))
|
|
251
|
+
painter.drawText(
|
|
252
|
+
0,
|
|
253
|
+
top,
|
|
254
|
+
self.line_number.width(),
|
|
255
|
+
self.fontMetrics().height(),
|
|
256
|
+
Qt.AlignmentFlag.AlignCenter,
|
|
257
|
+
number,
|
|
258
|
+
)
|
|
259
|
+
block = block.next()
|
|
260
|
+
top = bottom
|
|
261
|
+
bottom = top + self.blockBoundingRect(block).height()
|
|
262
|
+
block_number += 1
|
|
263
|
+
|
|
264
|
+
def line_number_width(self) -> int:
|
|
265
|
+
"""
|
|
266
|
+
計算行號區域寬度
|
|
267
|
+
Calculate line number area width
|
|
268
|
+
"""
|
|
269
|
+
jeditor_logger.info("CodeEditor line_number_width")
|
|
270
|
+
digits = len(str(self.blockCount())) # 根據總行數決定位數
|
|
271
|
+
space = 12 * digits
|
|
272
|
+
return space
|
|
273
|
+
|
|
274
|
+
def update_line_number_area_width(self, value) -> None:
|
|
275
|
+
"""
|
|
276
|
+
更新行號區域寬度
|
|
277
|
+
Update line number area width
|
|
278
|
+
"""
|
|
279
|
+
jeditor_logger.info(f"CodeEditor update_line_number_area_width value: {value}")
|
|
280
|
+
self.setViewportMargins(self.line_number_width(), 0, 0, 0)
|
|
281
|
+
|
|
282
|
+
def resizeEvent(self, event) -> None:
|
|
283
|
+
"""
|
|
284
|
+
視窗大小改變時,調整行號區域
|
|
285
|
+
Resize line number paint area
|
|
286
|
+
"""
|
|
287
|
+
jeditor_logger.info(f"CodeEditor resizeEvent event:{event}")
|
|
288
|
+
QPlainTextEdit.resizeEvent(self, event)
|
|
289
|
+
cr = self.contentsRect()
|
|
290
|
+
self.line_number.setGeometry(
|
|
291
|
+
QRect(cr.left(), cr.top(), self.line_number_width(), cr.height()),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def update_line_number_area(self, rect, dy) -> None:
|
|
295
|
+
"""
|
|
296
|
+
更新行號顯示
|
|
297
|
+
Update line number area
|
|
298
|
+
"""
|
|
299
|
+
jeditor_logger.info(f"CodeEditor update_line_number_area rect: {rect}, dy: {dy}")
|
|
300
|
+
if dy:
|
|
301
|
+
self.line_number.scroll(0, dy)
|
|
302
|
+
else:
|
|
303
|
+
self.line_number.update(
|
|
304
|
+
0,
|
|
305
|
+
rect.y(),
|
|
306
|
+
self.line_number.width(),
|
|
307
|
+
rect.height(),
|
|
308
|
+
)
|
|
309
|
+
if rect.contains(self.viewport().rect()):
|
|
310
|
+
self.update_line_number_area_width(0)
|
|
311
|
+
|
|
312
|
+
def highlight_current_line(self) -> None:
|
|
313
|
+
"""
|
|
314
|
+
高亮目前所在行
|
|
315
|
+
Highlight current line
|
|
316
|
+
"""
|
|
317
|
+
jeditor_logger.info("CodeEditor highlight_current_line")
|
|
318
|
+
selections = []
|
|
319
|
+
if not self.isReadOnly():
|
|
320
|
+
formats = QTextCharFormat()
|
|
321
|
+
selection = QTextEdit.ExtraSelection()
|
|
322
|
+
selection.format = formats
|
|
323
|
+
color_of_the_line = actually_color_dict.get("current_line_color")
|
|
324
|
+
selection.cursor = self.textCursor()
|
|
325
|
+
selection.cursor.clearSelection()
|
|
326
|
+
selections.append(selection)
|
|
327
|
+
selection.format.setBackground(color_of_the_line)
|
|
328
|
+
selection.format.setProperty(QTextFormat.FullWidthSelection, True)
|
|
329
|
+
self.setExtraSelections(selections)
|
|
330
|
+
|
|
331
|
+
def keyPressEvent(self, event: QKeyEvent) -> None:
|
|
332
|
+
"""
|
|
333
|
+
鍵盤事件處理
|
|
334
|
+
Handle key press events
|
|
335
|
+
- Ctrl+B: 使用 Jedi 跳轉定義
|
|
336
|
+
- Shift+Enter: 忽略軟換行
|
|
337
|
+
- 其他情況觸發自動補全
|
|
338
|
+
"""
|
|
339
|
+
key = event.key()
|
|
340
|
+
jeditor_logger.info(f"CodeEditor keyPressEvent event: {event} key: {key}")
|
|
341
|
+
|
|
342
|
+
# Ctrl + B → 跳轉到定義
|
|
343
|
+
if event.modifiers() and Qt.Modifier.CTRL:
|
|
344
|
+
if key == Qt.Key.Key_B:
|
|
345
|
+
if self.env is not None:
|
|
346
|
+
script = jedi.Script(code=self.toPlainText(), environment=self.env)
|
|
347
|
+
else:
|
|
348
|
+
script = jedi.Script(code=self.toPlainText())
|
|
349
|
+
goto_list: List[jedi.api.classes.Name] = script.goto(
|
|
350
|
+
self.textCursor().blockNumber() + 1, self.textCursor().positionInBlock())
|
|
351
|
+
if len(goto_list) > 0:
|
|
352
|
+
path = goto_list[0].module_path
|
|
353
|
+
if path is not None and path.exists():
|
|
354
|
+
if self.main_window.current_file != str(path):
|
|
355
|
+
self.main_window.main_window.go_to_new_tab(path)
|
|
356
|
+
else:
|
|
357
|
+
self.textCursor().setPosition(goto_list[0].line - 1)
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
# 如果補全視窗開啟,且按下不該觸發的按鍵 → 關閉補全
|
|
361
|
+
if self.completer.popup().isVisible() and key in self.skip_popup_behavior_list:
|
|
362
|
+
self.completer.popup().close()
|
|
363
|
+
event.ignore()
|
|
364
|
+
return
|
|
365
|
+
|
|
366
|
+
# Shift+Enter → 忽略 (避免軟換行影響行號)
|
|
367
|
+
if event.modifiers() and Qt.Modifier.SHIFT:
|
|
368
|
+
if key == Qt.Key.Key_Enter or key == Qt.Key.Key_Return:
|
|
369
|
+
event.ignore()
|
|
370
|
+
return
|
|
371
|
+
|
|
372
|
+
# 呼叫父類別處理其他按鍵
|
|
373
|
+
super().keyPressEvent(event)
|
|
374
|
+
|
|
375
|
+
# 更新目前行高亮
|
|
376
|
+
self.highlight_current_line()
|
|
377
|
+
|
|
378
|
+
# 如果輸入英文字母,觸發自動補全
|
|
379
|
+
if key in self.need_complete_list and self.completer is not None:
|
|
380
|
+
if self.completer.popup().isVisible():
|
|
381
|
+
self.completer.popup().close()
|
|
382
|
+
self.complete()
|
|
383
|
+
|
|
384
|
+
def mousePressEvent(self, event) -> None:
|
|
385
|
+
"""
|
|
386
|
+
滑鼠點擊事件
|
|
387
|
+
Mouse press event
|
|
388
|
+
- 點擊後高亮所在行
|
|
389
|
+
"""
|
|
390
|
+
jeditor_logger.info(f"CodeEditor mousePressEvent event: {event}")
|
|
391
|
+
super().mousePressEvent(event)
|
|
392
|
+
self.highlight_current_line()
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class LineNumber(QWidget):
|
|
396
|
+
"""
|
|
397
|
+
行號區域元件
|
|
398
|
+
Widget used to paint line numbers
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
def __init__(self, editor):
|
|
402
|
+
jeditor_logger.info("Init LineNumber")
|
|
403
|
+
QWidget.__init__(self, parent=editor)
|
|
404
|
+
self.editor = editor
|
|
405
|
+
|
|
406
|
+
def paintEvent(self, event) -> None:
|
|
407
|
+
"""
|
|
408
|
+
呼叫編輯器的 line_number_paint 來繪製行號
|
|
409
|
+
Delegate painting to CodeEditor.line_number_paint
|
|
410
|
+
"""
|
|
411
|
+
jeditor_logger.info(f"LineNumber paintEvent event: {event}")
|
|
412
|
+
self.editor.line_number_paint(event)
|