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,172 @@
1
+ import ast
2
+
3
+ from PySide6.QtCore import QAbstractTableModel, Qt, QTimer, QSortFilterProxyModel
4
+ from PySide6.QtWidgets import QTableView, QVBoxLayout, QWidget, QLineEdit, QLabel
5
+
6
+ from je_editor.utils.multi_language.multi_language_wrapper import language_wrapper
7
+
8
+
9
+ class VariableModel(QAbstractTableModel):
10
+ """
11
+ 變數模型:負責管理與顯示 Python 全域變數
12
+ Variable model: manages and displays Python global variables
13
+ """
14
+
15
+ def __init__(self, parent=None):
16
+ super().__init__(parent)
17
+ self.variables = [] # 儲存變數資訊 [名稱, 型別, 值字串, 真實值]
18
+ # Store variable info [name, type, repr(value), actual value]
19
+
20
+ def update_data(self):
21
+ """
22
+ 更新變數清單,從全域變數中擷取
23
+ Update variable list from globals()
24
+ """
25
+ parent_widget = self.parent()
26
+ # 避免在 table 正在互動時更新,造成衝突
27
+ # Avoid updating while table is in active state
28
+ if parent_widget and getattr(parent_widget, "table", None):
29
+ if parent_widget.table.state() != QTableView.State.NoState:
30
+ return
31
+
32
+ vars_dict = globals()
33
+ self.beginResetModel()
34
+ self.variables = [
35
+ [name, type(value).__name__, repr(value), value]
36
+ for name, value in vars_dict.items()
37
+ if not name.startswith("__") # 過濾內建變數 / filter out built-in variables
38
+ ]
39
+ self.endResetModel()
40
+
41
+ def rowCount(self, parent=None):
42
+ # 回傳變數數量 / return number of variables
43
+ return len(self.variables)
44
+
45
+ def columnCount(self, parent=None):
46
+ # 固定三欄:名稱、型別、值 / fixed 3 columns: name, type, value
47
+ return 3
48
+
49
+ def data(self, index, role=Qt.ItemDataRole.DisplayRole):
50
+ # 提供表格顯示的資料
51
+ # Provide data for table display
52
+ if not index.isValid() or not (0 <= index.row() < len(self.variables)):
53
+ return None
54
+ if role == Qt.ItemDataRole.DisplayRole:
55
+ return self.variables[index.row()][index.column()]
56
+ return None
57
+
58
+ def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
59
+ # 設定表頭文字 (支援多語系)
60
+ # Set header labels (multi-language support)
61
+ if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
62
+ return [
63
+ language_wrapper.language_word_dict.get("variable_inspector_var_name"),
64
+ language_wrapper.language_word_dict.get("variable_inspector_var_type"),
65
+ language_wrapper.language_word_dict.get("variable_inspector_var_value")
66
+ ][section]
67
+ return None
68
+
69
+ def flags(self, index):
70
+ # 設定欄位屬性,僅允許「值」欄可編輯
71
+ # Set column flags, only "value" column is editable
72
+ if not index.isValid() or not (0 <= index.row() < len(self.variables)):
73
+ return Qt.ItemFlag.NoItemFlags
74
+ base = super().flags(index)
75
+ if index.column() == 2: # 只允許編輯值欄 / only value column editable
76
+ base |= Qt.ItemFlag.ItemIsEditable
77
+ return base
78
+
79
+ def setData(self, index, value, role=Qt.ItemDataRole.EditRole):
80
+ """
81
+ 更新變數值,並同步到全域變數
82
+ Update variable value and sync to globals()
83
+ """
84
+ if role == Qt.ItemDataRole.EditRole and index.column() == 2:
85
+
86
+ if value == "":
87
+ return False
88
+
89
+ old_repr = self.variables[index.row()][2]
90
+ if str(value) == old_repr:
91
+ return False
92
+
93
+ var_name = self.variables[index.row()][0]
94
+ try:
95
+ # 嘗試將輸入轉換為 Python 物件
96
+ # Try to evaluate input as Python object
97
+ new_value = ast.literal_eval(value)
98
+ except Exception:
99
+ # 若失敗則當作字串處理
100
+ # If failed, treat as string
101
+ new_value = value
102
+
103
+ if new_value == self.variables[index.row()][3]:
104
+ return False
105
+
106
+ # 更新全域變數與模型資料
107
+ # Update globals and model data
108
+ globals()[var_name] = new_value
109
+ self.variables[index.row()][2] = repr(new_value)
110
+ self.variables[index.row()][3] = new_value
111
+ self.dataChanged.emit(index, index, [Qt.ItemDataRole.DisplayRole])
112
+ return True
113
+ return False
114
+
115
+
116
+ class VariableProxy(QSortFilterProxyModel):
117
+ """
118
+ 過濾代理模型:支援搜尋與編輯轉發
119
+ Proxy model: supports filtering and forwards editing
120
+ """
121
+
122
+ def setData(self, index, value, role=Qt.ItemDataRole.EditRole):
123
+ # 將編輯操作轉發到原始模型
124
+ # Forward editing to source model
125
+ return self.sourceModel().setData(
126
+ self.mapToSource(index), value, role
127
+ )
128
+
129
+
130
+ class VariableInspector(QWidget):
131
+ """
132
+ 變數檢視器:提供 GUI 介面顯示與搜尋全域變數
133
+ Variable inspector: GUI interface to display and search global variables
134
+ """
135
+
136
+ def __init__(self):
137
+ super().__init__()
138
+ self.setWindowTitle(language_wrapper.language_word_dict.get("variable_inspector_title"))
139
+ layout = QVBoxLayout(self)
140
+
141
+ # 搜尋框 / search box
142
+ self.search_box = QLineEdit()
143
+ self.search_box.setPlaceholderText(language_wrapper.language_word_dict.get("variable_inspector_search"))
144
+ layout.addWidget(QLabel(language_wrapper.language_word_dict.get("variable_inspector_search")))
145
+ layout.addWidget(self.search_box)
146
+
147
+ # 模型與代理模型 / model and proxy model
148
+ self.model = VariableModel(parent=self)
149
+ self.proxy_model = VariableProxy(self)
150
+ self.proxy_model.setSourceModel(self.model)
151
+ self.proxy_model.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) # 不分大小寫 / case-insensitive
152
+ self.proxy_model.setFilterKeyColumn(0) # 僅針對變數名稱過濾 / filter by variable name
153
+
154
+ # 表格顯示 / table view
155
+ self.table = QTableView()
156
+ self.table.setModel(self.proxy_model)
157
+ self.table.horizontalHeader().setStretchLastSection(True)
158
+ self.table.horizontalHeader().setSectionResizeMode(
159
+ self.table.horizontalHeader().ResizeMode.Stretch
160
+ )
161
+ layout.addWidget(self.table, stretch=1)
162
+
163
+ # 綁定搜尋框輸入事件 / bind search box input
164
+ self.search_box.textChanged.connect(
165
+ self.proxy_model.setFilterFixedString
166
+ )
167
+
168
+ # 定時更新變數清單 (每 500ms)
169
+ # Periodically update variable list (every 500ms)
170
+ self.timer = QTimer(self)
171
+ self.timer.timeout.connect(self.model.update_data)
172
+ self.timer.start(500)
File without changes
@@ -0,0 +1,71 @@
1
+ from PySide6.QtWidgets import QWidget, QLineEdit, QPushButton, QMessageBox, QGridLayout, QLabel
2
+
3
+ from je_editor.pyside_ui.main_ui.ai_widget.ai_config import ai_config
4
+ from je_editor.utils.logging.loggin_instance import jeditor_logger
5
+ from je_editor.utils.multi_language.multi_language_wrapper import language_wrapper
6
+
7
+
8
+ class SetAIDialog(QWidget):
9
+ """
10
+ 設定 AI 模型的對話框
11
+ Dialog for configuring AI model settings
12
+ """
13
+
14
+ def __init__(self):
15
+ jeditor_logger.info("Init SetAIDialog")
16
+ super().__init__()
17
+
18
+ # Base URL 輸入欄位 / Base URL input field
19
+ self.base_url_label = QLabel(language_wrapper.language_word_dict.get("base_url_label"))
20
+ self.base_url_input = QLineEdit()
21
+
22
+ # API Key 輸入欄位 / API Key input field
23
+ self.api_key_label = QLabel(language_wrapper.language_word_dict.get("api_key_label"))
24
+ self.api_key_input = QLineEdit()
25
+
26
+ # Chat Model 輸入欄位 / Chat model input field
27
+ self.chat_model_label = QLabel(language_wrapper.language_word_dict.get("ai_model_label"))
28
+ self.chat_model_input = QLineEdit()
29
+
30
+ # 新增 AI 設定按鈕 / Button to add AI configuration
31
+ self.add_ai_info_button = QPushButton()
32
+ self.add_ai_info_button.setText(language_wrapper.language_word_dict.get("add_ai_model_pushbutton"))
33
+ self.add_ai_info_button.clicked.connect(self.update_ai_config)
34
+
35
+ # 使用 GridLayout 排版 / Use GridLayout for layout
36
+ self.grid_layout = QGridLayout()
37
+ self.grid_layout.addWidget(self.base_url_label, 0, 0)
38
+ self.grid_layout.addWidget(self.base_url_input, 0, 1)
39
+ self.grid_layout.addWidget(self.api_key_label, 1, 0)
40
+ self.grid_layout.addWidget(self.api_key_input, 1, 1)
41
+ self.grid_layout.addWidget(self.chat_model_label, 2, 0)
42
+ self.grid_layout.addWidget(self.chat_model_input, 2, 1)
43
+ self.grid_layout.addWidget(self.add_ai_info_button, 3, 1)
44
+
45
+ # 設定視窗標題 / Set window title
46
+ self.setWindowTitle(language_wrapper.language_word_dict.get("add_ai_model_title"))
47
+ self.setLayout(self.grid_layout)
48
+
49
+ def update_ai_config(self):
50
+ """
51
+ 更新 AI 設定,將使用者輸入的 base_url、api_key、chat_model
52
+ 儲存到 ai_config.choosable_ai 中
53
+ Update AI configuration with user inputs (base_url, api_key, chat_model)
54
+ """
55
+ base_url = self.base_url_input.text().strip()
56
+ api_key = self.api_key_input.text().strip()
57
+ chat_model = self.chat_model_input.text().strip()
58
+
59
+ if base_url and chat_model:
60
+ # 更新設定字典 / Update configuration dictionary
61
+ ai_config.choosable_ai.update(
62
+ {"AI_model": {"base_url": base_url, "api_key": api_key, "chat_model": chat_model}}
63
+ )
64
+ else:
65
+ # 若缺少必要欄位,顯示警告訊息框
66
+ # Show warning message box if required fields are missing
67
+ QMessageBox.warning(
68
+ self,
69
+ language_wrapper.language_word_dict.get("set_ai_model_warring_title"),
70
+ language_wrapper.language_word_dict.get("set_ai_model_warring_text")
71
+ )
@@ -5,30 +5,64 @@ from je_editor.utils.multi_language.multi_language_wrapper import language_wrapp
5
5
 
