je-editor 0.0.221__py3-none-any.whl → 0.0.223__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 (36) hide show
  1. je_editor/__init__.py +2 -2
  2. je_editor/code_scan/ruff_thread.py +33 -6
  3. je_editor/code_scan/watchdog_implement.py +42 -20
  4. je_editor/code_scan/watchdog_thread.py +54 -9
  5. je_editor/git_client/commit_graph.py +32 -43
  6. je_editor/git_client/{git.py → git_action.py} +1 -1
  7. je_editor/git_client/git_cli.py +4 -4
  8. je_editor/pyside_ui/browser/browser_download_window.py +41 -5
  9. je_editor/pyside_ui/browser/browser_serach_lineedit.py +25 -1
  10. je_editor/pyside_ui/browser/browser_view.py +56 -3
  11. je_editor/pyside_ui/browser/browser_widget.py +52 -22
  12. je_editor/pyside_ui/browser/main_browser_widget.py +72 -0
  13. je_editor/pyside_ui/git_ui/code_diff_compare/__init__.py +0 -0
  14. je_editor/pyside_ui/git_ui/code_diff_compare/code_diff_viewer_widget.py +90 -0
  15. je_editor/pyside_ui/git_ui/code_diff_compare/line_number_code_viewer.py +141 -0
  16. je_editor/pyside_ui/git_ui/code_diff_compare/multi_file_diff_viewer.py +88 -0
  17. je_editor/pyside_ui/git_ui/code_diff_compare/side_by_side_diff_widget.py +271 -0
  18. je_editor/pyside_ui/git_ui/git_client/__init__.py +0 -0
  19. je_editor/pyside_ui/git_ui/{git_branch_tree_widget.py → git_client/git_branch_tree_widget.py} +17 -14
  20. je_editor/pyside_ui/git_ui/git_client/git_client_gui.py +799 -0
  21. je_editor/pyside_ui/main_ui/ai_widget/langchain_interface.py +1 -1
  22. je_editor/pyside_ui/main_ui/main_editor.py +5 -8
  23. je_editor/pyside_ui/main_ui/menu/dock_menu/build_dock_menu.py +17 -17
  24. je_editor/pyside_ui/main_ui/menu/help_menu/build_help_menu.py +2 -2
  25. je_editor/pyside_ui/main_ui/menu/tab_menu/build_tab_menu.py +21 -21
  26. je_editor/utils/multi_language/english.py +2 -1
  27. je_editor/utils/multi_language/traditional_chinese.py +2 -2
  28. {je_editor-0.0.221.dist-info → je_editor-0.0.223.dist-info}/METADATA +3 -3
  29. {je_editor-0.0.221.dist-info → je_editor-0.0.223.dist-info}/RECORD +34 -28
  30. je_editor/git_client/github.py +0 -50
  31. je_editor/pyside_ui/git_ui/git_client_gui.py +0 -291
  32. /je_editor/pyside_ui/git_ui/{commit_table.py → git_client/commit_table.py} +0 -0
  33. /je_editor/pyside_ui/git_ui/{graph_view.py → git_client/graph_view.py} +0 -0
  34. {je_editor-0.0.221.dist-info → je_editor-0.0.223.dist-info}/WHEEL +0 -0
  35. {je_editor-0.0.221.dist-info → je_editor-0.0.223.dist-info}/licenses/LICENSE +0 -0
  36. {je_editor-0.0.221.dist-info → je_editor-0.0.223.dist-info}/top_level.txt +0 -0
@@ -1,60 +1,90 @@
1
- from PySide6.QtGui import QAction, Qt
2
- from PySide6.QtWidgets import QWidget, QGridLayout, QPushButton, QInputDialog
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from je_editor.pyside_ui.browser.main_browser_widget import MainBrowserWidget
7
+
8
+ from PySide6.QtCore import Qt
9
+ from PySide6.QtGui import QAction
10
+ from PySide6.QtWidgets import QWidget, QPushButton, QGridLayout, QInputDialog
3
11
 
4
12
  from je_editor.pyside_ui.browser.browser_serach_lineedit import BrowserLineSearch
5
- from je_editor.pyside_ui.browser.browser_view import BrowserView
6
13
  from je_editor.utils.logging.loggin_instance import jeditor_logger
7
14
  from je_editor.utils.multi_language.multi_language_wrapper import language_wrapper
8
15
 
16
+ from je_editor.pyside_ui.browser.browser_view import BrowserView
9
17
 
10
- class BrowserWidget(QWidget):
11
18
 
