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
je_editor/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- from je_editor.pyside_ui.browser.browser_widget import BrowserWidget
1
+ from je_editor.pyside_ui.browser.main_browser_widget import MainBrowserWidget
2
2
  from je_editor.pyside_ui.code.code_process.code_exec import ExecManager
3
3
  from je_editor.pyside_ui.code.shell_process.shell_exec import ShellManager
4
4
  from je_editor.pyside_ui.code.syntax.python_syntax import PythonHighlighter
@@ -28,7 +28,7 @@ __all__ = [
28
28
  "JEditorRunOnShellException", "JEditorSaveFileException", "syntax_rule_setting_dict",
29
29
  "JEditorOpenFileException", "JEditorContentFileException", "syntax_extend_setting_dict",
30
30
  "JEditorCantFindLanguageException", "JEditorJsonException", "PythonHighlighter",
31
- "user_setting_dict", "user_setting_color_dict", "EditorWidget", "BrowserWidget",
31
+ "user_setting_dict", "user_setting_color_dict", "EditorWidget", "MainBrowserWidget",
32
32
  "ExecManager", "ShellManager", "traditional_chinese_word_dict", "english_word_dict",
33
33
  "language_wrapper"
34
34
  ]
@@ -5,27 +5,54 @@ from queue import Queue
5
5
 
6
6
 
7
7
  class RuffThread(threading.Thread):
8
+ """
9
+ A thread class to run Ruff (a Python linter/formatter) as a subprocess.
10
+ 使用子執行緒執行 Ruff (Python 程式碼檢查/格式化工具)。
11
+ """
8
12
 
9
13
  def __init__(self, ruff_commands: list, std_queue: Queue, stderr_queue: Queue):
14
+ """
15
+ Initialize the RuffThread.
16
+ 初始化 RuffThread。
17
+
18
+ :param ruff_commands: list of commands to run Ruff, e.g. ["ruff", "check"]
19
+ 要執行的 Ruff 指令,例如 ["ruff", "check"]
20
+ :param std_queue: queue to store standard output
21
+ 用來存放標準輸出的佇列
22
+ :param stderr_queue: queue to store error output
23
+ 用來存放錯誤輸出的佇列
24
+ """
10
25
  super().__init__()
11
26
  if ruff_commands is None:
12
27
  self.ruff_commands = ["ruff", "check"]
13
28
  else:
14
29
  self.ruff_commands = ruff_commands
30
+
15
31
  self.ruff_process = None
16
32
  self.std_queue = std_queue
17
33
  self.stderr_queue = stderr_queue
18
34
 
19
35
  def run(self):
36
+ """
37
+ Run the Ruff process in a separate thread.
38
+ 在子執行緒中執行 Ruff 程式。
39
+ """
40
+ # 啟動子程序,捕捉 stdout 與 stderr
20
41
  self.ruff_process = subprocess.Popen(
21
- self.ruff_commands, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1)
42
+ self.ruff_commands,
43
+ stdout=subprocess.PIPE,
44
+ stderr=subprocess.PIPE,
45
+ text=True,
46
+ bufsize=1
47
+ )
48
+
49
+ # 等待子程序結束
22
50
  while self.ruff_process.poll() is None:
23
51
  time.sleep(1)
24
52
  else:
53
+ # 子程序結束後,讀取 stdout 與 stderr
25
54
  for line in self.ruff_process.stdout:
26
- print(line.strip())
27
- for line in self.ruff_process.stderr:
28
- print(line.strip())
29
-
30
-
55
+ self.std_queue.put(line.strip())
31
56
 
57
+ for line in self.ruff_process.stderr:
58
+ self.stderr_queue.put(line.strip())
@@ -1,4 +1,6 @@
1
+ import time
1
2
  from queue import Queue
3
+ from typing import Dict
2
4
 
3
5
  from watchdog.events import FileSystemEventHandler
4
6
 
@@ -6,29 +8,49 @@ from je_editor.code_scan.ruff_thread import RuffThread
6
8
 
7
9
 
8
10
  class RuffPythonFileChangeHandler(FileSystemEventHandler):