6
6
 
7
7
  class CreateFileDialog(QWidget):
8
+ """
9
+ 建立新檔案的對話框
10
+ Dialog for creating a new file
11
+ """
8
12
 
9
13
  def __init__(self):
10
14
  jeditor_logger.info("Init CreateFileDialog")
11
15
  super().__init__()
16
+
17
+ # 垂直佈局 (上到下)
18
+ # Vertical layout (top to bottom)
12
19
  self.box_layout = QBoxLayout(QBoxLayout.Direction.TopToBottom)
20
+
21
+ # 檔案名稱輸入框 / File name input field
13
22
  self.file_name_input = QLineEdit()
23
+
24
+ # 建立檔案按鈕 / Create file button
14
25
  self.create_file_button = QPushButton()
15
- self.create_file_button.setText(language_wrapper.language_word_dict.get("dialog_create_file"))
26
+ self.create_file_button.setText(language_wrapper.language_word_dict.get("create_file_dialog_pushbutton"))
16
27
  self.create_file_button.clicked.connect(self.create_file)
28
+
29
+ # 水平佈局 (放置按鈕) / Horizontal layout (for button)
17
30
  self.box_h_layout = QHBoxLayout()
18
31
  self.box_h_layout.addWidget(self.create_file_button)
32
+
33
+ # 將元件加入主佈局 / Add widgets to main layout
19
34
  self.box_layout.addWidget(self.file_name_input)
