je-editor 0.0.222__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.

Files changed (75) hide show
  1. je_editor/__init__.py +2 -2
  2. je_editor/git_client/commit_graph.py +7 -7
  3. je_editor/git_client/git_action.py +0 -7
  4. je_editor/pyside_ui/browser/browser_view.py +16 -4
  5. je_editor/pyside_ui/browser/browser_widget.py +43 -29
  6. je_editor/pyside_ui/browser/main_browser_widget.py +85 -0
  7. je_editor/pyside_ui/code/auto_save/auto_save_manager.py +34 -2
  8. je_editor/pyside_ui/code/auto_save/auto_save_thread.py +19 -6
  9. je_editor/pyside_ui/code/code_format/pep8_format.py +53 -9
  10. je_editor/pyside_ui/code/code_process/code_exec.py +88 -52
  11. je_editor/pyside_ui/code/plaintext_code_edit/code_edit_plaintext.py +116 -55
  12. je_editor/pyside_ui/code/running_process_manager.py +19 -1
  13. je_editor/pyside_ui/code/shell_process/shell_exec.py +71 -48
  14. je_editor/pyside_ui/code/syntax/python_syntax.py +45 -10
  15. je_editor/pyside_ui/code/syntax/syntax_setting.py +40 -12
  16. je_editor/pyside_ui/code/textedit_code_result/code_record.py +34 -12
  17. je_editor/pyside_ui/code/variable_inspector/inspector_gui.py +53 -6
  18. je_editor/pyside_ui/dialog/ai_dialog/set_ai_dialog.py +30 -3
  19. je_editor/pyside_ui/dialog/file_dialog/create_file_dialog.py +35 -2
  20. je_editor/pyside_ui/dialog/file_dialog/open_file_dialog.py +33 -5
  21. je_editor/pyside_ui/dialog/file_dialog/save_file_dialog.py +25 -3
  22. je_editor/pyside_ui/dialog/search_ui/search_error_box.py +26 -1
  23. je_editor/pyside_ui/dialog/search_ui/search_text_box.py +26 -1
  24. je_editor/pyside_ui/git_ui/code_diff_compare/code_diff_viewer_widget.py +11 -11
  25. je_editor/pyside_ui/git_ui/git_client/commit_table.py +46 -8
  26. je_editor/pyside_ui/git_ui/git_client/git_branch_tree_widget.py +49 -15
  27. je_editor/pyside_ui/git_ui/git_client/git_client_gui.py +81 -16
  28. je_editor/pyside_ui/git_ui/git_client/graph_view.py +64 -20
  29. je_editor/pyside_ui/main_ui/ai_widget/ai_config.py +20 -5
  30. je_editor/pyside_ui/main_ui/ai_widget/ask_thread.py +20 -1
  31. je_editor/pyside_ui/main_ui/ai_widget/chat_ui.py +56 -41
  32. je_editor/pyside_ui/main_ui/ai_widget/langchain_interface.py +45 -6
  33. je_editor/pyside_ui/main_ui/console_widget/console_gui.py +44 -12
  34. je_editor/pyside_ui/main_ui/console_widget/qprocess_adapter.py +34 -13
  35. je_editor/pyside_ui/main_ui/dock/destroy_dock.py +33 -2
  36. je_editor/pyside_ui/main_ui/editor/editor_widget.py +104 -20
  37. je_editor/pyside_ui/main_ui/editor/editor_widget_dock.py +34 -7
  38. je_editor/pyside_ui/main_ui/editor/process_input.py +38 -11
  39. je_editor/pyside_ui/main_ui/ipython_widget/rich_jupyter.py +46 -11
  40. je_editor/pyside_ui/main_ui/main_editor.py +180 -42
  41. je_editor/pyside_ui/main_ui/menu/check_style_menu/build_check_style_menu.py +51 -28
  42. je_editor/pyside_ui/main_ui/menu/dock_menu/build_dock_menu.py +83 -36
  43. je_editor/pyside_ui/main_ui/menu/file_menu/build_file_menu.py +70 -17
  44. je_editor/pyside_ui/main_ui/menu/help_menu/build_help_menu.py +35 -4
  45. je_editor/pyside_ui/main_ui/menu/language_menu/build_language_server.py +41 -1
  46. je_editor/pyside_ui/main_ui/menu/python_env_menu/build_venv_menu.py +100 -42
  47. je_editor/pyside_ui/main_ui/menu/run_menu/build_run_menu.py +57 -7
  48. je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_debug_menu.py +50 -4
  49. je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_program_menu.py +52 -6
  50. je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/build_shell_menu.py +44 -4
  51. je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/utils.py +23 -1
  52. je_editor/pyside_ui/main_ui/menu/set_menu_bar.py +37 -12
  53. je_editor/pyside_ui/main_ui/menu/style_menu/build_style_menu.py +44 -7
  54. je_editor/pyside_ui/main_ui/menu/tab_menu/build_tab_menu.py +127 -44
  55. je_editor/pyside_ui/main_ui/menu/text_menu/build_text_menu.py +65 -1
  56. je_editor/pyside_ui/main_ui/save_settings/setting_utils.py +18 -1
  57. je_editor/pyside_ui/main_ui/save_settings/user_color_setting_file.py +33 -3
  58. je_editor/pyside_ui/main_ui/save_settings/user_setting_file.py +38 -11
  59. je_editor/pyside_ui/main_ui/system_tray/extend_system_tray.py +39 -2
  60. je_editor/start_editor.py +26 -1
  61. je_editor/utils/encodings/python_encodings.py +101 -98
  62. je_editor/utils/file/open/open_file.py +36 -19
  63. je_editor/utils/file/save/save_file.py +35 -14
  64. je_editor/utils/json/json_file.py +29 -14
  65. je_editor/utils/json_format/json_process.py +33 -2
  66. je_editor/utils/logging/loggin_instance.py +38 -8
  67. je_editor/utils/multi_language/multi_language_wrapper.py +29 -4
  68. je_editor/utils/redirect_manager/redirect_manager_class.py +49 -11
  69. je_editor/utils/venv_check/check_venv.py +45 -15
  70. {je_editor-0.0.222.dist-info → je_editor-0.0.224.dist-info}/METADATA +1 -1
  71. {je_editor-0.0.222.dist-info → je_editor-0.0.224.dist-info}/RECORD +74 -74
  72. je_editor/git_client/github.py +0 -81
  73. {je_editor-0.0.222.dist-info → je_editor-0.0.224.dist-info}/WHEEL +0 -0
  74. {je_editor-0.0.222.dist-info → je_editor-0.0.224.dist-info}/licenses/LICENSE +0 -0
  75. {je_editor-0.0.222.dist-info → je_editor-0.0.224.dist-info}/top_level.txt +0 -0