9
-
10
- def __init__(self, ruff_commands: list = None):
11
- super(RuffPythonFileChangeHandler, self).__init__()
12
- self.ruff_commands = ruff_commands
13
- self.stdout_queue = Queue()
14
- self.stderr_queue = Queue()
15
- self.ruff_threads_dict = dict()
11
+ """
12
+ File system event handler that runs Ruff when Python files are modified.
13
+ Python 檔案被修改時,自動觸發 Ruff 檢查。
14
+ """
15
+
16
+ def __init__(self, ruff_commands: list = None, debounce_interval: float = 1.0):
17
+ """
18
+ :param ruff_commands: Ruff command list, e.g. ["ruff", "check"]
19
+ :param debounce_interval: Minimum interval (seconds) between re-runs for the same file
20
+ 同一檔案觸發 Ruff 的最小間隔秒數
21
+ """
22
+ super().__init__()
23
+ self.ruff_commands = ruff_commands or ["ruff", "check"]
24
+ self.stdout_queue: Queue = Queue()
25
+ self.stderr_queue: Queue = Queue()
26
+ self.ruff_threads_dict: Dict[str, RuffThread] = {}
27
+ self.last_run_time: Dict[str, float] = {}
28
+ self.debounce_interval = debounce_interval
29
+
30
+ def _start_new_thread(self, file_path: str):
31
+ """Helper to start a new Ruff thread for a given file."""
32
+ ruff_thread = RuffThread(self.ruff_commands, self.stdout_queue, self.stderr_queue)
33
+ self.ruff_threads_dict[file_path] = ruff_thread
34
+ self.last_run_time[file_path] = time.time()
35
+ ruff_thread.start()
16
36
 
17
37
  def on_modified(self, event):
38
+ """Triggered when a file is modified."""
18
39
  if event.is_directory:
19
40
  return
20
- if event.src_path.endswith(".py"):
21
- if self.ruff_threads_dict.get(event.src_path) is None:
22
- ruff_thread = RuffThread(self.ruff_commands, self.stdout_queue, self.stderr_queue)
23
- self.ruff_threads_dict.update({event.src_path: ruff_thread})
24
- ruff_thread.start()
25
- else:
26
- ruff_thread = self.ruff_threads_dict.get(event.src_path)
27
- if not ruff_thread.is_alive():
28
- ruff_thread = RuffThread(self.ruff_commands, self.stdout_queue, self.stderr_queue)
29
- self.ruff_threads_dict.update({event.src_path: ruff_thread})
30
- ruff_thread.start()
31
- else:
32
- pass
33
41
 
42
+ if not event.src_path.endswith(".py"):
43
+ return
44
+
45
+ now = time.time()
46
+ last_time = self.last_run_time.get(event.src_path, 0)
47
+
48
+ # Debounce: skip if last run was too recent
49
+ if now - last_time < self.debounce_interval:
50
+ return
51
+
52
+ ruff_thread = self.ruff_threads_dict.get(event.src_path)
34
53
 
54
+ if ruff_thread is None or not ruff_thread.is_alive():
55
+ self._start_new_thread(event.src_path)
56
+ # else: thread still running, skip
@@ -1,5 +1,7 @@
1
1
  import threading
2
2
  import time
3
+ import sys
4
+ from pathlib import Path
3
5
 
4
6
  from watchdog.observers import Observer
5
7
 
@@ -7,27 +9,70 @@ from je_editor.code_scan.watchdog_implement import RuffPythonFileChangeHandler
7
9
 
8
10
 
9
11
  class WatchdogThread(threading.Thread):
12
+ """
13
+ A thread that runs a watchdog observer to monitor file changes.
14
+ 使用 watchdog 監控檔案變化的執行緒。
15
+ """
10
16
 
11
17
  def __init__(self, check_path: str):
12
- super().__init__()
13
- self.check_path = check_path
18
+ """
19
+ :param check_path: Path to monitor (directory or file)
20
+ 要監控的路徑(資料夾或檔案)
21
+ """
22
+ super().__init__(daemon=True) # 設為 daemon,主程式結束時自動退出
23
+ self.check_path = Path(check_path).resolve()
14
24
  self.ruff_handler = RuffPythonFileChangeHandler()
15
25
  self.running = True
26
+ self.observer = Observer()
16
27
 
17
28
  def run(self):
18
- observer = Observer()
19
- observer.schedule(self.ruff_handler, str(self.check_path), recursive=True)
20
- observer.start()
29
+ """Start the watchdog observer loop."""
30
+ if not self.check_path.exists():
31
+ print(f"[Error] Path does not exist: {self.check_path}", file=sys.stderr)
32
+ return
33
+
34
+ # 設定監控
35
+ self.observer.schedule(self.ruff_handler, str(self.check_path), recursive=True)
36
+ self.observer.start()
37
+ print(f"[Watchdog] Monitoring started on {self.check_path}")
38
+
21
39
  try:
22
40
  while self.running:
23
41
  time.sleep(1)
42
+ # 這裡可以加上 queue 輸出處理
43
+ self._process_ruff_output()
44
+ except KeyboardInterrupt:
45
+ print("[Watchdog] Interrupted by user")
24
46
  finally:
25
- observer.stop()
47
+ self.observer.stop()
48
+ self.observer.join()
49
+ print("[Watchdog] Monitoring stopped")
50
+
51
+ def stop(self):
52
+ """Stop the watchdog thread safely."""
53
+ self.running = False
54
+
55
+ def _process_ruff_output(self):
56
+ """Process stdout/stderr queues from Ruff threads."""
57
+ while not self.ruff_handler.stdout_queue.empty():
58
+ line = self.ruff_handler.stdout_queue.get()
59
+ print(f"[Ruff STDOUT] {line}")
60
+
61
+ while not self.ruff_handler.stderr_queue.empty():
62
+ line = self.ruff_handler.stderr_queue.get()
63
+ print(f"[Ruff STDERR] {line}", file=sys.stderr)
26
64
 
27
65
 
28
66
  if __name__ == '__main__':
29
- watchdog_thread = WatchdogThread("")
67
+ # 預設監控當前目錄
68
+ path_to_watch = "."
69
+ watchdog_thread = WatchdogThread(path_to_watch)
30
70
  watchdog_thread.start()
31
- while True:
32
- time.sleep(1)
33
71
 
72
+ try:
73
+ while True:
74
+ time.sleep(1)
75
+ except KeyboardInterrupt:
76
+ print("[Main] Stopping watchdog...")
77
+ watchdog_thread.stop()
78
+ watchdog_thread.join()
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  from dataclasses import dataclass, field
3
- from typing import List, Dict
3
+ from typing import List, Dict, Any
4
4
 
5
5
  log = logging.getLogger(__name__)
6
6
 
@@ -14,13 +14,17 @@ class CommitNode:
14
14
  parent_shas: List[str]
15
15
  lane_index: int = -1 # assigned later
16
16
 
17
+
17
18
  @dataclass
18
19
  class CommitGraph:
19
20
  nodes: List[CommitNode] = field(default_factory=list)
20
21
  index: Dict[str, int] = field(default_factory=dict) # sha -> row
21
22
 
22
- def build(self, commits: List[Dict], refs: Dict[str, str]) -> None:
23
- # commits are topo-ordered by git_client log --topo-order; we keep it.
23
+ def build(self, commits: List[Dict[str, Any]], refs: Dict[str, str] | None = None) -> None:
24
+ """
25
+ Build commit graph from topo-ordered commits.
26
+ 從 topo-order 的 commits 建立 commit graph。
27
+ """
24
28
  self.nodes = [
25
29
  CommitNode(
26
30
  commit_sha=c["sha"],
@@ -28,61 +32,46 @@ class CommitGraph:
28
32
  commit_date=c["date"],
29
33
  commit_message=c["message"],
30
34
  parent_shas=c["parents"],
31
- ) for c in commits
35
+ )
36
+ for c in commits
32
37
  ]
33
38
  self.index = {n.commit_sha: i for i, n in enumerate(self.nodes)}
34
39
  self._assign_lanes()
35
40
 
36
41
  def _assign_lanes(self) -> None:
37
42
  """
38
- Simple lane assignment similar to 'git_client log --graph' lanes.
39
- Greedy: reuse freed lanes; parents may create new lanes.
43
+ Assign lanes to commits, similar to `git log --graph`.
44
+ 分配 lanes,模擬 `git log --graph` 的效果。
40
45
  """
41
46
  active: Dict[int, str] = {} # lane -> sha
42
47
  free_lanes: List[int] = []
43
48
 
44
- for i, node in enumerate(self.nodes):
45
- # If any active lane points to this commit, use that lane
46
- lane_found = None
47
- for lane, sha in list(active.items()):
48
- if sha == node.commit_sha:
49
- lane_found = lane
50
- break
49
+ for node in self.nodes:
50
+ # Step 1: 找到 lane
51
+ lane_found = next((lane for lane, sha in active.items() if sha == node.commit_sha), None)
51
52
 