20
35
  self.box_layout.addLayout(self.box_h_layout)
21
- self.setWindowTitle(language_wrapper.language_word_dict.get("dialog_create_file"))
36
+
37
+ # 設定視窗標題 / Set window title
38
+ self.setWindowTitle(language_wrapper.language_word_dict.get("create_file_dialog_pushbutton"))
22
39
  self.setLayout(self.box_layout)
23
40
 
24
41
  def create_file(self):
42
+ """
43
+ 建立檔案的邏輯:
44
+ 1. 檢查輸入是否為空
45
+ 2. 若為空,顯示警告訊息
46
+ 3. 否則建立新檔案並關閉對話框
47
+ File creation logic:
48
+ 1. Check if input is empty
49
+ 2. If empty, show warning message
50
+ 3. Otherwise, create new file and close dialog
51
+ """
25
52
  jeditor_logger.info("CreateFileDialog create_file")
26
53
  file_name = self.file_name_input.text().strip()
54
+
27
55
  if file_name == "":
56
+ # 若未輸入檔名,顯示提示訊息
57
+ # Show warning if no file name is entered
28
58
  create_file_message_box = QMessageBox(self)
29
- create_file_message_box.setText(language_wrapper.language_word_dict.get("dialog_input_file_name"))
59
+ create_file_message_box.setText(
60
+ language_wrapper.language_word_dict.get("input_file_name_dialog_pushbutton")
61
+ )
30
62
  create_file_message_box.show()