@@ -2,18 +2,33 @@ from queue import Queue
2
2
 
3
3
 
4
4
  class AIConfig(object):
5
+ """
6
+ AIConfig 類別:用來管理 AI 模型的設定與訊息佇列
7
+ AIConfig class: manages AI model configuration and message queue
8
+ """
5
9
 
6
10
  def __init__(self):
11
+ # 當前 AI 模型的系統提示詞 (system prompt)
12
+ # Current AI model system prompt
7
13
  self.current_ai_model_system_prompt: str = ""
14
+
15
+ # 可選擇的 AI 模型設定字典
16
+ # Dictionary of choosable AI model configurations
17
+ # 結構: { "AI_model": { "ai_base_url": ..., "ai_api_key": ..., "chat_model": ..., "prompt_template": ... } }
8
18
  self.choosable_ai: dict[str, dict[str, str]] = {
9
19
  "AI_model": {
10
- "ai_base_url": "",
11
- "ai_api_key": "",
12
- "chat_model": "",
13
- "prompt_template": "",
20
+ "ai_base_url": "", # AI 服務的基礎 URL / Base URL of AI service
21
+ "ai_api_key": "", # API 金鑰 / API key
22
+ "chat_model": "", # 模型名稱 / Model name
23
+ "prompt_template": "", # 提示詞模板 / Prompt template
14
24
  }
15
25
  }
26
+
27
+ # 訊息佇列,用來暫存與 AI 的互動訊息
28
+ # Message queue for storing AI interaction messages
16
29
  self.message_queue = Queue()
17
30
 
18
31
 
19
- ai_config = AIConfig()
32
+ # 建立全域唯一的 AIConfig 實例
33
+ # Create a global singleton instance of AIConfig
34
+ ai_config = AIConfig()
@@ -5,13 +5,32 @@ from je_editor.pyside_ui.main_ui.ai_widget.langchain_interface import LangChainI
5
5
 
6
6
 
7
7
  class AskThread(Thread):
