je-editor 0.0.223__py3-none-any.whl → 0.0.224__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.
Potentially problematic release.
This version of je-editor might be problematic. Click here for more details.
- je_editor/git_client/commit_graph.py +7 -7
- je_editor/git_client/git_action.py +0 -7
- je_editor/pyside_ui/browser/browser_widget.py +24 -11
- je_editor/pyside_ui/browser/main_browser_widget.py +40 -27
- je_editor/pyside_ui/code/auto_save/auto_save_manager.py +34 -2
- je_editor/pyside_ui/code/auto_save/auto_save_thread.py +19 -6
- je_editor/pyside_ui/code/code_format/pep8_format.py +53 -9
- je_editor/pyside_ui/code/code_process/code_exec.py +88 -52
- je_editor/pyside_ui/code/plaintext_code_edit/code_edit_plaintext.py +116 -55
- je_editor/pyside_ui/code/running_process_manager.py +19 -1
- je_editor/pyside_ui/code/shell_process/shell_exec.py +71 -48
- je_editor/pyside_ui/code/syntax/python_syntax.py +45 -10
- je_editor/pyside_ui/code/syntax/syntax_setting.py +40 -12
- je_editor/pyside_ui/code/textedit_code_result/code_record.py +34 -12
- je_editor/pyside_ui/code/variable_inspector/inspector_gui.py +53 -6
- je_editor/pyside_ui/dialog/ai_dialog/set_ai_dialog.py +30 -3
- je_editor/pyside_ui/dialog/file_dialog/create_file_dialog.py +35 -2
- je_editor/pyside_ui/dialog/file_dialog/open_file_dialog.py +33 -5
- je_editor/pyside_ui/dialog/file_dialog/save_file_dialog.py +25 -3
- je_editor/pyside_ui/dialog/search_ui/search_error_box.py +26 -1
- je_editor/pyside_ui/dialog/search_ui/search_text_box.py +26 -1
- je_editor/pyside_ui/git_ui/code_diff_compare/code_diff_viewer_widget.py +11 -11
- je_editor/pyside_ui/git_ui/git_client/commit_table.py +46 -8
- je_editor/pyside_ui/git_ui/git_client/git_branch_tree_widget.py +49 -15
- je_editor/pyside_ui/git_ui/git_client/graph_view.py +64 -20
- je_editor/pyside_ui/main_ui/ai_widget/ai_config.py +20 -5
- je_editor/pyside_ui/main_ui/ai_widget/ask_thread.py +20 -1
- je_editor/pyside_ui/main_ui/ai_widget/chat_ui.py +56 -41
- je_editor/pyside_ui/main_ui/ai_widget/langchain_interface.py +45 -6
- je_editor/pyside_ui/main_ui/console_widget/console_gui.py +44 -12
- je_editor/pyside_ui/main_ui/console_widget/qprocess_adapter.py +34 -13
- je_editor/pyside_ui/main_ui/dock/destroy_dock.py +33 -2
- je_editor/pyside_ui/main_ui/editor/editor_widget.py +104 -20
- je_editor/pyside_ui/main_ui/editor/editor_widget_dock.py +34 -7
- je_editor/pyside_ui/main_ui/editor/process_input.py +38 -11
- je_editor/pyside_ui/main_ui/ipython_widget/rich_jupyter.py +46 -11
- je_editor/pyside_ui/main_ui/main_editor.py +175 -37
- je_editor/pyside_ui/main_ui/menu/check_style_menu/build_check_style_menu.py +51 -28
- je_editor/pyside_ui/main_ui/menu/dock_menu/build_dock_menu.py +80 -22
- je_editor/pyside_ui/main_ui/menu/file_menu/build_file_menu.py +70 -17
- je_editor/pyside_ui/main_ui/menu/help_menu/build_help_menu.py +34 -3
- je_editor/pyside_ui/main_ui/menu/language_menu/build_language_server.py +41 -1
- je_editor/pyside_ui/main_ui/menu/python_env_menu/build_venv_menu.py +100 -42
- je_editor/pyside_ui/main_ui/menu/run_menu/build_run_menu.py +57 -7
- je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_debug_menu.py +50 -4
- je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_program_menu.py +52 -6
- je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_shell_menu.py +44 -4
- je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/utils.py +23 -1
- je_editor/pyside_ui/main_ui/menu/set_menu_bar.py +37 -12
- je_editor/pyside_ui/main_ui/menu/style_menu/build_style_menu.py +44 -7
- je_editor/pyside_ui/main_ui/menu/tab_menu/build_tab_menu.py +126 -28
- je_editor/pyside_ui/main_ui/menu/text_menu/build_text_menu.py +65 -1
- je_editor/pyside_ui/main_ui/save_settings/setting_utils.py +18 -1
- je_editor/pyside_ui/main_ui/save_settings/user_color_setting_file.py +33 -3
- je_editor/pyside_ui/main_ui/save_settings/user_setting_file.py +38 -11
- je_editor/pyside_ui/main_ui/system_tray/extend_system_tray.py +39 -2
- je_editor/start_editor.py +26 -1
- je_editor/utils/encodings/python_encodings.py +101 -98
- je_editor/utils/file/open/open_file.py +36 -19
- je_editor/utils/file/save/save_file.py +35 -14
- je_editor/utils/json/json_file.py +29 -14
- je_editor/utils/json_format/json_process.py +33 -2
- je_editor/utils/logging/loggin_instance.py +38 -8
- je_editor/utils/multi_language/multi_language_wrapper.py +29 -4
- je_editor/utils/redirect_manager/redirect_manager_class.py +49 -11
- je_editor/utils/venv_check/check_venv.py +45 -15
- {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/METADATA +1 -1
- {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/RECORD +71 -71
- {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/WHEEL +0 -0
- {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/licenses/LICENSE +0 -0
- {je_editor-0.0.223.dist-info → je_editor-0.0.224.dist-info}/top_level.txt +0 -0
|
@@ -14,24 +14,59 @@ from je_editor.utils.multi_language.multi_language_wrapper import language_wrapp
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
15
|
from je_editor.pyside_ui.main_ui.ai_widget.chat_ui import ChatUI
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
class LangChainInterface(object):
|
|
19
|
+
"""
|
|
20
|
+
LangChainInterface 負責與 LangChain + OpenAI 模型互動
|
|
21
|
+
LangChainInterface is responsible for interacting with LangChain + OpenAI model
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, main_window: ChatUI, prompt_template: str, base_url: str,
|
|
25
|
+
api_key: Union[SecretStr, str], chat_model: str):
|
|
26
|
+
"""
|
|
27
|
+
初始化 LangChainInterface
|
|
28
|
+
Initialize LangChainInterface
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
|
|
30
|
+
:param main_window: 主視窗,用於顯示錯誤訊息 / Main window, used for showing error messages
|
|
31
|
+
:param prompt_template: 系統提示詞模板 / System prompt template
|
|
32
|
+
:param base_url: OpenAI API 的基礎 URL / Base URL for OpenAI API
|
|
33
|
+
:param api_key: OpenAI API 金鑰 / OpenAI API key
|
|
34
|
+
:param chat_model: 使用的聊天模型名稱 / Chat model name
|
|
35
|
+
"""
|
|
36
|
+
# 建立系統提示詞模板 / Create system message prompt template
|
|
21
37
|
self.system_message_prompt = SystemMessagePromptTemplate.from_template(prompt_template)
|
|
38
|
+
|
|
39
|
+
# 儲存基本設定 / Store basic settings
|
|
22
40
|
self.base_url = base_url
|
|
23
41
|
self.api_key = api_key
|
|
24
42
|
self.chat_model = chat_model
|
|
25
43
|
self.main_window = main_window
|
|
44
|
+
|
|
45
|
+
# 將設定寫入環境變數,方便其他套件讀取
|
|
46
|
+
# Save settings into environment variables for other packages to read
|
|
26
47
|
os.environ["OPENAI_BASE_URL"] = self.base_url
|
|
27
48
|
os.environ["OPENAI_API_KEY"] = self.api_key
|
|
28
49
|
os.environ["CHAT_MODEL"] = self.chat_model
|
|
50
|
+
|
|
51
|
+
# 初始化 ChatOpenAI 物件,用於呼叫模型
|
|
52
|
+
# Initialize ChatOpenAI object for model invocation
|
|
29
53
|
self.chat_ai = ChatOpenAI(base_url=self.base_url, api_key=self.api_key, model=self.chat_model)
|
|
30
54
|
|
|
31
55
|
def call_ai_model(self, prompt: str) -> str | None:
|
|
56
|
+
"""
|
|
57
|
+
呼叫 AI 模型並回傳結果
|
|
58
|
+
Call AI model and return response
|
|
59
|
+
|
|
60
|
+
:param prompt: 使用者輸入的提示詞 / User input prompt
|
|
61
|
+
:return: AI 回覆文字或 None / AI response text or None
|
|
62
|
+
"""
|
|
32
63
|
message = None
|
|
33
64
|
try:
|
|
65
|
+
# 呼叫 AI 並取得回覆 / Invoke AI and get response
|
|
34
66
|
message = self.chat_ai.invoke(prompt).text()
|
|
67
|
+
|
|
68
|
+
# 嘗試過濾掉 <think> 標籤前的內容,只保留主要回覆
|
|
69
|
+
# Try to filter out content before </think>, keep only main response
|
|
35
70
|
match = re.search(r"</think>\s*(.*)", message, re.DOTALL)
|
|
36
71
|
if match:
|
|
37
72
|
message = match.group(1).strip()
|
|
@@ -39,7 +74,11 @@ class LangChainInterface(object):
|
|
|
39
74
|
message = message
|
|
40
75
|
|
|
41
76
|
except Exception as error:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
77
|
+
# 發生錯誤時,彈出警告視窗顯示錯誤訊息
|
|
78
|
+
# Show error message in a warning dialog if exception occurs
|
|
79
|
+
QMessageBox.warning(
|
|
80
|
+
self.main_window,
|
|
81
|
+
language_wrapper.language_word_dict.get("call_ai_model_error_title"),
|
|
82
|
+
str(error)
|
|
83
|
+
)
|
|
84
|
+
return message
|
|
@@ -12,42 +12,59 @@ from je_editor.utils.multi_language.multi_language_wrapper import language_wrapp
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class ConsoleWidget(QWidget):
|
|
15
|
+
"""
|
|
16
|
+
ConsoleWidget 提供一個互動式終端機介面,讓使用者可以輸入指令並查看輸出。
|
|
17
|
+
ConsoleWidget provides an interactive console interface for running commands and viewing output.
|
|
18
|
+
"""
|
|
19
|
+
|
|
15
20
|
def __init__(self, parent=None):
|
|
16
21
|
super().__init__(parent)
|
|
17
22
|
self.language_word_dict_get = language_wrapper.language_word_dict.get
|
|
23
|
+
|
|
24
|
+
# 初始化子程序控制器 / Initialize process adapter
|
|
18
25
|
self.proc = ConsoleProcessAdapter(self)
|
|
19
|
-
self.proc.stdout.connect(self.append_text)
|
|
20
|
-
self.proc.stderr.connect(lambda t: self.append_text(t, "#d33"))
|
|
26
|
+
self.proc.stdout.connect(self.append_text) # 標準輸出 / Standard output
|
|
27
|
+
self.proc.stderr.connect(lambda t: self.append_text(t, "#d33")) # 錯誤輸出 (紅色) / Error output (red)
|
|
21
28
|
self.proc.system.connect(
|
|
22
|
-
lambda t: self.append_text(f"{self.language_word_dict_get('dynamic_console_system_prefix')}{t}\n", "#888")
|
|
29
|
+
lambda t: self.append_text(f"{self.language_word_dict_get('dynamic_console_system_prefix')}{t}\n", "#888")
|
|
30
|
+
)
|
|
23
31
|
self.proc.started.connect(lambda: self.proc.system.emit(self.language_word_dict_get("dynamic_console_running")))
|
|
24
32
|
self.proc.finished.connect(self.on_finished)
|
|
25
33
|
|
|
34
|
+
# 指令歷史紀錄 / Command history
|
|
26
35
|
self.history, self.history_index = [], -1
|
|
27
36
|
|
|
37
|
+
# 輸出區域 / Output area
|
|
28
38
|
self.output = QPlainTextEdit(readOnly=True)
|
|
29
|
-
self.output.setMaximumBlockCount(10000)
|
|
39
|
+
self.output.setMaximumBlockCount(10000) # 最多保留 10000 行 / Keep up to 10000 lines
|
|
30
40
|
self.output.setStyleSheet("font-family: Consolas, monospace;")
|
|
31
41
|
|
|
42
|
+
# 輸入框 / Input field
|
|
32
43
|
self.input = QLineEdit()
|
|
33
|
-
self.input.setPlaceholderText("Enter command")
|
|
34
|
-
self.input.returnPressed.connect(self.run_command)
|
|
35
|
-
self.input.installEventFilter(self)
|
|
44
|
+
self.input.setPlaceholderText("Enter command") # 預設提示文字 / Placeholder text
|
|
45
|
+
self.input.returnPressed.connect(self.run_command) # 按 Enter 執行指令 / Run command on Enter
|
|
46
|
+
self.input.installEventFilter(self) # 安裝事件過濾器,用於上下鍵瀏覽歷史 / Install event filter for history navigation
|
|
36
47
|
|
|
48
|
+
# 控制按鈕 / Control buttons
|
|
37
49
|
self.btn_run = QPushButton(self.language_word_dict_get("dynamic_console_run"))
|
|
38
50
|
self.btn_run.clicked.connect(self.run_command)
|
|
51
|
+
|
|
39
52
|
self.btn_stop = QPushButton(self.language_word_dict_get("dynamic_console_stop"))
|
|
40
53
|
self.btn_stop.clicked.connect(self.proc.stop)
|
|
54
|
+
|
|
41
55
|
self.btn_clear = QPushButton(self.language_word_dict_get("dynamic_console_clear"))
|
|
42
56
|
self.btn_clear.clicked.connect(self.output.clear)
|
|
43
57
|
|
|
58
|
+
# 工作目錄顯示與選擇 / Current working directory display and picker
|
|
44
59
|
self.cwd_label = QLabel(f"{self.language_word_dict_get('dynamic_console_cwd')}: {os.getcwd()}")
|
|
45
60
|
self.btn_pick_cwd = QPushButton("…")
|
|
46
61
|
self.btn_pick_cwd.clicked.connect(self.pick_cwd)
|
|
47
62
|
|
|
63
|
+
# Shell 選擇下拉選單 / Shell selection combobox
|
|
48
64
|
self.shell_combo = QComboBox()
|
|
49
65
|
self.shell_combo.addItems(["auto", "cmd", "powershell", "bash", "sh"])
|
|
50
66
|
|
|
67
|
+
# 版面配置:上方 (工作目錄 + Shell 選擇) / Layout: Top (CWD + Shell selection)
|
|
51
68
|
top = QHBoxLayout()
|
|
52
69
|
top.addWidget(self.cwd_label)
|
|
53
70
|
top.addWidget(self.btn_pick_cwd)
|
|
@@ -55,21 +72,27 @@ class ConsoleWidget(QWidget):
|
|
|
55
72
|
top.addWidget(QLabel(self.language_word_dict_get("dynamic_console_shell")))
|
|
56
73
|
top.addWidget(self.shell_combo)
|
|
57
74
|
|
|
75
|
+
# 版面配置:中間 (輸入框 + 按鈕) / Layout: Middle (Input + Buttons)
|
|
58
76
|
mid = QHBoxLayout()
|
|
59
77
|
mid.addWidget(self.input, 1)
|
|
60
78
|
mid.addWidget(self.btn_run)
|
|
61
79
|
mid.addWidget(self.btn_stop)
|
|
62
80
|
mid.addWidget(self.btn_clear)
|
|
63
81
|
|
|
82
|
+
# 主版面配置 / Main layout
|
|
64
83
|
lay = QVBoxLayout(self)
|
|
65
84
|
lay.addLayout(top)
|
|
66
85
|
lay.addWidget(self.output, 1)
|
|
67
86
|
lay.addLayout(mid)
|
|
68
87
|
|
|
88
|
+
# 初始化狀態訊息 / Initial status message
|
|
69
89
|
self.proc.system.emit(self.language_word_dict_get("dynamic_console_ready"))
|
|
70
|
-
|
|
90
|
+
|
|
91
|
+
# 啟動互動式 shell / Start interactive shell
|
|
71
92
|
self.proc.start_shell(self.shell_combo.currentText())
|
|
72
93
|
|
|
94
|
+
# 事件過濾器:支援上下鍵瀏覽歷史指令
|
|
95
|
+
# Event filter: Support navigating command history with Up/Down keys
|
|
73
96
|
def eventFilter(self, obj, event):
|
|
74
97
|
if obj is self.input and isinstance(event, QKeyEvent) and event.type() == QEvent.Type.KeyPress:
|
|
75
98
|
if event.key() == Qt.Key.Key_Up:
|
|
@@ -80,12 +103,14 @@ class ConsoleWidget(QWidget):
|
|
|
80
103
|
return True
|
|
81
104
|
return super().eventFilter(obj, event)
|
|
82
105
|
|
|
106
|
+
# 瀏覽上一個歷史指令 / Navigate to previous command in history
|
|
83
107
|
def history_prev(self):
|
|
84
108
|
if not self.history: return
|
|
85
109
|
self.history_index = len(self.history) - 1 if self.history_index < 0 else max(0, self.history_index - 1)
|
|
86
110
|
self.input.setText(self.history[self.history_index])
|
|
87
111
|
self.input.end(False)
|
|
88
112
|
|
|
113
|
+
# 瀏覽下一個歷史指令 / Navigate to next command in history
|
|
89
114
|
def history_next(self):
|
|
90
115
|
if not self.history: return
|
|
91
116
|
if self.history_index < 0: return
|
|
@@ -97,6 +122,7 @@ class ConsoleWidget(QWidget):
|
|
|
97
122
|
self.input.setText(self.history[self.history_index])
|
|
98
123
|
self.input.end(False)
|
|
99
124
|
|
|
125
|
+
# 選擇新的工作目錄 / Pick a new working directory
|
|
100
126
|
def pick_cwd(self):
|
|
101
127
|
d = QFileDialog.getExistingDirectory(self, "Select working directory", os.getcwd())
|
|
102
128
|
if d:
|
|
@@ -104,16 +130,19 @@ class ConsoleWidget(QWidget):
|
|
|
104
130
|
self.cwd_label.setText(f"{self.language_word_dict_get('dynamic_console_cwd')}: {d}")
|
|
105
131
|
self.proc.system.emit(f'cd "{d}"')
|
|
106
132
|
|
|
133
|
+
# 在輸出區域新增文字 (支援顏色) / Append text to output area (with optional color)
|
|
107
134
|
def append_text(self, text, color=None):
|
|
108
135
|
cursor = self.output.textCursor()
|
|
109
136
|
cursor.movePosition(QTextCursor.MoveOperation.End)
|
|
110
137
|
fmt = self.output.currentCharFormat()
|
|
111
|
-
if color:
|
|
138
|
+
if color:
|
|
139
|
+
fmt.setForeground(QColor(color))
|
|
112
140
|
cursor.setCharFormat(fmt)
|
|
113
141
|
cursor.insertText(text)
|
|
114
142
|
self.output.setTextCursor(cursor)
|
|
115
143
|
self.output.ensureCursorVisible()
|
|
116
144
|
|
|
145
|
+
# 執行輸入的指令 / Run the entered command
|
|
117
146
|
def run_command(self):
|
|
118
147
|
cmd = self.input.text().strip()
|
|
119
148
|
if not cmd: return
|
|
@@ -124,7 +153,10 @@ class ConsoleWidget(QWidget):
|
|
|
124
153
|
self.proc.send_command(cmd)
|
|
125
154
|
self.input.clear()
|
|
126
155
|
|
|
156
|
+
# 子程序結束時的處理 / Handle process finished event
|
|
127
157
|
def on_finished(self, code, status):
|
|
128
|
-
self.append_text(
|
|
129
|
-
|
|
130
|
-
|
|
158
|
+
self.append_text(
|
|
159
|
+
f"\n{self.language_word_dict_get('dynamic_console_done').format(code=code, status=status)}\n",
|
|
160
|
+
"#888"
|
|
161
|
+
)
|
|
162
|
+
self.proc.system.emit(self.language_word_dict_get("dynamic_console_ready"))
|
|
@@ -4,60 +4,81 @@ from PySide6.QtCore import QObject, QProcess, Signal, QTimer
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class ConsoleProcessAdapter(QObject):
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
"""
|
|
8
|
+
ConsoleProcessAdapter 負責管理 QProcess,提供互動式 shell 的啟動、指令傳送與輸出監聽。
|
|
9
|
+
ConsoleProcessAdapter manages QProcess, providing interactive shell start, command sending, and output handling.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# 定義訊號 / Define signals
|
|
13
|
+
started = Signal() # 當子程序啟動時發射 / Emitted when process starts
|
|
14
|
+
finished = Signal(int, QProcess.ExitStatus) # 當子程序結束時發射 / Emitted when process finishes
|
|
15
|
+
stdout = Signal(str) # 標準輸出訊號 / Standard output signal
|
|
16
|
+
stderr = Signal(str) # 錯誤輸出訊號 / Standard error signal
|
|
17
|
+
system = Signal(str) # 系統訊息訊號 / System message signal
|
|
12
18
|
|
|
13
19
|
def __init__(self, parent=None):
|
|
14
20
|
super().__init__(parent)
|
|
21
|
+
# 建立 QProcess 物件 / Create QProcess object
|
|
15
22
|
self.proc = QProcess(self)
|
|
23
|
+
# 設定輸出通道分離 (stdout / stderr) / Separate stdout and stderr
|
|
16
24
|
self.proc.setProcessChannelMode(QProcess.ProcessChannelMode.SeparateChannels)
|
|
25
|
+
|
|
26
|
+
# 綁定事件處理函式 / Connect signals to handlers
|
|
17
27
|
self.proc.readyReadStandardOutput.connect(self._on_stdout)
|
|
18
28
|
self.proc.readyReadStandardError.connect(self._on_stderr)
|
|
19
29
|
self.proc.started.connect(self.started)
|
|
20
30
|
self.proc.finished.connect(self.finished)
|
|
21
31
|
|
|
32
|
+
# 設定工作目錄 / Set working directory
|
|
22
33
|
def set_cwd(self, path: str):
|
|
23
34
|
self.proc.setWorkingDirectory(path)
|
|
24
35
|
|
|
36
|
+
# 啟動 shell / Start shell
|
|
25
37
|
def start_shell(self, shell: str = "auto"):
|
|
26
38
|
if self.is_running():
|
|
27
|
-
self.system.emit("Shell already running")
|
|
39
|
+
self.system.emit("Shell already running") # 如果已經在執行,發送提示 / Emit message if already running
|
|
28
40
|
return
|
|
29
|
-
program, args = self._build_shell_command(shell)
|
|
30
|
-
self.proc.start(program, args)
|
|
41
|
+
program, args = self._build_shell_command(shell) # 建立 shell 指令 / Build shell command
|
|
42
|
+
self.proc.start(program, args) # 啟動子程序 / Start process
|
|
31
43
|
|
|
44
|
+
# Windows 特殊處理:設定 UTF-8 編碼 / Windows-specific: set UTF-8 encoding
|
|
32
45
|
if os.name == "nt":
|
|
33
46
|
QTimer.singleShot(500, lambda: self.send_command("chcp 65001"))
|
|
34
47
|
|
|
48
|
+
# 傳送指令到 shell / Send command to shell
|
|
35
49
|
def send_command(self, cmd: str):
|
|
36
50
|
if not self.is_running():
|
|
37
|
-
self.system.emit("Shell not running")
|
|
51
|
+
self.system.emit("Shell not running") # 如果 shell 未啟動,發送提示 / Emit message if not running
|
|
38
52
|
return
|
|
39
|
-
self.proc.write((cmd + "\n").encode("utf-8"))
|
|
53
|
+
self.proc.write((cmd + "\n").encode("utf-8")) # 傳送指令並換行 / Send command with newline
|
|
40
54
|
|
|
55
|
+
# 停止 shell / Stop shell
|
|
41
56
|
def stop(self):
|
|
42
57
|
if not self.is_running():
|
|
43
58
|
return
|
|
44
|
-
self.proc.terminate()
|
|
59
|
+
self.proc.terminate() # 嘗試正常結束 / Try graceful termination
|
|
60
|
+
# 如果 1 秒後仍在執行,強制 kill / Force kill if still running after 1s
|
|
45
61
|
QTimer.singleShot(1000, lambda: self.is_running() and self.proc.kill())
|
|
46
62
|
|
|
63
|
+
# 判斷是否正在執行 / Check if process is running
|
|
47
64
|
def is_running(self):
|
|
48
65
|
return self.proc.state() != QProcess.ProcessState.NotRunning
|
|
49
66
|
|
|
67
|
+
# 處理標準輸出 / Handle standard output
|
|
50
68
|
def _on_stdout(self):
|
|
51
69
|
self.stdout.emit(bytes(self.proc.readAllStandardOutput()).decode("utf-8", errors="replace"))
|
|
52
70
|
|
|
71
|
+
# 處理錯誤輸出 / Handle standard error
|
|
53
72
|
def _on_stderr(self):
|
|
54
73
|
self.stderr.emit(bytes(self.proc.readAllStandardError()).decode("utf-8", errors="replace"))
|
|
55
74
|
|
|
75
|
+
# 建立 shell 指令 / Build shell command
|
|
56
76
|
def _build_shell_command(self, shell: str):
|
|
57
77
|
if shell == "auto":
|
|
58
|
-
shell = "cmd" if os.name == "nt" else "bash"
|
|
78
|
+
shell = "cmd" if os.name == "nt" else "bash" # Windows 預設 cmd,Linux/macOS 預設 bash
|
|
59
79
|
if os.name == "nt":
|
|
60
80
|
if shell == "powershell":
|
|
61
81
|
return "powershell.exe", ["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass"]
|
|
62
82
|
return "cmd.exe", []
|
|
63
|
-
|
|
83
|
+
# Linux/macOS 預設 bash,否則使用 sh / Default bash, fallback to sh
|
|
84
|
+
return ("/bin/bash" if shell == "bash" else "/bin/sh"), []
|
|
@@ -5,15 +5,46 @@ from je_editor.utils.logging.loggin_instance import jeditor_logger
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class DestroyDock(QDockWidget):
|
|
8
|
+
"""
|
|
9
|
+
DestroyDock 繼承自 QDockWidget,主要用於建立一個可停駐的視窗,
|
|
10
|
+
並在關閉時自動釋放資源與記錄日誌。
|
|
11
|
+
|
|
12
|
+
DestroyDock inherits from QDockWidget, mainly used to create a dockable window,
|
|
13
|
+
and automatically release resources and log events when closed.
|
|
14
|
+
"""
|
|
8
15
|
|
|
9
16
|
def __init__(self):
|
|
17
|
+
# 初始化時記錄日誌 / Log initialization
|
|
10
18
|
jeditor_logger.info("Init DestroyDock")
|
|
11
19
|
super().__init__()
|
|
20
|
+
|
|
21
|
+
# 設定允許停駐的區域 (可停駐在所有邊)
|
|
22
|
+
# Allow docking in all areas (top, bottom, left, right)
|
|
12
23
|
self.setAllowedAreas(Qt.DockWidgetArea.AllDockWidgetAreas)
|
|
13
|
-
|
|
24
|
+
|
|
25
|
+
# 設定屬性:當視窗關閉時,自動刪除物件以釋放記憶體
|
|
26
|
+
# Set attribute: delete object on close to free memory
|
|
14
27
|
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
|
|
15
28
|
|
|
16
29
|
def closeEvent(self, event) -> None:
|
|
30
|
+
"""
|
|
31
|
+
覆寫 closeEvent,在關閉時額外處理:
|
|
32
|
+
- 記錄關閉事件
|
|
33
|
+
- 關閉內部 widget
|
|
34
|
+
- 呼叫父類別的 close()
|
|
35
|
+
|
|
36
|
+
Override closeEvent to:
|
|
37
|
+
- Log the close event
|
|
38
|
+
- Close the internal widget
|
|
39
|
+
- Call parent close()
|
|
40
|
+
"""
|
|
41
|
+
# 記錄關閉事件 / Log close event
|
|
17
42
|
jeditor_logger.info(f"DestroyDock closeEvent event: {event}")
|
|
43
|
+
|
|
44
|
+
# 關閉 dock 內部的 widget,確保資源釋放
|
|
45
|
+
# Close the internal widget to ensure resource release
|
|
18
46
|
self.widget().close()
|
|
19
|
-
|
|
47
|
+
|
|
48
|
+
# 呼叫父類別的 close(),完成 Qt 預設的關閉流程
|
|
49
|
+
# Call parent close() to complete default Qt closing process
|
|
50
|
+
super().close()
|
|
@@ -32,11 +32,24 @@ from je_editor.utils.file.open.open_file import read_file
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class EditorWidget(QWidget):
|
|
35
|
+
"""
|
|
36
|
+
EditorWidget 是主要的程式碼編輯器元件,包含:
|
|
37
|
+
- 專案檔案樹狀檢視
|
|
38
|
+
- 程式碼編輯區
|
|
39
|
+
- 執行結果、格式檢查、除錯輸出
|
|
40
|
+
- 自動儲存與檔案管理
|
|
41
|
+
|
|
42
|
+
EditorWidget is the main code editor widget, including:
|
|
43
|
+
- Project file tree view
|
|
44
|
+
- Code editing area
|
|
45
|
+
- Execution result, format check, debugger output
|
|
46
|
+
- Auto-save and file management
|
|
47
|
+
"""
|
|
35
48
|
|
|
36
49
|
def __init__(self, main_window: EditorMain):
|
|
37
50
|
jeditor_logger.info(f"Init EditorWidget main_window: {main_window}")
|
|
38
51
|
super().__init__()
|
|
39
|
-
# Init
|
|
52
|
+
# ---------------- Init variables 初始化變數 ----------------
|
|
40
53
|
self.checker: Union[PEP8FormatChecker, None] = None
|
|
41
54
|
self.current_file = None
|
|
42
55
|
self.tree_view_scroll_area = None
|
|
@@ -45,41 +58,51 @@ class EditorWidget(QWidget):
|
|
|
45
58
|
self.python_compiler = None
|
|
46
59
|
self.main_window = main_window
|
|
47
60
|
self.tab_manager = self.main_window.tab_widget
|
|
48
|
-
|
|
49
|
-
#
|
|
61
|
+
|
|
62
|
+
# 執行相關物件 / Execution related objects
|
|
50
63
|
self.exec_program: Union[None, ExecManager] = None
|
|
51
64
|
self.exec_shell: Union[None, ShellManager] = None
|
|
52
65
|
self.exec_python_debugger: Union[None, ExecManager] = None
|
|
53
|
-
|
|
66
|
+
|
|
67
|
+
# 自動儲存執行緒 / Auto-save thread
|
|
54
68
|
self.code_save_thread: Union[CodeEditSaveThread, None] = None
|
|
55
|
-
|
|
69
|
+
|
|
70
|
+
# ---------------- UI 初始化 ----------------
|
|
56
71
|
self.grid_layout = QGridLayout(self)
|
|
57
72
|
self.setWindowTitle(language_wrapper.language_word_dict.get("application_name"))
|
|
58
|
-
|
|
73
|
+
|
|
74
|
+
# 建立專案檔案樹狀檢視 / Setup project tree view
|
|
59
75
|
self.set_project_treeview()
|
|
60
|
-
|
|
76
|
+
|
|
77
|
+
# 主分割器 (左:檔案樹,右:編輯器) / Main splitter (left: tree, right: editor)
|
|
61
78
|
self.full_splitter = QSplitter()
|
|
62
79
|
self.full_splitter.setOrientation(Qt.Orientation.Horizontal)
|
|
63
|
-
|
|
80
|
+
|
|
81
|
+
# 編輯器分割器 (上:編輯器,下:輸出) / Editor splitter (top: editor, bottom: output)
|
|
64
82
|
self.edit_splitter = QSplitter(self.full_splitter)
|
|
65
83
|
self.edit_splitter.setOrientation(Qt.Orientation.Vertical)
|
|
66
|
-
|
|
84
|
+
|
|
85
|
+
# 程式碼編輯器與輸出區 / Code editor and result area
|
|
67
86
|
self.code_edit = CodeEditor(self)
|
|
68
87
|
self.code_result = CodeRecord()
|
|
69
88
|
self.code_result_cursor = self.code_result.textCursor()
|
|
89
|
+
|
|
90
|
+
# 捲動區包裝編輯器與輸出 / Scroll areas for editor and result
|
|
70
91
|
self.code_edit_scroll_area = QScrollArea()
|
|
71
92
|
self.code_edit_scroll_area.setWidgetResizable(True)
|
|
72
93
|
self.code_edit_scroll_area.setViewportMargins(0, 0, 0, 0)
|
|
73
94
|
self.code_edit_scroll_area.setWidget(self.code_edit)
|
|
95
|
+
|
|
74
96
|
self.code_result_scroll_area = QScrollArea()
|
|
75
97
|
self.code_result_scroll_area.setWidgetResizable(True)
|
|
76
98
|
self.code_result_scroll_area.setViewportMargins(0, 0, 0, 0)
|
|
77
99
|
self.code_result_scroll_area.setWidget(self.code_result)
|
|
78
|
-
|
|
100
|
+
|
|
101
|
+
# 格式檢查與除錯輸出 / Format check and debugger output
|
|
79
102
|
self.format_check_result = CodeRecord()
|
|
80
|
-
# Debugger
|
|
81
103
|
self.debugger_result = CodeRecord()
|
|
82
|
-
|
|
104
|
+
|
|
105
|
+
# 輸出分頁 (執行結果 / 格式檢查 / 除錯) / Output tabs
|
|
83
106
|
self.code_difference_result = QTabWidget()
|
|
84
107
|
self.code_difference_result.addTab(
|
|
85
108
|
self.code_result_scroll_area, language_wrapper.language_word_dict.get("editor_code_result"))
|
|
@@ -87,18 +110,21 @@ class EditorWidget(QWidget):
|
|
|
87
110
|
self.format_check_result, language_wrapper.language_word_dict.get("editor_format_check"))
|
|
88
111
|
self.code_difference_result.addTab(
|
|
89
112
|
self.debugger_result, language_wrapper.language_word_dict.get("editor_debugger_input_title_label"))
|
|
90
|
-
|
|
113
|
+
|
|
114
|
+
# 加入分割器 / Add widgets to splitters
|
|
91
115
|
self.edit_splitter.addWidget(self.code_edit_scroll_area)
|
|
92
116
|
self.edit_splitter.addWidget(self.code_difference_result)
|
|
93
117
|
self.edit_splitter.setStretchFactor(0, 3)
|
|
94
118
|
self.edit_splitter.setStretchFactor(1, 1)
|
|
95
119
|
self.edit_splitter.setSizes([300, 100])
|
|
120
|
+
|
|
96
121
|
self.full_splitter.addWidget(self.project_treeview)
|
|
97
122
|
self.full_splitter.addWidget(self.edit_splitter)
|
|
98
123
|
self.full_splitter.setStretchFactor(0, 1)
|
|
99
124
|
self.full_splitter.setStretchFactor(1, 3)
|
|
100
125
|
self.full_splitter.setSizes([100, 300])
|
|
101
|
-
|
|
126
|
+
|
|
127
|
+
# 設定字體樣式 / Set font style
|
|
102
128
|
self.code_edit.setStyleSheet(
|
|
103
129
|
f"font-size: {user_setting_dict.get('font_size', 12)}pt;"
|
|
104
130
|
f"font-family: {user_setting_dict.get('font', 'Lato')};"
|
|
@@ -107,15 +133,23 @@ class EditorWidget(QWidget):
|
|
|
107
133
|
f"font-size: {user_setting_dict.get('font_size', 12)}pt;"
|
|
108
134
|
f"font-family: {user_setting_dict.get('font', 'Lato')};"
|
|
109
135
|
)
|
|
110
|
-
|
|
136
|
+
|
|
137
|
+
# 加入主版面配置 / Add to main layout
|
|
111
138
|
self.grid_layout.addWidget(self.full_splitter)
|
|
112
139
|
|
|
140
|
+
# ---------------- Project Treeview ----------------
|
|
113
141
|
def set_project_treeview(self) -> None:
|
|
142
|
+
"""
|
|
143
|
+
建立並設定專案檔案樹狀檢視
|
|
144
|
+
Setup and configure project file tree view
|
|
145
|
+
"""
|
|
114
146
|
jeditor_logger.info("EditorWidget set_project_treeview")
|
|
115
147
|
self.project_treeview_model = QFileSystemModel()
|
|
116
148
|
self.project_treeview_model.setRootPath(QDir.currentPath())
|
|
117
149
|
self.project_treeview = QTreeView()
|
|
118
150
|
self.project_treeview.setModel(self.project_treeview_model)
|
|
151
|
+
|
|
152
|
+
# 設定根目錄 (工作目錄或當前路徑) / Set root directory (working dir or current path)
|
|
119
153
|
if self.main_window.working_dir is None:
|
|
120
154
|
self.project_treeview.setRootIndex(
|
|
121
155
|
self.project_treeview_model.index(str(Path.cwd()))
|
|
@@ -124,53 +158,87 @@ class EditorWidget(QWidget):
|
|
|
124
158
|
self.project_treeview.setRootIndex(
|
|
125
159
|
self.project_treeview_model.index(self.main_window.working_dir)
|
|
126
160
|
)
|
|
161
|
+
|
|
162
|
+
# 包裝成可捲動區域 / Wrap in scroll area
|
|
127
163
|
self.tree_view_scroll_area = QScrollArea()
|
|
128
164
|
self.tree_view_scroll_area.setWidgetResizable(True)
|
|
129
165
|
self.tree_view_scroll_area.setViewportMargins(0, 0, 0, 0)
|
|
130
166
|
self.tree_view_scroll_area.setWidget(self.project_treeview)
|
|
131
167
|
self.grid_layout.addWidget(self.tree_view_scroll_area, 0, 0, 0, 1)
|
|
168
|
+
|
|
169
|
+
# 點擊檔案時觸發 / Connect click event
|
|
132
170
|
self.project_treeview.clicked.connect(self.treeview_click)
|
|
133
171
|
|
|
134
172
|
def check_is_open(self, path: Path):
|
|
173
|
+
"""
|
|
174
|
+
檢查檔案是否已經開啟,如果已開啟則切換到該分頁。
|
|
175
|
+
Check if the file is already open, if yes then switch to that tab.
|
|
176
|
+
"""
|
|
135
177
|
jeditor_logger.info(f"EditorWidget check_is_open path: {path}")
|
|
136
178
|
if file_is_open_manager_dict.get(str(path), None) is not None:
|
|
179
|
+
# 嘗試在分頁中找到對應的 EditorWidget
|
|
180
|
+
# Try to find the corresponding EditorWidget in tab manager
|
|
137
181
|
widget: QWidget = self.tab_manager.findChild(EditorWidget, str(path))
|
|
138
182
|
if widget is None:
|
|
183
|
+
# 如果找不到,代表之前的紀錄失效,移除紀錄
|
|
184
|
+
# If not found, remove stale record
|
|
139
185
|
file_is_open_manager_dict.pop(str(path), None)
|
|
140
186
|
else:
|
|
187
|
+
# 如果找到,直接切換到該分頁
|
|
188
|
+
# If found, switch to that tab
|
|
141
189
|
self.tab_manager.setCurrentWidget(widget)
|
|
142
190
|
return False
|
|
143
191
|
else:
|
|
192
|
+
# 如果檔案未開啟,加入紀錄
|
|
193
|
+
# If file not open, add to open manager dict
|
|
144
194
|
file_is_open_manager_dict.update({str(path): str(path)})
|
|
145
195
|
return True
|
|
146
196
|
|
|
147
197
|
def open_an_file(self, path: Path) -> bool:
|
|
148
198
|
"""
|
|
149
|
-
|
|
150
|
-
|
|
199
|
+
開啟檔案並載入到編輯器。
|
|
200
|
+
Open a file and load it into the editor.
|
|
201
|
+
|
|
202
|
+
:param path: 檔案路徑 / File path
|
|
203
|
+
:return: 如果檔案已經開啟則回傳 False / Return False if file tab already exists
|
|
151
204
|
"""
|
|
152
205
|
jeditor_logger.info(f"EditorWidget open_an_file path: {path}")
|
|
153
206
|
if not self.check_is_open(path):
|
|
154
207
|
return False
|
|
208
|
+
|
|
209
|
+
# 如果有自動儲存執行緒,暫時跳過這一輪
|
|
210
|
+
# If auto-save thread exists, skip this round
|
|
155
211
|
if self.code_save_thread:
|
|
156
212
|
self.code_save_thread.skip_this_round = True
|
|
213
|
+
|
|
214
|
+
# 讀取檔案內容 / Read file content
|
|
157
215
|
file, file_content = read_file(str(path))
|
|
158
|
-
self.code_edit.setPlainText(
|
|
159
|
-
|
|
160
|
-
|
|
216
|
+
self.code_edit.setPlainText(file_content)
|
|
217
|
+
|
|
218
|
+
# 更新目前檔案資訊 / Update current file info
|
|
161
219
|
self.current_file = file
|
|
162
220
|
self.code_edit.current_file = file
|
|
163
221
|
self.code_edit.reset_highlighter()
|
|
222
|
+
|
|
223
|
+
# 更新使用者設定中的最後開啟檔案 / Update last opened file in user settings
|
|
164
224
|
user_setting_dict.update({"last_file": str(self.current_file)})
|
|
225
|
+
|
|
226
|
+
# 啟動或更新自動儲存執行緒 / Start or update auto-save thread
|
|
165
227
|
if self.current_file is not None and self.code_save_thread is None:
|
|
166
228
|
init_new_auto_save_thread(self.current_file, self)
|
|
167
229
|
else:
|
|
168
230
|
self.code_save_thread.file = self.current_file
|
|
169
231
|
self.code_save_thread.skip_this_round = False
|
|
232
|
+
|
|
233
|
+
# 更新分頁標籤名稱 / Update tab title
|
|
170
234
|
self.rename_self_tab()
|
|
171
235
|
return True
|
|
172
236
|
|
|
173
237
|
def treeview_click(self) -> None:
|
|
238
|
+
"""
|
|
239
|
+
當使用者點擊檔案樹中的項目時觸發。
|
|
240
|
+
Triggered when user clicks an item in the project tree view.
|
|
241
|
+
"""
|
|
174
242
|
jeditor_logger.info("EditorWidget treeview_click")
|
|
175
243
|
clicked_item: QFileSystemModel = self.project_treeview.selectedIndexes()[0]
|
|
176
244
|
file_info: QFileInfo = self.project_treeview.model().fileInfo(clicked_item)
|
|
@@ -179,6 +247,10 @@ class EditorWidget(QWidget):
|
|
|
179
247
|
self.open_an_file(path)
|
|
180
248
|
|
|
181
249
|
def rename_self_tab(self):
|
|
250
|
+
"""
|
|
251
|
+
將目前分頁的標籤名稱改為目前檔案名稱。
|
|
252
|
+
Rename the current tab to the current file name.
|
|
253
|
+
"""
|
|
182
254
|
jeditor_logger.info("EditorWidget rename_self_tab")
|
|
183
255
|
if self.tab_manager.currentWidget() is self:
|
|
184
256
|
self.tab_manager.setTabText(
|
|
@@ -186,13 +258,20 @@ class EditorWidget(QWidget):
|
|
|
186
258
|
self.setObjectName(str(Path(self.current_file)))
|
|
187
259
|
|
|
188
260
|
def check_file_format(self):
|
|
261
|
+
"""
|
|
262
|
+
檢查目前檔案的程式碼格式 (僅支援 Python)。
|
|
263
|
+
Check the code format of the current file (only supports Python).
|
|
264
|
+
"""
|
|
189
265
|
if self.current_file:
|
|
190
266
|
jeditor_logger.info("EditorWidget check_file_format")
|
|
191
267
|
suffix_checker = Path(self.current_file).suffix
|
|
192
268
|
if suffix_checker == ".py":
|
|
269
|
+
# 使用 PEP8 格式檢查器 / Use PEP8 format checker
|
|
193
270
|
self.checker = PEP8FormatChecker(self.current_file)
|
|
194
271
|
self.checker.check_all_format()
|
|
195
272
|
self.format_check_result.setPlainText("")
|
|
273
|
+
|
|
274
|
+
# 顯示錯誤訊息並套用顏色 / Display errors with color formatting
|
|
196
275
|
for error in self.checker.error_list:
|
|
197
276
|
text_cursor = self.format_check_result.textCursor()
|
|
198
277
|
text_format = QTextCharFormat()
|
|
@@ -201,12 +280,17 @@ class EditorWidget(QWidget):
|
|
|
201
280
|
text_cursor.insertBlock()
|
|
202
281
|
self.checker.error_list.clear()
|
|
203
282
|
else:
|
|
283
|
+
# 非 Python 檔案,顯示提示訊息 / Show message if not Python file
|
|
204
284
|
message_box = QMessageBox()
|
|
205
285
|
message_box.setText(
|
|
206
286
|
language_wrapper.language_word_dict.get("python_format_checker_only_support_python_message"))
|
|
207
287
|
message_box.exec_()
|
|
208
288
|
|
|
209
289
|
def close(self) -> bool:
|
|
290
|
+
"""
|
|
291
|
+
關閉編輯器,釋放資源並移除自動儲存紀錄。
|
|
292
|
+
Close the editor, release resources, and remove auto-save records.
|
|
293
|
+
"""
|
|
210
294
|
jeditor_logger.info("EditorWidget close")
|
|
211
295
|
if self.code_save_thread is not None:
|
|
212
296
|
self.code_save_thread.still_run = False
|