31
63
  else:
64
+ # 建立新檔案 (若存在則覆蓋)
65
+ # Create new file (overwrite if exists)
32
66
  with open(file_name, "w+") as file:
33
67
  file.write("")
34
- self.close()
68
+ self.close() # 關閉對話框 / close dialog
@@ -12,6 +12,8 @@ from je_editor.utils.multi_language.multi_language_wrapper import language_wrapp
12
12
  from je_editor.utils.venv_check.check_venv import check_and_choose_venv
13
13
 
14
14
  if TYPE_CHECKING:
15
+ # 僅在型別檢查時匯入,避免循環依賴
16
+ # Only imported during type checking to avoid circular imports
15
17
  from je_editor.pyside_ui.main_ui.main_editor import EditorMain
16
18
 
17
19
  from PySide6.QtWidgets import QFileDialog
@@ -22,14 +24,16 @@ from je_editor.utils.file.open.open_file import read_file
22
24
 
23
25
  def choose_file_get_open_file_path(parent_qt_instance: EditorMain) -> None:
24
26
  """
27
+ 開啟檔案並將內容載入編輯器
25
28
  Open file and set code edit content
26
- :param parent_qt_instance: Pyside parent
29
+ :param parent_qt_instance: Pyside 主視窗 / Pyside parent
27
30
  :return: None
28
31
  """
29
32
  jeditor_logger.info("open_file_dialog.py choose_file_get_open_file_path"
30
33
  f" parent_qt_instance: {parent_qt_instance}")
31
34
  widget = parent_qt_instance.tab_widget.currentWidget()
32
35
  if isinstance(widget, EditorWidget):