8
+ """
9
+ AskThread 類別:用來在背景執行緒中呼叫 AI 模型,避免阻塞主執行緒
10
+ AskThread class: runs AI model calls in a background thread to avoid blocking the main thread
11
+ """
8
12
 
9
13
  def __init__(self, lang_chain_interface: LangChainInterface, prompt):
14
+ """
15
+ 初始化 AskThread
16
+ Initialize AskThread
17
+ :param lang_chain_interface: LangChainInterface 實例,用來呼叫 AI 模型
18
+ LangChainInterface instance for calling AI model
19
+ :param prompt: 傳給 AI 模型的提示詞
20
+ Prompt to send to the AI model
21
+ """
10
22
  super().__init__()
11
23
  self.lang_chain_interface = lang_chain_interface
12
24
  self.prompt = prompt
13
25
 
14
-
15
26
  def run(self):
27
+ """
28
+ 執行緒的主要邏輯:
29
+ 1. 呼叫 AI 模型並取得回應
30
+ 2. 將回應放入 ai_config 的 message_queue 中,供主程式使用
31
+ Thread main logic:
32
+ 1. Call AI model and get response
33
+ 2. Put response into ai_config.message_queue for main program to consume
34
+ """
16
35
  ai_response = self.lang_chain_interface.call_ai_model(prompt=self.prompt)
17
36
  ai_config.message_queue.put(ai_response)
@@ -23,73 +23,86 @@ class ChatUI(QWidget):
23
23
 
24
24
  def __init__(self, main_window: EditorMain):
25
25
  super().__init__()
26
- self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
26
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) # 關閉視窗時自動釋放資源 / Auto delete on close
27
27
  self.main_window = main_window
28
- # Chat panel
29
- self.chat_panel = QPlainTextEdit()
30
- self.chat_panel.setLineWrapMode(self.chat_panel.LineWrapMode.NoWrap)
31
- self.chat_panel.setReadOnly(True)
32
- self.chat_panel_scroll_area = QScrollArea()
28
+
29
+ # ---------------- Chat Panel 聊天面板 ----------------
30
+ self.chat_panel = QPlainTextEdit() # 顯示聊天訊息的文字框 / Text area for chat messages
31
+ self.chat_panel.setLineWrapMode(self.chat_panel.LineWrapMode.NoWrap) # 不自動換行 / Disable line wrap
32
+ self.chat_panel.setReadOnly(True) # 設為唯讀,避免使用者直接輸入 / Read-only
33
+ self.chat_panel_scroll_area = QScrollArea() # 加入滾動區域 / Scroll area for chat panel
33
34
  self.chat_panel_scroll_area.setWidgetResizable(True)
34
35
  self.chat_panel_scroll_area.setViewportMargins(0, 0, 0, 0)
35
36
  self.chat_panel_scroll_area.setWidget(self.chat_panel)
36
- self.chat_panel.setFont(QFontDatabase.font(self.font().family(), "", 16))
37
- # Prompt input
38
- self.prompt_input = QLineEdit()
37
+ self.chat_panel.setFont(QFontDatabase.font(self.font().family(), "", 16)) # 設定字體大小 / Set font size
38
+
39
+ # ---------------- Prompt Input 輸入框 ----------------
40
+ self.prompt_input = QLineEdit() # 使用者輸入提示詞 / Input field for prompts
39
41
  self.prompt_input.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
40
- self.prompt_input.returnPressed.connect(self.call_ai_model)
41
- # Font size combobox
42
- self.font_size_label = QLabel(language_wrapper.language_word_dict.get("font_size"))
43
- self.font_size_combobox = QComboBox()
44
- for font_size in range(2, 101, 2):
42
+ self.prompt_input.returnPressed.connect(self.call_ai_model) # 按 Enter 時呼叫 AI / Call AI on Enter
43
+
44
+ # ---------------- Font Size Combobox 字體大小選單 ----------------
45
+ self.font_size_label = QLabel(language_wrapper.language_word_dict.get("font_size")) # 標籤 / Label
46
+ self.font_size_combobox = QComboBox() # 下拉選單 / Dropdown for font size
47
+ for font_size in range(2, 101, 2): # 提供 2~100 的字體大小選項 / Font size options
45
48
  self.font_size_combobox.addItem(str(font_size))
