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