36
+ # 開啟檔案選擇對話框 / Open file dialog
33
37
  file_path = QFileDialog().getOpenFileName(
34
38
  parent=parent_qt_instance,
35
39
  dir=str(Path.cwd()),
@@ -37,27 +41,40 @@ def choose_file_get_open_file_path(parent_qt_instance: EditorMain) -> None:
37
41
  HTML file (*.html);;
38
42
  File (*.*)"""
39
43
  )[0]
44
+
40
45
  if file_path is not None and file_path != "":
46
+ # 檢查檔案是否已經開啟 / Check if file already opened
41
47
  if file_is_open_manager_dict.get(str(Path(file_path)), None) is not None:
42
48
  widget.tab_manager.setCurrentWidget(
43
49
  widget.tab_manager.findChild(EditorWidget, str(Path(file_path).name)))
44
50
  return
45
51
  else:
52
+ # 記錄已開啟檔案 / Register opened file
46
53
  file_is_open_manager_dict.update({file_path: str(Path(file_path).name)})
54
+
55
+ # 設定目前檔案路徑 / Set current file path
47
56
  widget.current_file = file_path
57
+ # 讀取檔案內容 / Read file content
48
58
  file_content = read_file(file_path)[1]
49
- widget.code_edit.setPlainText(
50
- file_content
51
- )
59
+ widget.code_edit.setPlainText(file_content)
60
+
61
+ # 啟動自動儲存執行緒 / Start auto-save thread
52
62
  if widget.current_file is not None and widget.code_save_thread is None:
53
63
  init_new_auto_save_thread(widget.current_file, widget)
54
64
  else:
55
65
  widget.code_save_thread.file = widget.current_file
66
+
67
+ # 更新使用者設定中的最後開啟檔案 / Update last opened file in user settings
56
68
  user_setting_dict.update({"last_file": str(widget.current_file)})
69
+ # 更新分頁標題 / Rename tab title
57
70
  widget.rename_self_tab()
58
71
 
59
72
 
60
73
  def choose_dir_get_dir_path(parent_qt_instance: EditorMain) -> None:
74
+ """
75
+ 選擇資料夾並更新工作目錄與專案樹
76
+ Choose directory and update working dir and project tree
77
+ """
61
78
  jeditor_logger.info("open_file_dialog.py choose_dir_get_dir_path"
62
79
  f" parent_qt_instance: {parent_qt_instance}")
63
80
  dir_path = QFileDialog().getExistingDirectory(parent=parent_qt_instance, )
@@ -65,19 +82,30 @@ def choose_dir_get_dir_path(parent_qt_instance: EditorMain) -> None:
65
82
  check_path = Path(dir_path)
66
83
  else:
67
84
  return
85
+
68
86
  if check_path.exists() and check_path.is_dir():
87
+ # 更新工作目錄 / Update working directory
69
88
  parent_qt_instance.working_dir = dir_path
70
89
  os.chdir(dir_path)
90
+
91
+ # 更新所有編輯器的專案樹與環境檢查 / Update project tree and check env for all editors
71
92
  for code_editor in range(parent_qt_instance.tab_widget.count()):
72
93
  widget = parent_qt_instance.tab_widget.widget(code_editor)
73
94
  if isinstance(widget, EditorWidget):
74
95
  widget.project_treeview.setRootIndex(widget.project_treeview_model.index(dir_path))
75
96
  widget.code_edit.check_env()
97
+
98
+ # 設定虛擬環境路徑 / Set virtual environment path
76
99
  if sys.platform in ["win32", "cygwin", "msys"]:
77
100
  venv_path = Path(os.getcwd() + "/venv/Scripts")
78
101
  else:
79
102
  venv_path = Path(os.getcwd() + "/venv/bin")
103
+
80
104
  parent_qt_instance.python_compiler = check_and_choose_venv(venv_path)
105
+
106
+ # 重新讀取使用者設定並套用啟動設定 / Reload user settings and apply startup settings
81
107
  read_user_setting()
82
108
  parent_qt_instance.startup_setting()
109
+
110
+ # 重設語言設定 / Reset language
83
111
  language_wrapper.reset_language(user_setting_dict.get("language", "English"))
@@ -8,7 +8,10 @@ from je_editor.pyside_ui.code.auto_save.auto_save_manager import file_is_open_ma
8
8
  from je_editor.utils.logging.loggin_instance import jeditor_logger
9
9
 
10
10
  if TYPE_CHECKING:
11
+ # 僅在型別檢查時匯入,避免循環依賴
12
+ # Only imported during type checking to avoid circular imports
11
13
  from je_editor.pyside_ui.main_ui.main_editor import EditorMain
14
+
12
15
  from PySide6.QtWidgets import QFileDialog
13
16
 
14
17
  from je_editor.pyside_ui.main_ui.editor.editor_widget import EditorWidget
@@ -17,13 +20,20 @@ from je_editor.utils.file.save.save_file import write_file
17
20
 
18
21
  def choose_file_get_save_file_path(parent_qt_instance: EditorMain) -> bool:
19
22
  """
20
- :param parent_qt_instance: Pyside parent
21
- :return: save code edit content to file
23
+ 開啟「另存新檔」對話框,將編輯器內容儲存到檔案
24
+ Open "Save As" dialog and save editor content to file
25
+ :param parent_qt_instance: Pyside 主視窗 / Pyside parent
26
+ :return: 是否成功儲存檔案 / whether file was saved successfully
22
27
  """
23
28
  jeditor_logger.info("save_file_dialog.py choose_file_get_save_file_path"
24
29
  f" parent_qt_instance: {parent_qt_instance}")
30
+
31
+ # 取得目前分頁的編輯器元件
32
+ # Get current tab's editor widget
25
33
  widget = parent_qt_instance.tab_widget.currentWidget()
34
+
26
35
  if isinstance(widget, EditorWidget):
36
+ # 開啟檔案儲存對話框 / Open file save dialog
27
37
  file_path = QFileDialog().getSaveFileName(
28
38
  parent=parent_qt_instance,
29
39
  dir=os.getcwd(),
@@ -31,14 +41,27 @@ def choose_file_get_save_file_path(parent_qt_instance: EditorMain) -> bool:
31
41
  HTML file (*.html);;
32
42
  File (*.*)"""
33
43
  )[0]