52
- if lane_found is None:
53
- if free_lanes:
54
- node.lane_index = free_lanes.pop(0)
55
- else:
56
- node.lane_index = 0 if not active else max(active.keys()) + 1
57
- else:
53
+ if lane_found is not None:
58
54
  node.lane_index = lane_found
55
+ elif free_lanes:
56
+ node.lane_index = free_lanes.pop(0)
57
+ else:
58
+ node.lane_index = 0 if not active else max(active.keys()) + 1
59
59
 
60
- # Update active: current node consumes its lane, parents occupy lanes
61
- # Remove the current sha from any lane that pointed to it
62
- for lane, sha in list(active.items()):
63
- if sha == node.commit_sha:
64
- del active[lane]
60
+ # Step 2: 更新 active
61
+ # 移除舊的 sha
62
+ active = {lane: sha for lane, sha in active.items() if sha != node.commit_sha}
65
63
 
66
- # First parent continues in the same lane; others go to free/new lanes
64
+ # 父節點分配 lane
67
65
  if node.parent_shas:
68
- first = node.parent_shas[0]
69
- active[node.lane_index] = first
70
- # Side branches
66
+ first_parent = node.parent_shas[0]
67
+ active[node.lane_index] = first_parent
71
68
  for p in node.parent_shas[1:]:
72
- # Pick a free lane or new one
73
- if free_lanes:
74
- pl = free_lanes.pop(0)
75
- else:
76
- pl = 0 if not active else max(active.keys()) + 1
69
+ pl = free_lanes.pop(0) if free_lanes else (max(active.keys()) + 1)
77
70
  active[pl] = p
78
71
 
79
- # Any lane whose target no longer appears in the future will be freed later
80
- # We approximate by freeing lanes when a target didn't appear in the next rows;
81
- # but for minimal viable, free when lane not reassigned by parents this row.
82
- used_lanes = set(active.keys())
83
- # Collect gaps below max lane as free lanes to reuse
84
- max_lane = max(used_lanes) if used_lanes else -1
85
- present = set(range(max_lane + 1))
86
- missing = sorted(list(present - used_lanes))
87
- # Merge missing into free_lanes maintaining order
88
- free_lanes = sorted(set(free_lanes + missing))
72
+ # Step 3: 更新 free_lanes
73
+ if active:
74
+ max_lane = max(active.keys())
75
+ used = set(active.keys())
76
+ all_lanes = set(range(max_lane + 1))
77
+ free_lanes = sorted(set(free_lanes).union(all_lanes - used))
@@ -160,7 +160,7 @@ NULL_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
160
160
  # -----------------------
161
161
  # Worker thread wrapper
162
162
  # -----------------------