19
+ class BrowserWidget(QWidget):
12
20
  def __init__(self, start_url: str = "https://www.google.com/",
13
- search_prefix: str = "https://www.google.com.tw/search?q="):
21
+ search_prefix: str = "https://www.google.com.tw/search?q=",
22
+ main_widget: MainBrowserWidget = None, browser_view: BrowserView = None):
23
+ # --- Browser setting / 瀏覽器設定 ---
14
24
  super().__init__()
15
- jeditor_logger.info("Init BrowserWidget "
16
- f"start_url: {start_url} "
17
- f"search_prefix: {search_prefix}")
18
- # Browser setting
19
- self.browser = BrowserView(start_url)
20
- self.search_prefix = search_prefix
25
+ self.main_widget = main_widget
26
+ if browser_view:
27
+ self.browser = browser_view
28
+ else:
29
+ self.browser = BrowserView(start_url, main_widget=main_widget) # 內嵌的瀏覽器視圖
30
+ self.search_prefix = search_prefix # 搜尋引擎前綴字串
21
31
  self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
22
- # Top bar
32
+
33
+ # --- Top bar buttons / 上方工具列按鈕 ---
23
34
  self.back_button = QPushButton(language_wrapper.language_word_dict.get("browser_back_button"))
24
- self.back_button.clicked.connect(self.browser.back)
35
+ self.back_button.clicked.connect(self.browser.back) # 返回上一頁
36
+
25
37
  self.forward_button = QPushButton(language_wrapper.language_word_dict.get("browser_forward_button"))
26
- self.forward_button.clicked.connect(self.browser.forward)
38
+ self.forward_button.clicked.connect(self.browser.forward) # 前往下一頁
39
+
27
40
  self.reload_button = QPushButton(language_wrapper.language_word_dict.get("browser_reload_button"))
28
- self.reload_button.clicked.connect(self.browser.reload)
41
+ self.reload_button.clicked.connect(self.browser.reload) # 重新整理
42
+
29
43
  self.search_button = QPushButton(language_wrapper.language_word_dict.get("browser_search_button"))
30
- self.search_button.clicked.connect(self.search)
44
+ self.search_button.clicked.connect(self.search) # 搜尋按鈕
45
+
46
+ # URL / Search input line (custom QLineEdit)
31
47
  self.url_input = BrowserLineSearch(self)
32
- # Action
48
+
49
+ # --- Action: Ctrl+F to find text / 快捷鍵 Ctrl+F 搜尋文字 ---
33
50
  self.find_action = QAction()
34
51
  self.find_action.setShortcut("Ctrl+f")
35
52
  self.find_action.triggered.connect(self.find_text)
36
53
  self.addAction(self.find_action)
37
- # Layout
54
+
55
+ # --- Layout / 版面配置 ---
38
56
  self.grid_layout = QGridLayout()
39
57
  self.grid_layout.addWidget(self.back_button, 0, 0)
40
58
  self.grid_layout.addWidget(self.forward_button, 0, 1)
41
59
  self.grid_layout.addWidget(self.reload_button, 0, 2)
42
60
  self.grid_layout.addWidget(self.url_input, 0, 3)
43
61
  self.grid_layout.addWidget(self.search_button, 0, 4)
44
- self.grid_layout.addWidget(self.browser, 1, 0, -1, -1)
62
+ self.grid_layout.addWidget(self.browser, 1, 0, -1, -1) # 瀏覽器視圖佔滿下方
45
63
  self.setLayout(self.grid_layout)
46
64
 
47
65
  def search(self):
66
+ """
67
+ Perform a search using the text in the input line.
68
+ 使用輸入框的文字進行搜尋,將字串附加到 search_prefix 後送出。
69
+ """
48
70
  jeditor_logger.info("BrowserWidget Search")
49
71
  self.browser.setUrl(f"{self.search_prefix}{self.url_input.text()}")
50
72
 
51
73
  def find_text(self):
74
+ """
75
+ Open a dialog to find text in the current page.
76
+ 開啟輸入對話框,在當前頁面中搜尋文字。
77
+ - 如果按下 OK,搜尋輸入的文字
78
+ - 如果取消,清除搜尋
79
+ """
52
80
  jeditor_logger.info("BrowserWidget Find Text")
53
81
  search_box = QInputDialog(self)
54
82
  search_text, press_ok = search_box.getText(
55
- self, language_wrapper.language_word_dict.get("browser_find_text"),
56
- language_wrapper.language_word_dict.get("browser_find_text_input"))
83
+ self,
84
+ language_wrapper.language_word_dict.get("browser_find_text"),
85
+ language_wrapper.language_word_dict.get("browser_find_text_input")
86
+ )
57
87
  if press_ok:
58
88
  self.browser.findText(search_text)