44
+
45
+ # 確認使用者有選擇檔案路徑 / Ensure user selected a file path
34
46
  if file_path is not None and file_path != "":
47
+ # 更新目前檔案路徑 / Update current file path
35
48
  widget.current_file = file_path
49
+
50
+ # 將編輯器內容寫入檔案 / Write editor content to file
36
51
  write_file(file_path, widget.code_edit.toPlainText())
52
+
53
+ # 更新已開啟檔案管理字典 / Update opened file manager dictionary
37
54
  path = Path(file_path)
38
55
  file_is_open_manager_dict.update({str(path): str(path.name)})
56
+
57
+ # 若有自動儲存執行緒,更新其監控的檔案與編輯器
58
+ # If auto-save thread exists, update its file and editor reference
39
59
  if widget.code_save_thread is not None:
40
60
  widget.code_save_thread.file = file_path
41
61
  widget.code_save_thread.editor = widget.code_edit
62
+
63
+ # 更新分頁標題 / Update tab title
42
64
  widget.rename_self_tab()
43
65
  return True
44
66
  return False
67
+ return False
@@ -5,20 +5,45 @@ from je_editor.utils.multi_language.multi_language_wrapper import language_wrapp
5
5
 
6
6
 
7
7
  class SearchResultBox(QWidget):
8
+ """
9
+ 搜尋結果對話框
10
+ Search result dialog box
11
+ """
8
12
 
9
13
  def __init__(self):
10
14
  jeditor_logger.info("Init SearchResultBox")
11
15
  super().__init__()
16
+
17
+ # 垂直佈局 (上到下)
18
+ # Vertical layout (top to bottom)
12
19
  self.box_layout = QBoxLayout(QBoxLayout.Direction.TopToBottom)
20
+
21
+ # 搜尋輸入框 / Search input field
13
22
  self.search_input = QLineEdit()