163
- class Worker(QThread):
163
+ class GitWorker(QThread):
164
164
  """
165
165
  Runs a function in a separate thread to avoid blocking the UI.
166
166
  Emits (result, error) when done.
@@ -11,12 +11,12 @@ class GitCLI:
11
11
  self.repo_path = Path(repo_path)
12
12
 
13
13
  def is_git_repo(self) -> bool:
14
- return (self.repo_path / ".git_client").exists()
14
+ return (self.repo_path / ".git").exists()
15
15
 
16
16
  def _run(self, args: List[str]) -> str:
17
- log.debug("git_client %s", " ".join(args))
17
+ log.debug("git %s", " ".join(args))
18
18
  res = subprocess.run(
19
- ["git_client"] + args,
19
+ ["git"] + args,
20
20
  cwd=self.repo_path,
21
21
  stdout=subprocess.PIPE,
22
22
  stderr=subprocess.PIPE,
@@ -63,4 +63,4 @@ class GitCLI:
63
63
  "date": date,
64
64
  "message": msg,
65
65
  })
66
- return commits
66
+ return commits
@@ -7,33 +7,69 @@ from je_editor.utils.multi_language.multi_language_wrapper import language_wrapp
7
7
 
8
8
 
9
9
  class BrowserDownloadWindow(QWidget):
10
+ """
11
+ A window widget to display details of a browser download.
12
+ 瀏覽器下載視窗,用來顯示下載的詳細資訊。
13
+ """
10
14
 
11
15
  def __init__(self, download_instance: QWebEngineDownloadRequest):
16
+ """
17
+ Initialize the download window with a given QWebEngineDownloadRequest.
18
+ 使用指定的 QWebEngineDownloadRequest 初始化下載視窗。
19
+ """
12
20
  super().__init__()
21
+ # 記錄初始化訊息到 logger
13
22
  jeditor_logger.info("Init BrowserDownloadWindow "
14
23
  f"download_instance: {download_instance}")
24
+
25
+ # 設定視窗屬性:當視窗關閉時自動刪除
15
26
  self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
27
+
28
+ # 建立垂直方向的 BoxLayout
16
29
  self.box_layout = QBoxLayout(QBoxLayout.Direction.TopToBottom)
30
+
31
+ # 建立文字框來顯示下載細節,並設為唯讀
17
32
  self.show_download_detail_plaintext = QPlainTextEdit()
18
33
  self.show_download_detail_plaintext.setReadOnly(True)
34
+
35
+ # 設定視窗標題,支援多語言
19
36
  self.setWindowTitle(language_wrapper.language_word_dict.get("browser_download_detail"))
37
+
38
+ # 儲存下載實例
20
39
  self.download_instance = download_instance
21
- self.download_instance.isFinishedChanged.connect(self.print_finish)
22
- self.download_instance.interruptReasonChanged.connect(self.print_interrupt)
23
- self.download_instance.stateChanged.connect(self.print_state)
40
+
41
+ # 綁定下載事件到對應的處理函式
42
+ self.download_instance.isFinishedChanged.connect(self.print_finish) # 當下載完成時
43
+ self.download_instance.interruptReasonChanged.connect(self.print_interrupt) # 當下載被中斷時
44
+ self.download_instance.stateChanged.connect(self.print_state) # 當下載狀態改變時
45
+
46
+ # 接受下載請求,開始下載
24
47
  self.download_instance.accept()
48
+
49
+ # 將文字框加入版面配置
25
50
  self.box_layout.addWidget(self.show_download_detail_plaintext)
26
51
  self.setLayout(self.box_layout)
27
52
 
28
-
29
53
  def print_finish(self):
54
+ """
55
+ Slot function triggered when download finishes.
56
+ 當下載完成時觸發,將完成狀態輸出到 logger 與文字框。
57
+ """
30
58
  jeditor_logger.info("BrowserDownloadWindow Print Download is Finished")
31
59
  self.show_download_detail_plaintext.appendPlainText(str(self.download_instance.isFinished()))
32
60
 
33
61
  def print_interrupt(self):
62
+ """
63
+ Slot function triggered when download is interrupted.
64
+ 當下載被中斷時觸發,將中斷原因輸出到 logger 與文字框。
65
+ """
34
66
  jeditor_logger.info("BrowserDownloadWindow Print interruptReason")
35
67
  self.show_download_detail_plaintext.appendPlainText(str(self.download_instance.interruptReason()))
36
68
 
37
69
  def print_state(self):
70
+ """
71
+ Slot function triggered when download state changes.
72
+ 當下載狀態改變時觸發,將狀態輸出到 logger 與文字框。
73
+ """
38
74
  jeditor_logger.info("BrowserDownloadWindow Print State")
39
- self.show_download_detail_plaintext.appendPlainText(str(self.download_instance.state()))
75
+ self.show_download_detail_plaintext.appendPlainText(str(self.download_instance.state()))
@@ -8,20 +8,44 @@ from PySide6.QtWidgets import QLineEdit
8
8
  from je_editor.utils.logging.loggin_instance import jeditor_logger
9
9
 
10
10
  if TYPE_CHECKING:
11
+ # Forward declaration to avoid circular import at runtime
12
+ # 僅在型別檢查時匯入,避免執行時循環匯入問題
11
13
  from je_editor.pyside_ui.browser.browser_widget import BrowserWidget
12
14
 
13
15
 
14
16
  class BrowserLineSearch(QLineEdit):
17
+ """
18
+ A custom QLineEdit widget for browser search input.
19
+ 自訂的 QLineEdit,用於瀏覽器搜尋輸入。
20
+ """
15
21
 
16
22
  def __init__(self, browser_widget: BrowserWidget):
23
+ """
24
+ Initialize the search line with a reference to the browser widget.
25
+ 初始化搜尋輸入框,並保存瀏覽器元件的參考。
26
+ """
17
27
  super().__init__()
28
+ # 記錄初始化訊息到 logger
18
29
  jeditor_logger.info("Init BrowserLineSearch "
19
30
  f"browser_widget: {browser_widget}")
31
+
32
+ # 設定屬性:當視窗關閉時自動刪除
20
33
  self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
34
+
35
+ # 保存瀏覽器元件的參考,用於觸發搜尋
21
36
  self.browser = browser_widget
22
37
 
23
38
  def keyPressEvent(self, event) -> None:
39
+ """
40
+ Handle key press events.
41
+ 當使用者按下按鍵時觸發:
42
+ - 如果是 Enter 或 Return,則呼叫瀏覽器的 search() 方法。
43
+ - 其他情況則交由父類別處理。
44
+ """
24
45
  if event.key() in [Qt.Key.Key_Enter, Qt.Key.Key_Return]:
25
46
  jeditor_logger.info("Browser Search")
47
+ # 呼叫瀏覽器元件的搜尋方法
26
48
  self.browser.search()
27
- super().keyPressEvent(event)
49
+
50
+ # 呼叫父類別的 keyPressEvent,確保其他按鍵行為正常
51
+ super().keyPressEvent(event)
@@ -1,4 +1,11 @@
1
- from typing import List
1
+ from __future__ import annotations
2
+
3
+ from typing import List, TYPE_CHECKING
4
+
5
+ from PySide6.QtCore import Signal
6
+
7
+ if TYPE_CHECKING:
8
+ from je_editor.pyside_ui.browser.browser_widget import BrowserWidget
2
9
 
3
10
  from PySide6.QtWebEngineCore import QWebEngineDownloadRequest
4
11
  from PySide6.QtWebEngineWidgets import QWebEngineView
@@ -8,28 +15,74 @@ from je_editor.utils.logging.loggin_instance import jeditor_logger
8
15
 
9
16
 
10
17
  class BrowserView(QWebEngineView):
18
+ new_tab_requested = Signal(QWebEngineView)
19
+ """
20
+ A custom QWebEngineView that supports file downloads and manages download windows.
21
+ 自訂的 QWebEngineView,支援檔案下載並管理下載視窗。
22
+ """
11
23
 
12
- def __init__(self, start_url: str = "https://www.google.com/"):
13
- super().__init__()
24
+ def __init__(self, start_url: str = "https://www.google.com/",
25
+ main_widget: BrowserWidget = None, parent=None):
26
+ """
27
+ Initialize the browser view with a start URL.
28
+ 使用指定的起始網址初始化瀏覽器視圖。
29
+ """
30
+ super().__init__(parent)
31
+ # 記錄初始化訊息
14
32
  jeditor_logger.info("Init BrowserView "
15
33
  f"start_url: {start_url}")
34
+
35
+ # 設定初始網址
16
36
  self.setUrl(start_url)
37
+
38
+ # 儲存下載請求的清單
17
39
  self.download_list: List[QWebEngineDownloadRequest] = list()
40
+
41
+ # 儲存下載視窗的清單
18
42
  self.download_window_list: List[BrowserDownloadWindow] = list()
43
+
44
+ # 綁定下載事件:當有下載請求時觸發 download_file
19
45
  self.page().profile().downloadRequested.connect(self.download_file)
20
46
 
47
+ self.main_widget = main_widget
48
+
49
+
21
50
  def download_file(self, download_instance: QWebEngineDownloadRequest):
51
+ """
52
+ Handle a new download request.
53
+ 當有新的下載請求時觸發:
54
+ - 將下載請求加入清單
55
+ - 建立並顯示下載細節視窗
56
+ """
22
57
  jeditor_logger.info("Download File "
23
58
  f"download_instance: {download_instance}")
59
+
60
+ # 加入下載請求到清單
24
61
  self.download_list.append(download_instance)
62
+
63
+ # 建立下載細節視窗
25
64
  download_detail_window = BrowserDownloadWindow(download_instance)
65
+
66
+ # 加入視窗到清單並顯示
26
67
  self.download_window_list.append(download_detail_window)
27
68
  download_detail_window.show()
28
69
 
29
70
  def closeEvent(self, event) -> None:
71
+ """
72
+ Handle the close event of the browser view.
73
+ 當瀏覽器視窗關閉時:
74
+ - 取消所有進行中的下載
75
+ - 關閉所有下載細節視窗
76
+ """
30
77
  jeditor_logger.info(f"BrowserView closeEvent event: {event}")
78
+
79
+ # 取消所有下載
31
80
  for download_instance in self.download_list:
32
81
  download_instance.cancel()
82
+
83
+ # 關閉所有下載視窗
33
84
  for download_window in self.download_window_list:
34
85
  download_window.close()
86
+
87
+ # 呼叫父類別的 closeEvent,確保正常關閉
35
88
  super().closeEvent(event)