46
- self.font_size_combobox.setCurrentText("16")
49
+ self.font_size_combobox.setCurrentText("16") # 預設字體大小 / Default font size
47
50
  self.font_size_combobox.currentTextChanged.connect(self.update_panel_text_size)
48
- # Buttons
51
+
52
+ # ---------------- Buttons 按鈕 ----------------
49
53
  self.set_ai_config_button = QPushButton(language_wrapper.language_word_dict.get("chat_ui_set_ai_button"))
50
- self.set_ai_config_button.clicked.connect(self.set_ai_config)
54
+ self.set_ai_config_button.clicked.connect(self.set_ai_config) # 開啟 AI 設定視窗 / Open AI config dialog
55
+
51
56
  self.load_ai_config_button = QPushButton(language_wrapper.language_word_dict.get("chat_ui_load_ai_button"))
52
- self.load_ai_config_button.clicked.connect(lambda: self.load_ai_config(show_load_complete=True))
57
+ self.load_ai_config_button.clicked.connect(lambda: self.load_ai_config(show_load_complete=True)) # 載入設定 / Load config
58
+
53
59
  self.call_ai_model_button = QPushButton(language_wrapper.language_word_dict.get("chat_ui_call_ai_model_button"))
54
- self.call_ai_model_button.clicked.connect(self.call_ai_model)
55
- # Add to layout
60
+ self.call_ai_model_button.clicked.connect(self.call_ai_model) # 呼叫 AI / Call AI
61
+
62
+ # ---------------- Layout 版面配置 ----------------
56
63
  self.grid_layout = QGridLayout()
57
- self.grid_layout.addWidget(self.chat_panel_scroll_area, 0, 0, 1, 4)
58
- self.grid_layout.addWidget(self.call_ai_model_button, 1, 0)
59
- self.grid_layout.addWidget(self.font_size_combobox, 1, 1)
60
- self.grid_layout.addWidget(self.set_ai_config_button, 1, 2)
61
- self.grid_layout.addWidget(self.load_ai_config_button, 1, 3)
62
- self.grid_layout.addWidget(self.prompt_input, 2, 0, 1, 4)
63
-
64
- # Variable
65
- self.ai_config: AIConfig = ai_config
66
- self.lang_chain_interface: Union[LangChainInterface, None] = None
67
- self.set_ai_config_dialog = None
68
- # Timer to pop queue
69
- self.pull_message_timer = QTimer(self)
70
- self.pull_message_timer.setInterval(1000)
64
+ self.grid_layout.addWidget(self.chat_panel_scroll_area, 0, 0, 1, 4) # 聊天面板 / Chat panel
65
+ self.grid_layout.addWidget(self.call_ai_model_button, 1, 0) # 呼叫 AI 按鈕 / Call AI button
66
+ self.grid_layout.addWidget(self.font_size_combobox, 1, 1) # 字體大小選單 / Font size combobox
67
+ self.grid_layout.addWidget(self.set_ai_config_button, 1, 2) # 設定 AI 按鈕 / Set AI config button
68
+ self.grid_layout.addWidget(self.load_ai_config_button, 1, 3) # 載入設定按鈕 / Load AI config button
69
+ self.grid_layout.addWidget(self.prompt_input, 2, 0, 1, 4) # 輸入框 / Prompt input
70
+
71
+ # ---------------- Variables 變數 ----------------
72
+ self.ai_config: AIConfig = ai_config # AI 設定物件 / AI config object
73
+ self.lang_chain_interface: Union[LangChainInterface, None] = None # LangChain 介面 / LangChain interface
74
+ self.set_ai_config_dialog = None # 設定對話框 / Config dialog
75
+
76
+ # ---------------- Timer 計時器 ----------------
77
+ self.pull_message_timer = QTimer(self) # 定時檢查訊息佇列 / Timer to pull messages
78
+ self.pull_message_timer.setInterval(1000) # 每秒檢查一次 / Check every 1 second
71
79
  self.pull_message_timer.timeout.connect(self.pull_message)
72
80
  self.pull_message_timer.start()
73
81
 
74
- # Set layout
82
+ # ---------------- Set Layout 設定版面 ----------------
75
83
  self.setLayout(self.grid_layout)
76
84
 
85
+ # ---------------- Load AI Config 載入 AI 設定 ----------------
77
86
  self.load_ai_config()
78
87
 
88
+ # 更新聊天面板字體大小 / Update chat panel font size
79
89
  def update_panel_text_size(self):