23
+
24
+ # 下一個搜尋按鈕 / Next search button
14
25
  self.search_next_button = QPushButton()
15
- self.search_next_button.setText(language_wrapper.language_word_dict.get("dialog_search_next"))
26
+ self.search_next_button.setText(language_wrapper.language_word_dict.get("search_next_dialog_pushbutton"))
27
+
28
+ # 上一個搜尋按鈕 / Previous search button
16
29
  self.search_back_button = QPushButton()
17
- self.search_back_button.setText(language_wrapper.language_word_dict.get("dialog_search_back"))
30
+ self.search_back_button.setText(language_wrapper.language_word_dict.get("search_back_dialog_pushbutton"))
31
+
32
+ # 水平佈局 (放置前後搜尋按鈕)
33
+ # Horizontal layout (for back/next buttons)
18
34
  self.box_h_layout = QHBoxLayout()
19
35
  self.box_h_layout.addWidget(self.search_back_button)
20
36
  self.box_h_layout.addWidget(self.search_next_button)
37
+
38
+ # 將元件加入主佈局
39
+ # Add widgets to main layout
21
40
  self.box_layout.addWidget(self.search_input)
22
41
  self.box_layout.addLayout(self.box_h_layout)
42
+
43
+ # 設定視窗標題
44
+ # Set window title
23
45
  self.setWindowTitle("Search Result")
46
+
47
+ # 套用主佈局
48
+ # Apply main layout
24
49
  self.setLayout(self.box_layout)
@@ -5,20 +5,45 @@ from je_editor.utils.multi_language.multi_language_wrapper import language_wrapp
5
5
 
6
6
 
7
7
  class SearchBox(QWidget):
8
+ """
9
+ 搜尋框元件
10
+ Search box widget
11
+ """
8
12
 
9
13
  def __init__(self):
10
14
  jeditor_logger.info("Init SearchBox")
11
15
  super().__init__()
16
+
17
+ # 垂直佈局 (上到下)
18
+ # Vertical layout (top to bottom)
12
19
  self.box_layout = QBoxLayout(QBoxLayout.Direction.TopToBottom)
20
+
21
+ # 搜尋輸入框 / Search input field
13
22
  self.search_input = QLineEdit()
23
+
24
+ # 下一個搜尋按鈕 / Next search button
14
25
  self.search_next_button = QPushButton()
15
- self.search_next_button.setText(language_wrapper.language_word_dict.get("dialog_search_next"))
26
+ self.search_next_button.setText(language_wrapper.language_word_dict.get("search_next_dialog_pushbutton"))
27
+
28
+ # 上一個搜尋按鈕 / Previous search button
16
29
  self.search_back_button = QPushButton()
17
- self.search_back_button.setText(language_wrapper.language_word_dict.get("dialog_search_back"))
30
+ self.search_back_button.setText(language_wrapper.language_word_dict.get("search_back_dialog_pushbutton"))
31
+
32
+ # 水平佈局 (放置前後搜尋按鈕)
33
+ # Horizontal layout (for back/next buttons)
18
34
  self.box_h_layout = QHBoxLayout()
19
35
  self.box_h_layout.addWidget(self.search_back_button)
20
36
  self.box_h_layout.addWidget(self.search_next_button)
37
+
38
+ # 將元件加入主佈局
39
+ # Add widgets to main layout
21
40
  self.box_layout.addWidget(self.search_input)
22
41
  self.box_layout.addLayout(self.box_h_layout)
23
- self.setWindowTitle("Search Text")
42
+
43
+ # 設定視窗標題 (支援多語系)
44
+ # Set window title (multi-language support)
45
+ self.setWindowTitle(language_wrapper.language_word_dict.get("search_box_dialog_title"))
46
+
47
+ # 套用主佈局
48
+ # Apply main layout
24
49
  self.setLayout(self.box_layout)