59
89
  else:
60
- self.browser.findText("")
90
+ self.browser.findText("")
@@ -0,0 +1,72 @@
1
+ from PySide6.QtWidgets import QWidget, QGridLayout, QTabWidget, QTabBar
2
+
3
+ from je_editor.pyside_ui.browser.browser_widget import BrowserWidget
4
+ from je_editor.utils.logging.loggin_instance import jeditor_logger
5
+
6
+
7
+ class MainBrowserWidget(QWidget):
8
+ """
9
+ 瀏覽器元件:包含分頁,並在最右方固定一個「+」分頁。
10
+ """
11
+
12
+ def __init__(self, start_url: str = "https://www.google.com/",
13
+ search_prefix: str = "https://www.google.com.tw/search?q="):
14
+ super().__init__()
15
+ jeditor_logger.info("Init BrowserWidget "
16
+ f"start_url: {start_url} "
17
+ f"search_prefix: {search_prefix}")
18
+
19
+ grid_layout = QGridLayout()
20
+
21
+ self.browser_tab = QTabWidget()
22
+ self.browser_tab.setTabsClosable(True)
23
+ self.browser_tab.tabCloseRequested.connect(self.close_tab)
24
+
25
+ self.search_prefix = search_prefix
26
+
27
+ # 預設第一個分頁
28
+ self.add_browser_tab(BrowserWidget(start_url=start_url,
29
+ search_prefix=search_prefix),
30
+ "Google")
31
+
32
+ # 固定一個「+」分頁
33
+ self.add_plus_tab()
34
+
35
+ grid_layout.addWidget(self.browser_tab)
36
+ self.setLayout(grid_layout)
37
+
38
+ def add_browser_tab(self, browser_widget: BrowserWidget, title: str = "New Tab"):
39
+ # 在「+」分頁前插入新分頁
40
+ plus_index = self.browser_tab.count() - 1
41
+ index = self.browser_tab.insertTab(plus_index, browser_widget, title)
42
+ self.browser_tab.setCurrentIndex(index)
43
+
44
+ # 更新分頁標題
45
+ browser_widget.browser.titleChanged.connect(
46
+ lambda t, i=index: self.browser_tab.setTabText(i, t)
47
+ )
48
+
49
+ def add_plus_tab(self):
50
+ """新增一個固定的「+」分頁"""
51
+ add_new_page_widget = QWidget()
52
+ self.browser_tab.addTab(add_new_page_widget, "+")
53
+ # 禁止關閉「+」分頁
54
+ self.browser_tab.tabBar().setTabButton(self.browser_tab.count() - 1,
55
+ QTabBar.ButtonPosition.RightSide, None)
56
+ self.browser_tab.tabBar().tabBarClicked.connect(self.handle_tab_changed)
57
+
58
+ def handle_tab_changed(self, index: int):
59
+ """如果點到「+」分頁,就開新分頁"""
60
+ if self.browser_tab.tabText(index) == "+": # 最後一個是「+」
61
+ # 建立新分頁(會自動切換到新分頁)
62
+ self.add_browser_tab(
63
+ BrowserWidget(start_url="https://www.google.com/",
64
+ search_prefix=self.search_prefix),
65
+ "Google"
66
+ )
67
+
68
+ def close_tab(self, index: int):
69
+ """關閉指定分頁,但保留「+」"""
70
+ widget = self.browser_tab.widget(index)
71
+ self.browser_tab.removeTab(index)
72
+ widget.deleteLater()
@@ -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) # 確保只能選擇一個主題
38
+
39
+ dark_action = QAction("Dark Mode", self, checkable=True) # 深色模式
40
+ light_action = QAction("Light Mode", self, checkable=True) # 淺色模式
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) # 把選單列放在上方
59
+ layout.addWidget(self.viewer) # 把差異檢視器放在主要區域
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 專案
71
+ diff_text = repo.git.diff() # 取得差異文字
72
+ if not diff_text.strip():
73
+ # 如果沒有差異,顯示提示訊息
74
+ QMessageBox.information(self, "Info", "No changes in repo.")
75
+ else:
76
+ # 如果有差異,顯示在 viewer 中
77
+ self.viewer.set_diff_text(diff_text)
78
+ except Exception as e:
79
+ # 如果開啟失敗,顯示錯誤訊息
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()
@@ -0,0 +1,141 @@
1
+ from PySide6.QtCore import QRect, QSize, Qt
2
+ from PySide6.QtGui import QPainter, QColor
3
+ from PySide6.QtWidgets import QPlainTextEdit, QWidget, QTextEdit
4
+
5
+
6
+ class LineNumberArea(QWidget):
7
+ """
8
+ Side widget for displaying line numbers.
9
+ 用來顯示行號的側邊元件。
10
+ """
11
+
12
+ def __init__(self, editor):
13
+ super().__init__(editor)
14
+ self.code_editor = editor # 綁定主要的編輯器 / Bind to main editor
15
+
16
+ def sizeHint(self):
17
+ """
18
+ Suggest width for line number area.
19
+ 建議行號區域的寬度。
20
+ """
21
+ return QSize(self.code_editor.line_number_area_width(), 0)
22
+
23
+ def paintEvent(self, event):
24
+ """
25
+ Delegate paint event to the main editor.
26
+ 將繪製事件交給主要編輯器處理。
27
+ """
28
+ self.code_editor.line_number_area_paint_event(event)
29
+
30
+
31
+ class LineNumberedCodeViewer(QPlainTextEdit):
32
+ """
33
+ QPlainTextEdit with line numbers.
34
+ 帶有行號顯示的文字編輯器。
35
+ """
36
+
37
+ def __init__(self, parent=None):
38
+ super().__init__(parent)
39
+ self.lineNumberArea = LineNumberArea(self)
40
+
41
+ # === 信號連接 / Signal connections ===
42
+ self.blockCountChanged.connect(self.update_line_number_area_width) # 當行數改變時更新行號區寬度
43
+ self.updateRequest.connect(self.update_line_number_area) # 當需要重繪時更新行號區
44
+ self.cursorPositionChanged.connect(self.highlight_current_line) # 當游標移動時高亮當前行
45
+
46
+ # 初始化行號區寬度與高亮
47
+ self.update_line_number_area_width(0)
48
+ self.highlight_current_line()
49
+
50
+ def line_number_area_width(self):
51
+ """
52
+ Calculate width of line number area based on digit count.
53
+ 根據行數位數計算行號區寬度。
54
+ """
55
+ digits = len(str(max(1, self.blockCount())))
56
+ space = 3 + self.fontMetrics().horizontalAdvance('9') * digits
57
+ return space
58
+
59
+ def update_line_number_area_width(self, _):
60
+ """
61
+ Adjust viewport margins to fit line number area.
62
+ 調整視口邊界以容納行號區。
63
+ """
64
+ self.setViewportMargins(self.line_number_area_width(), 0, 0, 0)
65
+
66
+ def update_line_number_area(self, rect, dy):
67
+ """
68
+ Update/redraw line number area when scrolling or editing.
69
+ 當滾動或編輯時更新行號區。
70
+ """
71
+ if dy:
72
+ self.lineNumberArea.scroll(0, dy)
73
+ else:
74
+ self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height())
75
+ if rect.contains(self.viewport().rect()):
76
+ self.update_line_number_area_width(0)
77
+
78
+ def resizeEvent(self, event):
79
+ """
80
+ Resize line number area when editor resizes.
81
+ 編輯器大小改變時調整行號區。
82
+ """
83
+ super().resizeEvent(event)
84
+ cr = self.contentsRect()
85
+ self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.line_number_area_width(), cr.height()))
86
+
87
+ def line_number_area_paint_event(self, event):
88
+ """
89
+ Paint line numbers.
90
+ 繪製行號。
91
+ """
92
+ painter = QPainter(self.lineNumberArea)
93
+ # 背景顏色依主題切換 / Background color depends on theme
94
+ painter.fillRect(event.rect(), QColor(40, 40, 40) if getattr(self, "is_dark", False) else QColor(230, 230, 230))
95
+
96
+ block = self.firstVisibleBlock()
97
+ blockNumber = block.blockNumber()
98
+ top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
99
+ bottom = top + int(self.blockBoundingRect(block).height())
100
+
101
+ while block.isValid() and top <= event.rect().bottom():
102
+ if block.isVisible() and bottom >= event.rect().top():
103
+ number = str(blockNumber + 1)
104
+ # 行號顏色依主題切換 / Line number color depends on theme
105
+ painter.setPen(QColor("#d4d4d4") if getattr(self, "is_dark", False) else Qt.GlobalColor.black)
106
+ painter.drawText(
107
+ 0, top, self.lineNumberArea.width() - 2, self.fontMetrics().height(),
108
+ Qt.AlignmentFlag.AlignRight, number
109
+ )
110
+ block = block.next()
111
+ top = bottom
112
+ bottom = top + int(self.blockBoundingRect(block).height())
113
+ blockNumber += 1
114
+
115
+ def highlight_current_line(self):
116
+ """
117
+ Highlight the current line where the cursor is.
118
+ 高亮顯示游標所在的行。
119
+ """
120
+ selection = QTextEdit.ExtraSelection()
121
+ lineColor = QColor(50, 50, 50) if getattr(self, "is_dark", False) else QColor(232, 232, 255)
122
+ selection.format.setBackground(lineColor)
123
+ selection.cursor = self.textCursor()
124
+ selection.cursor.clearSelection()
125
+
126
+ # Keep current-line extras for merging
127
+ self._current_line_extras = [selection]
128
+
129
+ # Merge with diff extras if present
130
+ if hasattr(self, "_diff_extras"):
131
+ merged = self._diff_extras + self._current_line_extras
132
+ else:
133
+ merged = self._current_line_extras
134
+
135
+ self.setExtraSelections(merged)
136
+
137
+ def apply_theme_to_editor(self, dark: bool):
138
+ self.setStyleSheet("QPlainTextEdit { background-color: #1e1e1e; color: #d4d4d4; }" if dark
139
+ else "QPlainTextEdit { background-color: white; color: black; }")
140
+ # Re-trigger current line highlight with the new color
141
+ self.highlight_current_line()
@@ -0,0 +1,88 @@
1
+ from PySide6.QtWidgets import QTabWidget, QVBoxLayout, QWidget
2
+
3
+ from je_editor.pyside_ui.git_ui.code_diff_compare.side_by_side_diff_widget import SideBySideDiffWidget
4
+
5
+
6
+ class MultiFileDiffViewer(QWidget):
7
+ """
8
+ Multi-file diff viewer using tabs to display each file.
9
+ 多檔案差異檢視器,使用分頁 (tab) 來顯示每個檔案的差異。
10
+ """
11
+
12
+ def __init__(self, parent=None):
13
+ """
14
+ Initialize the multi-file diff viewer.
15
+ 初始化多檔案差異檢視器。
16
+ """
17
+ super().__init__(parent)
18
+ # Tab widget to hold each file's diff
19
+ # 用來存放每個檔案差異的分頁元件
20
+ self.tabs = QTabWidget()
21
+
22
+ # Layout: vertical box with only the tab widget
23
+ # 版面配置:垂直佈局,僅包含分頁元件
24
+ layout = QVBoxLayout(self)
25
+ layout.addWidget(self.tabs)
26
+
27
+ def set_diff_text(self, diff_text: str):
28
+ """
29
+ Set the diff text and split it into per-file tabs.
30
+ 設定差異文字,並依檔案拆分成多個分頁。
31
+ """
32
+ self.tabs.clear()
33
+ file_diffs = self._split_by_file(diff_text)
34
+ for file_name, ftext in file_diffs:
35
+ viewer = SideBySideDiffWidget() # 每個檔案使用 side-by-side viewer
36
+ viewer.set_diff_text(ftext)
37
+ self.tabs.addTab(viewer, file_name) # 新增分頁,標題為檔名
38
+
39
+ def _split_by_file(self, diff_text: str):
40
+ """
41
+ Split a unified diff text into chunks per file.
42
+ 將 unified diff 文字依檔案切分。
43
+ """
44
+ chunks = []
45
+ current = []
46
+ file_name = None
47
+
48
+ for line in diff_text.splitlines():
49
+ if line.startswith("diff --git"):
50
+ # 如果遇到新的檔案 diff,先把前一個檔案的內容存起來
51
+ if current and file_name:
52
+ chunks.append((file_name, "\n".join(current)))
53
+ current = []
54
+ # 解析檔名,格式通常為 "diff --git a/file b/file"
55
+ parts = line.split()
56
+ if len(parts) >= 4:
57
+ file_name = parts[2][2:] # 去掉 "a/"
58
+ else:
59
+ file_name = "unknown"
60
+ current.append(line)
61
+ else:
62
+ current.append(line)
63
+
64
+ # 最後一個檔案也要加入結果
65
+ if current and file_name:
66
+ chunks.append((file_name, "\n".join(current)))
67
+
68
+ return chunks
69
+
70
+ def set_dark_theme(self):
71
+ """
72
+ Apply dark theme to all tabs.
73
+ 對所有分頁套用深色主題。
74
+ """
75
+ for i in range(self.tabs.count()):
76
+ w = self.tabs.widget(i)
77
+ if isinstance(w, SideBySideDiffWidget):
78
+ w.set_dark_theme()
79
+
80
+ def set_light_theme(self):
81
+ """
82
+ Apply light theme to all tabs.
83
+ 對所有分頁套用淺色主題。
84
+ """
85
+ for i in range(self.tabs.count()):
86
+ w = self.tabs.widget(i)
87
+ if isinstance(w, SideBySideDiffWidget):
88
+ w.set_light_theme()