80
90
  self.chat_panel.setFont(
81
91
  QFontDatabase.font(self.font().family(), "", int(self.font_size_combobox.currentText())))
82
92
 
93
+ # 載入 AI 設定檔 / Load AI configuration file
83
94
  def load_ai_config(self, show_load_complete: bool = False):
84
95
  ai_config_file = Path(str(Path.cwd()) + "/" + ".jeditor/ai_config.json")
85
96
  if ai_config_file.exists():
86
97
  with open(ai_config_file, "r", encoding="utf-8"):
87
98
  json_data: dict = read_json(str(ai_config_file))
88
99
  if json_data:
100
+ # 確認 AI_model 設定存在且格式正確 / Ensure AI_model config exists and valid
89
101
  if json_data.get("AI_model") and len(json_data.get("AI_model")) == 4:
90
102
  ai_info: dict = json_data.get("AI_model")
91
103
  if ai_info.get("ai_base_url") and ai_info.get("chat_model"):
92
- ai_config.choosable_ai.update(json_data)
104
+ ai_config.choosable_ai.update(json_data) # 更新全域設定 / Update global config
105
+ # 建立 LangChain 介面 / Initialize LangChain interface
93
106
  self.lang_chain_interface = LangChainInterface(
94
107
  main_window=self,
95
108
  api_key=ai_info.get("ai_api_key"),
@@ -103,12 +116,14 @@ class ChatUI(QWidget):
103
116
  load_complete.setText(language_wrapper.language_word_dict.get("load_ai_messagebox_text"))
104
117
  load_complete.exec()
105
118
 
106
-
119
+ # 呼叫 AI 模型 / Call AI model
107
120
  def call_ai_model(self):
108
121
  if isinstance(self.lang_chain_interface, LangChainInterface):
122
+ # 建立新執行緒處理 AI 請求 / Start a new thread for AI request
109
123
  thread = AskThread(lang_chain_interface=self.lang_chain_interface, prompt=self.prompt_input.text())
110
124
  thread.start()
111
125
  else:
126
+ # 若未正確設定 AI,顯示錯誤訊息 / Show error if AI not configured
112
127
  ai_info = ai_config.choosable_ai.get('AI_model')
113
128
  QMessageBox.warning(self,
114
129
  language_wrapper.language_word_dict.get("call_ai_model_error_title"),
@@ -118,13 +133,13 @@ class ChatUI(QWidget):
118
133
  f"chat_model: {ai_info.get('chat_model')}, \n"
119
134
  f"prompt_template: {ai_info.get('prompt_template')}"))
120
135
 
136
+ # 從訊息佇列中取出 AI 回覆並顯示 / Pull AI response from queue
121
137
  def pull_message(self):
122
138
  if not ai_config.message_queue.empty():
123
139
  ai_response = ai_config.message_queue.get_nowait()
124
- self.chat_panel.appendPlainText(ai_response)
140
+ self.chat_panel.appendPlainText(ai_response) # 顯示回覆 / Display response
125
141
  self.chat_panel.appendPlainText("\n")
126
142
 
143
+ # 開啟 AI 設定對話框 / Open AI config dialog
127
144
  def set_ai_config(self):
128
- # Set and output AI a config file
129
- self.set_ai_config_dialog = SetAIDialog()
130
- self.set_ai_config_dialog.show()
145
+ self.set_ai_config
@@ -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
- def __init__(self, main_window: ChatUI, prompt_template: str, base_url: str, api_key: Union[SecretStr, str],
20
- chat_model: str):
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
- QMessageBox.warning(self.main_window,
43
- language_wrapper.language_word_dict.get("call_ai_model_error_title"),
44
- str(error))
45
- return message
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
- # 啟動互動式 shell
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: fmt.setForeground(QColor(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(f"\n{self.language_word_dict_get('dynamic_console_done').format(code=code, status=status)}\n",
129
- "#888")
130
- self.proc.system.emit(self.language_word_dict_get("dynamic_console_ready"))
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
- started = Signal()
8
- finished = Signal(int, QProcess.ExitStatus)
9
- stdout = Signal(str)
10
- stderr = Signal(str)
11
- system = Signal(str)
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
- return ("/bin/bash" if shell == "bash" else "/bin/sh"), []
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()
19
- super().close()
47
+
48
+ # 呼叫父類別的 close(),完成 Qt 預設的關閉流程
49
+ # Call parent close() to complete default Qt closing process
50
+ super().close()