File without changes
@@ -0,0 +1,90 @@
1
+ from PySide6.QtGui import QAction, QActionGroup
2
+ from PySide6.QtWidgets import (
3
+ QWidget, QVBoxLayout, QMenuBar, QFileDialog, QMessageBox
4
+ )
5
+ from git import Repo
6
+
7
+ from je_editor.pyside_ui.git_ui.code_diff_compare.multi_file_diff_viewer import MultiFileDiffViewer
8
+
9
+
10
+ class DiffViewerWidget(QWidget):
11
+ """
12
+ Git Diff Viewer Application
13
+ Git 差異檢視器應用程式
14
+ """
15
+
16
+ def __init__(self):
17
+ super().__init__()
18
+ self.setWindowTitle("Git Diff Viewer")
19
+
20
+ # === Main diff viewer widget / 主要的差異檢視元件 ===
21
+ self.viewer = MultiFileDiffViewer()
22
+
23
+ # === Menu bar / 選單列 ===
24
+ self.menubar = QMenuBar(self)
25
+ file_menu = self.menubar.addMenu("File") # File 選單
26
+ view_menu = self.menubar.addMenu("View") # View 選單
27
+
28
+ # --- File → Open Git Repo ---
29
+ # Action to open a Git repository
30
+ # 開啟 Git 專案的動作
31
+ open_repo_action = QAction("Open Git Repo", self)
32
+ open_repo_action.triggered.connect(self.open_repo)
33
+ file_menu.addAction(open_repo_action)
34
+
35
+ # === View → Theme switching (exclusive) / 主題切換(單選模式) ===
36
+ theme_group = QActionGroup(self)
37
+ theme_group.setExclusive(True) # 確保只能選擇一個主題 / ensure only one theme can be selected
38
+
39
+ dark_action = QAction("Dark Mode", self, checkable=True) # 深色模式 / dark mode
40
+ light_action = QAction("Light Mode", self, checkable=True) # 淺色模式 / light mode
41
+
42
+ theme_group.addAction(dark_action)
43
+ theme_group.addAction(light_action)
44
+
45
+ # Default to dark mode / 預設為深色模式
46
+ dark_action.setChecked(True)
47
+ self.viewer.set_dark_theme()
48
+
49
+ # Connect theme actions / 綁定主題切換事件
50
+ dark_action.triggered.connect(lambda: self.set_theme("dark"))
51
+ light_action.triggered.connect(lambda: self.set_theme("light"))
52
+
53
+ view_menu.addAction(dark_action)
54
+ view_menu.addAction(light_action)
55
+
56
+ # === Layout / 版面配置 ===
57
+ layout = QVBoxLayout(self)
58
+ layout.setMenuBar(self.menubar) # 把選單列放在上方 / put menu bar at the top
59
+ layout.addWidget(self.viewer) # 把差異檢視器放在主要區域 / add diff viewer in main area
60
+
61
+ def open_repo(self):
62
+ """
63
+ Open a Git repository and display its diff.
64
+ 開啟一個 Git 專案並顯示差異。
65
+ """
66
+ path = QFileDialog.getExistingDirectory(self, "Select Git Repository")
67
+ if not path:
68
+ return
69
+ try:
70
+ repo = Repo(path) # 嘗試載入 Git 專案 / try to load Git repo
71
+ diff_text = repo.git.diff() # 取得差異文字 / get diff text
72
+ if not diff_text.strip():
73
+ # 如果沒有差異,顯示提示訊息 / show info if no changes
74
+ QMessageBox.information(self, "Info", "No changes in repo.")
75
+ else:
76
+ # 如果有差異,顯示在 viewer 中 / show diff in viewer
77
+ self.viewer.set_diff_text(diff_text)
78
+ except Exception as e:
79
+ # 如果開啟失敗,顯示錯誤訊息 / show error if failed
80
+ QMessageBox.critical(self, "Error", f"Failed to open repo:\n{e}")
81
+
82
+ def set_theme(self, mode: str):
83
+ """
84
+ Switch between dark and light themes.
85
+ 切換深色與淺色主題。
86
+ """
87
+ if mode == "dark":
88
+ self.viewer.set_dark_theme()
89
+ else:
90
+ self.viewer.set_light_theme()