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

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
  ]
@@ -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,17 +15,19 @@ 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)
11
19
  """
12
20
  A custom QWebEngineView that supports file downloads and manages download windows.
13
21
  自訂的 QWebEngineView,支援檔案下載並管理下載視窗。
14
22
  """
15
23
 
16
- def __init__(self, start_url: str = "https://www.google.com/"):
24
+ def __init__(self, start_url: str = "https://www.google.com/",
25
+ main_widget: BrowserWidget = None, parent=None):
17
26
  """
18
27
  Initialize the browser view with a start URL.
19
28
  使用指定的起始網址初始化瀏覽器視圖。
20
29
  """
21
- super().__init__()
30
+ super().__init__(parent)
22
31
  # 記錄初始化訊息
23
32
  jeditor_logger.info("Init BrowserView "
24
33
  f"start_url: {start_url}")
@@ -35,6 +44,9 @@ class BrowserView(QWebEngineView):
35
44
  # 綁定下載事件:當有下載請求時觸發 download_file
36
45
  self.page().profile().downloadRequested.connect(self.download_file)
37
46
 
47
+ self.main_widget = main_widget
48
+
49
+
38
50
  def download_file(self, download_instance: QWebEngineDownloadRequest):
39
51
  """
40
52
  Handle a new download request.
@@ -73,4 +85,4 @@ class BrowserView(QWebEngineView):
73
85
  download_window.close()
74
86
 
75
87
  # 呼叫父類別的 closeEvent,確保正常關閉
76
- super().closeEvent(event)
88
+ super().closeEvent(event)
@@ -1,46 +1,47 @@
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
- """
12
- A composite browser widget with navigation buttons, search bar, and embedded BrowserView.
13
- 瀏覽器元件:包含導覽按鈕、搜尋列,以及內嵌的 BrowserView。
14
- """
15
18
 
19
+ class BrowserWidget(QWidget):
16
20
  def __init__(self, start_url: str = "https://www.google.com/",
17
- search_prefix: str = "https://www.google.com.tw/search?q="):
18
- """
19
- Initialize the browser widget with a start URL and a search prefix.
20
- 使用起始網址與搜尋前綴字串初始化瀏覽器元件。
21
- """
22
- super().__init__()
23
- jeditor_logger.info("Init BrowserWidget "
24
- f"start_url: {start_url} "
25
- f"search_prefix: {search_prefix}")
26
-
21
+ search_prefix: str = "https://www.google.com.tw/search?q=",
22
+ main_widget: MainBrowserWidget = None, browser_view: BrowserView = None):
27
23
  # --- Browser setting / 瀏覽器設定 ---
28
- self.browser = BrowserView(start_url) # 內嵌的瀏覽器視圖
29
- self.search_prefix = search_prefix # 搜尋引擎前綴字串
24
+ super().__init__()
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 # 搜尋引擎前綴字串
30
31
  self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
31
32
 
32
33
  # --- Top bar buttons / 上方工具列按鈕 ---
33
34
  self.back_button = QPushButton(language_wrapper.language_word_dict.get("browser_back_button"))
34
- self.back_button.clicked.connect(self.browser.back) # 返回上一頁
35
+ self.back_button.clicked.connect(self.browser.back) # 返回上一頁
35
36
 
36
37
  self.forward_button = QPushButton(language_wrapper.language_word_dict.get("browser_forward_button"))
37
38
  self.forward_button.clicked.connect(self.browser.forward) # 前往下一頁
38
39
 
39
40
  self.reload_button = QPushButton(language_wrapper.language_word_dict.get("browser_reload_button"))
40
- self.reload_button.clicked.connect(self.browser.reload) # 重新整理
41
+ self.reload_button.clicked.connect(self.browser.reload) # 重新整理
41
42
 
42
43
  self.search_button = QPushButton(language_wrapper.language_word_dict.get("browser_search_button"))
43
- self.search_button.clicked.connect(self.search) # 搜尋按鈕
44
+ self.search_button.clicked.connect(self.search) # 搜尋按鈕
44
45
 
45
46
  # URL / Search input line (custom QLineEdit)
46
47
  self.url_input = BrowserLineSearch(self)
@@ -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()
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
- from PySide6.QtCore import Qt
3
+ from PySide6.QtCore import Qt, QTimer
4
4
  from PySide6.QtGui import QTextOption, QTextCharFormat, QColor, QFont, QSyntaxHighlighter, QAction
5
5
  from PySide6.QtWidgets import (
6
6
  QWidget, QVBoxLayout, QHBoxLayout, QLabel,
@@ -15,9 +15,9 @@ class GitChangeItem:
15
15
  簡單的資料結構,用來存放檔案變更資訊。
16
16
  """
17
17
 
18
- def __init__(self, file_path: str, change_status: str):
19
- self.file_path = file_path # repo 相對路徑 / repo-relative path
20
- self.change_status = change_status # 狀態,例如 'untracked', 'modified', 'deleted', 'renamed', 'staged'
18
+ def __init__(self, path: str, status: str):
19
+ self.path = path # repo 相對路徑 / repo-relative path
20
+ self.status = status # 狀態,例如 'untracked', 'modified', 'deleted', 'renamed', 'staged'
21
21
 
22
22
 
23
23
  class GitGui(QWidget):
@@ -36,6 +36,7 @@ class GitGui(QWidget):
36
36
  self.checkout_button = QPushButton("Checkout")
37
37
  self.clone_repo_button = QPushButton("Clone Repo")
38
38
  self.repo_status_label = QLabel("Status: -")
39
+ self.commit_status_label = QLabel("Unpushed commits: ...")
39
40
 
40
41
  top = QHBoxLayout()
41
42
  top.addWidget(self.repo_path_label, 1)
@@ -48,7 +49,6 @@ class GitGui(QWidget):
48
49
  # === Left: changes list / 左側:變更清單 ===
49
50
  self.changes_list_widget = QListWidget()
50
51
  self.changes_list_widget.setSelectionMode(QListWidget.SelectionMode.SingleSelection)
51
- self.changes_list_widget.setMinimumWidth(420)
52
52
 
53
53
  # === Right: diff / info viewer / 右側:差異或資訊檢視器 ===
54
54
  self.diff_viewer = QPlainTextEdit()
@@ -84,6 +84,7 @@ class GitGui(QWidget):
84
84
  self.commit_button = QPushButton("Commit")
85
85
  self.unstage_all_button = QPushButton("Unstage All")
86
86
  self.track_all_untracked_button = QPushButton("Track All Untracked")
87
+ self.git_push_button = QPushButton("Push")
87
88
 
88
89
  bottom = QHBoxLayout()
89
90
  bottom.addWidget(QLabel("Message:"))
@@ -94,11 +95,13 @@ class GitGui(QWidget):
94
95
  bottom.addWidget(self.stage_all_button)
95
96
  bottom.addWidget(self.commit_button)
96
97
  bottom.addWidget(self.track_all_untracked_button)
98
+ bottom.addWidget(self.git_push_button)
97
99
 
98
100
  # === Main layout / 主版面配置 ===
99
101
  center_layout = QVBoxLayout()
100
102
  center_layout.addLayout(top)
101
103
  center_layout.addWidget(self.repo_status_label)
104
+ center_layout.addWidget(self.commit_status_label)
102
105
  center_layout.addWidget(splitter, 1)
103
106
  center_layout.addLayout(bottom)
104
107
  self.setLayout(center_layout)
@@ -115,6 +118,7 @@ class GitGui(QWidget):
115
118
  self.commit_button.clicked.connect(self.on_commit_staged_changes)
116
119
  self.unstage_all_button.clicked.connect(self.on_unstage_all_changes)
117
120
  self.track_all_untracked_button.clicked.connect(self.on_track_all_untracked_files)
121
+ self.git_push_button.clicked.connect(self.on_push_to_github)
118
122
 
119
123
  self._update_ui_controls(enabled=False)
120
124
 
@@ -133,6 +137,12 @@ class GitGui(QWidget):
133
137
 
134
138
  center_layout.setMenuBar(menubar)
135
139
 
140
+ # === Timer ===
141
+ self.update_commit_status_timer = QTimer()
142
+ self.update_commit_status_timer.setInterval(1000)
143
+ self.update_commit_status_timer.timeout.connect(self.update_commit_status)
144
+ self.update_commit_status_timer.start()
145
+
136
146
  # ---------- Repo operations ----------
137
147
  # ---------- 儲存庫操作 ----------
138
148
 
@@ -258,16 +268,16 @@ class GitGui(QWidget):
258
268
 
259
269
  # Normalize paths (for rename "a -> b" 取目的與來源)
260
270
  src, dst = None, None
261
- if "->" in change.file_path and change.change_status in ("renamed", "staged"):
262
- parts = [p.strip() for p in change.file_path.split("->")]
271
+ if "->" in change.path and change.status in ("renamed", "staged"):
272
+ parts = [p.strip() for p in change.path.split("->")]
263
273
  if len(parts) == 2:
264
274
  src, dst = parts
265
- rel = dst or change.file_path
275
+ rel = dst or change.path
266
276
  abs_path = Path(current_repo.working_tree_dir) / Path(rel)
267
277
 
268
278
  try:
269
279
  # Untracked: 顯示新增檔案的內容(模擬 unified diff)
270
- if change.change_status == "untracked":
280
+ if change.status == "untracked":
271
281
  if not abs_path.exists():
272
282
  self._safe_set_diff_text(f"(untracked file missing: {rel})")
273
283
  return
@@ -282,7 +292,7 @@ class GitGui(QWidget):
282
292
  return
283
293
 
284
294
  # Deleted: 顯示與 HEAD/Index 的差異(若工作樹檔案不存在也能呈現)
285
- if change.change_status == "deleted":
295
+ if change.status == "deleted":
286
296
  # working tree vs index(未暫存刪除)
287
297
  if rel and not current_repo.index.diff(None, paths=[rel]):
288
298
  # 若 diff(None) 空,試試 index vs HEAD
@@ -296,7 +306,7 @@ class GitGui(QWidget):
296
306
  return
297
307
 
298
308
  # Renamed: 顯示 rename 變更;若僅 rename 無內容改動,diff 可能為空
299
- if change.change_status == "renamed":
309
+ if change.status == "renamed":
300
310
  # 優先顯示 staged rename
301
311
  diff_text = ""
302
312
  try:
@@ -319,7 +329,7 @@ class GitGui(QWidget):
319
329
  return
320
330
 
321
331
  # Staged vs HEAD
322
- if change.change_status == "staged":
332
+ if change.status == "staged":
323
333
  diff_text = current_repo.git.diff("--cached", rel)
324
334
  if not diff_text.strip():
325
335
  self._safe_set_diff_text("(no staged changes vs HEAD)")
@@ -328,7 +338,7 @@ class GitGui(QWidget):
328
338
  return
329
339
 
330
340
  # Modified / general unstaged
331
- if change.change_status in ("modified",):
341
+ if change.status in ("modified",):
332
342
  # working tree vs index
333
343
  diff_text = current_repo.git.diff(rel)
334
344
  if not diff_text.strip():
@@ -344,7 +354,7 @@ class GitGui(QWidget):
344
354
  return
345
355
 
346
356
  # Fallback
347
- self._safe_set_diff_text(f"(no diff handler for status: {change.change_status})")
357
+ self._safe_set_diff_text(f"(no diff handler for status: {change.status})")
348
358
 
349
359
  except GitCommandError as e:
350
360
  self._safe_set_diff_text(f"Git error while generating diff:\n{e}")
@@ -433,7 +443,7 @@ class GitGui(QWidget):
433
443
  Add a file change entry to the list.
434
444
  將檔案變更項目加入清單。
435
445
  """
436
- item_text = f"[{change.change_status}] {change.file_path}"
446
+ item_text = f"[{change.status}] {change.path}"
437
447
  list_item = QListWidgetItem(item_text)
438
448
  list_item.setData(Qt.ItemDataRole.UserRole, change) # 存放 GitChangeItem 物件
439
449
  list_item.setCheckState(Qt.CheckState.Unchecked) # 預設未勾選
@@ -513,6 +523,7 @@ class GitGui(QWidget):
513
523
  try:
514
524
  file_paths = []
515
525
  for change_entry in selected_changes:
526
+ print(change_entry.path)
516
527
  if "->" in change_entry.path and change_entry.status in ("renamed", "staged"):
517
528
  parts = change_entry.path.split("->")
518
529
  source_path = parts[0].strip()
@@ -582,7 +593,7 @@ class GitGui(QWidget):
582
593
  self.branch_selector, self.checkout_button, self.changes_list_widget,
583
594
  self.diff_viewer, self.commit_message_input, self.stage_selected_button,
584
595
  self.unstage_selected_button, self.stage_all_button, self.commit_button,
585
- self.unstage_all_button, self.track_all_untracked_button
596
+ self.unstage_all_button, self.track_all_untracked_button, self.git_push_button
586
597
  ):
587
598
  widget.setEnabled(enabled)
588
599
 
@@ -655,6 +666,60 @@ class GitGui(QWidget):
655
666
  except GitCommandError as e:
656
667
  QMessageBox.critical(self, "Clone Error", str(e))
657
668
 
669
+ # ===== GitHub =====
670
+
671
+ def on_push_to_github(self):
672
+ if not self.current_repo:
673
+ QMessageBox.warning(self, "Warning", "No repository opened.")
674
+ return
675
+ try:
676
+ origin = self.current_repo.remote(name="origin")
677
+ result = origin.push()
678
+ msg = "\n".join(str(r) for r in result)
679
+ QMessageBox.information(self, "Push Result", f"Pushed to origin:\n{msg}")
680
+ except Exception as e:
681
+ QMessageBox.critical(self, "Track Untracked Error", str(e))
682
+
683
+ def get_unpushed_commit_count(self, remote_name: str = "origin") -> dict:
684
+ try:
685
+ repo = self.current_repo
686
+ if repo is None:
687
+ return {"ahead": 0, "behind": 0, "error": "No repo loaded"}
688
+ if repo.bare:
689
+ return {"ahead": 0, "behind": 0, "error": "Repository is bare"}
690
+ if repo.head.is_detached:
691
+ return {"ahead": 0, "behind": 0, "error": "HEAD is detached"}
692
+
693
+ branch = repo.active_branch
694
+ remote = repo.remote(remote_name)
695
+ remote.fetch()
696
+
697
+ upstream_ref = f"{remote_name}/{branch.name}"
698
+ if upstream_ref not in repo.refs:
699
+ return {"ahead": 0, "behind": 0, "error": f"No upstream branch for {branch.name}"}
700
+
701
+ ahead_commits = list(repo.iter_commits(f"{upstream_ref}..{branch.name}"))
702
+ behind_commits = list(repo.iter_commits(f"{branch.name}..{upstream_ref}"))
703
+
704
+ return {"ahead": len(ahead_commits), "behind": len(behind_commits), "error": None}
705
+
706
+ except GitCommandError as e:
707
+ return {"ahead": 0, "behind": 0, "error": f"Git error: {e}"}
708
+ except Exception as e:
709
+ return {"ahead": 0, "behind": 0, "error": str(e)}
710
+
711
+ def update_commit_status(self):
712
+ result = self.get_unpushed_commit_count()
713
+ if result["error"]:
714
+ self.commit_status_label.setText(f"Error: {result['error']}")
715
+ else:
716
+ self.commit_status_label.setText(
717
+ f"Ahead (push): {result['ahead']} | Behind (pull): {result['behind']}"
718
+ )
719
+
720
+
721
+ # ===== Theme =====
722
+
658
723
  def apply_light_theme(self):
659
724
  """
660
725
  Switch to light theme highlighting.
@@ -8,10 +8,10 @@ import jedi.settings
8
8
  from PySide6.QtCore import QTimer, QEvent
9
9
  from PySide6.QtGui import QFontDatabase, QIcon, Qt, QTextCharFormat
10
10
  from PySide6.QtWidgets import QMainWindow, QWidget, QTabWidget
11
- from frontengine import FrontEngineMainUI
12
11
  from qt_material import QtStyleTools
13
12
 
14
13
  from je_editor.pyside_ui.browser.browser_widget import BrowserWidget
14
+ from je_editor.pyside_ui.browser.main_browser_widget import MainBrowserWidget
15
15
  from je_editor.pyside_ui.code.auto_save.auto_save_manager import init_new_auto_save_thread, file_is_open_manager_dict
16
16
  from je_editor.pyside_ui.main_ui.editor.editor_widget import EditorWidget
17
17
  from je_editor.pyside_ui.main_ui.menu.set_menu_bar import set_menu_bar
@@ -104,11 +104,11 @@ class EditorMain(QMainWindow, QtStyleTools):
104
104
  self.redirect_timer.timeout.connect(self.redirect)
105
105
  self.redirect_timer.start()
106
106
  # TAB Add
107
+ main_browser_widget = MainBrowserWidget()
107
108
  self.tab_widget.addTab(EditorWidget(self), language_wrapper.language_word_dict.get("tab_name_editor"))
108
- self.tab_widget.addTab(BrowserWidget(), language_wrapper.language_word_dict.get("tab_name_web_browser"))
109
- self.tab_widget.addTab(
110
- BrowserWidget(start_url="https://stackoverflow.com/", search_prefix="https://stackoverflow.com/search?q="),
111
- language_wrapper.language_word_dict.get("tab_menu_stackoverflow_tab_name"))
109
+ self.tab_widget.addTab(main_browser_widget, language_wrapper.language_word_dict.get("tab_name_web_browser"))
110
+ main_browser_widget.add_browser_tab(
111
+ BrowserWidget(start_url="https://stackoverflow.com/", search_prefix="https://stackoverflow.com/search?q="))
112
112
 
113
113
  for widget_name, widget in EDITOR_EXTEND_TAB.items():
114
114
  self.tab_widget.addTab(widget(), widget_name)
@@ -9,7 +9,7 @@ from PySide6.QtWidgets import QFileDialog
9
9
  from frontengine import FrontEngineMainUI
10
10
 
11
11
  from je_editor.pyside_ui.git_ui.code_diff_compare.code_diff_viewer_widget import DiffViewerWidget
12
- from je_editor.pyside_ui.browser.browser_widget import BrowserWidget
12
+ from je_editor.pyside_ui.browser.main_browser_widget import MainBrowserWidget
13
13
  from je_editor.pyside_ui.code.variable_inspector.inspector_gui import VariableInspector
14
14
  from je_editor.pyside_ui.git_ui.git_client.git_branch_tree_widget import GitTreeViewGUI
15
15
  from je_editor.pyside_ui.git_ui.git_client.git_client_gui import GitGui
@@ -37,13 +37,6 @@ def set_dock_menu(ui_we_want_to_set: EditorMain) -> None:
37
37
  lambda: add_dock_widget(ui_we_want_to_set)
38
38
  )
39
39
  ui_we_want_to_set.dock_menu.addAction(ui_we_want_to_set.dock_menu.new_dock_browser_action)
40
- # Stackoverflow
41
- ui_we_want_to_set.dock_menu.new_dock_stackoverflow_action = QAction(
42
- language_wrapper.language_word_dict.get("dock_stackoverflow_label"))
43
- ui_we_want_to_set.dock_menu.new_dock_stackoverflow_action.triggered.connect(
44
- lambda: add_dock_widget(ui_we_want_to_set, "stackoverflow")
45
- )
46
- ui_we_want_to_set.dock_menu.addAction(ui_we_want_to_set.dock_menu.new_dock_stackoverflow_action)
47
40
  # Editor
48
41
  ui_we_want_to_set.dock_menu.new_tab_dock_editor_action = QAction(
49
42
  language_wrapper.language_word_dict.get("dock_editor_label"))
@@ -114,11 +107,7 @@ def add_dock_widget(ui_we_want_to_set: EditorMain, widget_type: str = None):
114
107
  f"widget_type: {widget_type}")
115
108
  # Dock widget
116
109
  dock_widget = DestroyDock()
117
- if widget_type == "stackoverflow":
118
- dock_widget.setWindowTitle("stackoverflow")
119
- dock_widget.setWidget(BrowserWidget(
120
- start_url="https://stackoverflow.com/", search_prefix="https://stackoverflow.com/search?q="))
121
- elif widget_type == "editor":
110
+ if widget_type == "editor":
122
111
  file_path = QFileDialog().getOpenFileName(
123
112
  parent=ui_we_want_to_set,
124
113
  dir=str(Path.cwd())
@@ -157,6 +146,6 @@ def add_dock_widget(ui_we_want_to_set: EditorMain, widget_type: str = None):
157
146
  dock_widget.setWidget(DiffViewerWidget())
158
147
  else:
159
148
  dock_widget.setWindowTitle(language_wrapper.language_word_dict.get("dock_browser_title"))
160
- dock_widget.setWidget(BrowserWidget())
149
+ dock_widget.setWidget(MainBrowserWidget())
161
150
  if dock_widget.widget() is not None:
162
151
  ui_we_want_to_set.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, dock_widget)
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from je_editor.pyside_ui.browser.browser_widget import BrowserWidget
5
+ from je_editor.pyside_ui.browser.main_browser_widget import MainBrowserWidget
6
6
  from je_editor.utils.logging.loggin_instance import jeditor_logger
7
7
 
8
8
  if TYPE_CHECKING:
@@ -53,7 +53,7 @@ def open_web_browser(ui_we_want_to_set: EditorMain, url: str, tab_name: str):
53
53
  f"url: {url} "
54
54
  f"tab_name: {tab_name}")
55
55
  ui_we_want_to_set.tab_widget.addTab(
56
- BrowserWidget(start_url=url),
56
+ MainBrowserWidget(start_url=url),
57
57
  f"{tab_name}{ui_we_want_to_set.tab_widget.count()}"
58
58
  )
59
59
 
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
5
5
  from frontengine import FrontEngineMainUI
6
6
 
7
7
  from je_editor.pyside_ui.git_ui.code_diff_compare.code_diff_viewer_widget import DiffViewerWidget
8
- from je_editor.pyside_ui.browser.browser_widget import BrowserWidget
8
+ from je_editor.pyside_ui.browser.main_browser_widget import MainBrowserWidget
9
9
  from je_editor.pyside_ui.code.variable_inspector.inspector_gui import VariableInspector
10
10
  from je_editor.pyside_ui.git_ui.git_client.git_branch_tree_widget import GitTreeViewGUI
11
11
  from je_editor.pyside_ui.git_ui.git_client.git_client_gui import GitGui
@@ -47,13 +47,6 @@ def set_tab_menu(ui_we_want_to_set: EditorMain) -> None:
47
47
  lambda: add_web_tab(ui_we_want_to_set)
48
48
  )
49
49
  ui_we_want_to_set.tab_menu.addAction(ui_we_want_to_set.tab_menu.add_web_action)
50
- # Stackoverflow
51
- ui_we_want_to_set.tab_menu.add_stackoverflow_action = QAction(
52
- language_wrapper.language_word_dict.get("tab_menu_add_stackoverflow_label"))
53
- ui_we_want_to_set.tab_menu.add_stackoverflow_action.triggered.connect(
54
- lambda: add_stackoverflow_tab(ui_we_want_to_set)
55
- )
56
- ui_we_want_to_set.tab_menu.addAction(ui_we_want_to_set.tab_menu.add_stackoverflow_action)
57
50
  # IPython
58
51
  ui_we_want_to_set.tab_menu.add_ipython_action = QAction(
59
52
  language_wrapper.language_word_dict.get("tab_menu_ipython_tab_name"))
@@ -125,19 +118,11 @@ def add_frontengine_tab(ui_we_want_to_set: EditorMain):
125
118
  def add_web_tab(ui_we_want_to_set: EditorMain):
126
119
  jeditor_logger.info(f"build_tab_menu.py add web tab ui_we_want_to_set: {ui_we_want_to_set}")
127
120
  ui_we_want_to_set.tab_widget.addTab(
128
- BrowserWidget(),
121
+ MainBrowserWidget(),
129
122
  f"{language_wrapper.language_word_dict.get('tab_menu_web_tab_name')} "
130
123
  f"{ui_we_want_to_set.tab_widget.count()}")
131
124
 
132
125
 
133
- def add_stackoverflow_tab(ui_we_want_to_set: EditorMain):
134
- jeditor_logger.info(f"build_tab_menu.py add stackoverflow tab ui_we_want_to_set: {ui_we_want_to_set}")
135
- ui_we_want_to_set.tab_widget.addTab(
136
- BrowserWidget(start_url="https://stackoverflow.com/", search_prefix="https://stackoverflow.com/search?q="),
137
- f"{language_wrapper.language_word_dict.get('tab_menu_stackoverflow_tab_name')} "
138
- f"{ui_we_want_to_set.tab_widget.count()}")
139
-
140
-
141
126
  def add_ipython_tab(ui_we_want_to_set: EditorMain):
142
127
  jeditor_logger.info(f"build_tab_menu.py add ipython tab ui_we_want_to_set: {ui_we_want_to_set}")
143
128
  ui_we_want_to_set.tab_widget.addTab(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: je_editor
3
- Version: 0.0.222
3
+ Version: 0.0.223
4
4
  Summary: JEditor is basic but powerful editor include GPT
5
5
  Author-email: JE-Chen <jechenmailman@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/JE-Chen/je_editor
@@ -1,4 +1,4 @@
1
- je_editor/__init__.py,sha256=IZm-mUID0W7-smb9SYEtwcWrpr8mz6OyF4e5akHGrFU,2405
1
+ je_editor/__init__.py,sha256=yIUH6MZZUrVYKo0emfnHmq4hzjTu1NygO2lbIpmYkao,2418
2
2
  je_editor/__main__.py,sha256=2ymm4ESRcqP4K1YQyeaDbGhuK-0C1IausIiEOpbm6Ds,570
3
3
  je_editor/start_editor.py,sha256=hW7JFOjBkcW7hdC7q-9JsaVTeSNCep1GsqxLmbMU4wg,543
4
4
  je_editor/code_scan/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -9,13 +9,13 @@ je_editor/git_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
9
9
  je_editor/git_client/commit_graph.py,sha256=z5as1qS0rQMp_u8xdtIn0b0FKyZzUm68l3EiTjvykCU,2677
10
10
  je_editor/git_client/git_action.py,sha256=FgIvS_xPZSu0H_UIQoaqJKJa-JZiQwj1oGLVOeSD1nA,6256
11
11
  je_editor/git_client/git_cli.py,sha256=Ad-OdPyOQvAvUHeXWVwvHPCi7xo6o3ljKMTlUzOW53Q,2096
12
- je_editor/git_client/github.py,sha256=aJTIl2LftyWR0Olmv2WDuJDkmLLukz6JKd3FZntb2-8,3324
13
12
  je_editor/pyside_ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
13
  je_editor/pyside_ui/browser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
14
  je_editor/pyside_ui/browser/browser_download_window.py,sha256=cCOj1WeglNIbabJZdXL0eXWE6bZT-U9huZGFbSSKXgw,3397
16
15
  je_editor/pyside_ui/browser/browser_serach_lineedit.py,sha256=78Wa1X9LWoJW_ppCVTOmD51rJzjOElJGkdPkk4VBkBY,1875
17
- je_editor/pyside_ui/browser/browser_view.py,sha256=6b0J46Y8GnfdYuR40hqlanpG84AtrPkEo9laS8nc374,2771
18
- je_editor/pyside_ui/browser/browser_widget.py,sha256=3DINK7V7BSckm8tqvDggBtGo3sYDrYHsyeFmsqH9zFs,4246
16
+ je_editor/pyside_ui/browser/browser_view.py,sha256=UgzOyHBx8egGO7Pt4Ch18Hie9IrFtMFBvax2tItWJq0,3122
17
+ je_editor/pyside_ui/browser/browser_widget.py,sha256=YrNF918BUt2PlraeUqzhp1cyc9QCaqHv6lo5GmhmEZA,4123
18
+ je_editor/pyside_ui/browser/main_browser_widget.py,sha256=Ib5buoyDUQn0VIXfBPB8xN29QdDUDnLaWPF-WUMw0kY,2955
19
19
  je_editor/pyside_ui/code/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  je_editor/pyside_ui/code/running_process_manager.py,sha256=VdfdprPNlPmMySOKDOjEEl5X9IsJVPSDb0a2KDKgiQg,1008
21
21
  je_editor/pyside_ui/code/auto_save/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -55,10 +55,10 @@ je_editor/pyside_ui/git_ui/code_diff_compare/side_by_side_diff_widget.py,sha256=
55
55
  je_editor/pyside_ui/git_ui/git_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
56
  je_editor/pyside_ui/git_ui/git_client/commit_table.py,sha256=5NkPx27omhtql1n0E51i5wuXqojnioxRD3SJPq-X0Xg,1136
57
57
  je_editor/pyside_ui/git_ui/git_client/git_branch_tree_widget.py,sha256=wZ2OwY1LZ-Qv4TFx_7uuDGgb2jUsUKiUVgZoU_mg__I,4748
58
- je_editor/pyside_ui/git_ui/git_client/git_client_gui.py,sha256=PIGrJOlEba4Rg3rHOqNRf-9IjQa2ALl2O5z5mQMu0wo,31542
58
+ je_editor/pyside_ui/git_ui/git_client/git_client_gui.py,sha256=oMJl2TKz_C-JxrbKiq7S_7wl_e01EKfEVXPq-qNwWU8,34230
59
59
  je_editor/pyside_ui/git_ui/git_client/graph_view.py,sha256=0qLxZMzRZMpDFACiUlv0EyyVkAZiYFlU7d2jE5NuVIE,6045
60
60
  je_editor/pyside_ui/main_ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
- je_editor/pyside_ui/main_ui/main_editor.py,sha256=ZehoXiVy9ZE6_2tHdAC3Gdg5PqdccpqZnM-oD6d9sOQ,11339
61
+ je_editor/pyside_ui/main_ui/main_editor.py,sha256=ZGOiqKwa5qolDFXUTczItUYC6GUEvJiCT4gC0wFEdes,11354
62
62
  je_editor/pyside_ui/main_ui/ai_widget/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
63
  je_editor/pyside_ui/main_ui/ai_widget/ai_config.py,sha256=Uc9UkrIeALqjmZCF5ukXtSgU1EB4xa5223gk3CfYXX4,459
64
64
  je_editor/pyside_ui/main_ui/ai_widget/ask_thread.py,sha256=ma5Cnwlr0RbU-sytmodcYabWueyGE8DKKISZ_0zf3Eo,571
@@ -80,11 +80,11 @@ je_editor/pyside_ui/main_ui/menu/set_menu_bar.py,sha256=I12DXLyRO4cKe17fQY-QDazK
80
80
  je_editor/pyside_ui/main_ui/menu/check_style_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
81
  je_editor/pyside_ui/main_ui/menu/check_style_menu/build_check_style_menu.py,sha256=1wD6FI21Dw3IcQONuTkHP9rUPGwY70OvCtSc-ZcTKMk,3793
82
82
  je_editor/pyside_ui/main_ui/menu/dock_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
- je_editor/pyside_ui/main_ui/menu/dock_menu/build_dock_menu.py,sha256=KWblg3ZxsMSPLYWeYsnx15vJ3TdHL7kQQEEiuk0t0DE,9146
83
+ je_editor/pyside_ui/main_ui/menu/dock_menu/build_dock_menu.py,sha256=2G9KaqjC9WwGE6ZkVBtuDFqcfnGBlqr_DYnvJw1hjZU,8478
84
84
  je_editor/pyside_ui/main_ui/menu/file_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
85
  je_editor/pyside_ui/main_ui/menu/file_menu/build_file_menu.py,sha256=YT6kvXShRbfM4Qo51VveO_IK8uDqlNrGwHZUiM6_Tes,6881
86
86
  je_editor/pyside_ui/main_ui/menu/help_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
- je_editor/pyside_ui/main_ui/menu/help_menu/build_help_menu.py,sha256=iy1xDpV-u4DhZp8ZsetVKwHuJCctYgCWMpkIv12u3V0,2869
87
+ je_editor/pyside_ui/main_ui/menu/help_menu/build_help_menu.py,sha256=KjMDwPNxpiOGlHoiJ_XPkYbCtm7LuNUTso_rwM5fIY0,2882
88
88
  je_editor/pyside_ui/main_ui/menu/language_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
89
  je_editor/pyside_ui/main_ui/menu/language_menu/build_language_server.py,sha256=23jKdAYxN65xJMVd5NbCwhIjwh2kfXVDszN1wmIs1A0,2333
90
90
  je_editor/pyside_ui/main_ui/menu/python_env_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -99,7 +99,7 @@ je_editor/pyside_ui/main_ui/menu/run_menu/under_run_menu/utils.py,sha256=zk5adQ7
99
99
  je_editor/pyside_ui/main_ui/menu/style_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
100
  je_editor/pyside_ui/main_ui/menu/style_menu/build_style_menu.py,sha256=OjfcjQ7fA1Z1rMwIS9oGkIkC-jy-e0jdGoYo1btTqUQ,1852
101
101
  je_editor/pyside_ui/main_ui/menu/tab_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
102
- je_editor/pyside_ui/main_ui/menu/tab_menu/build_tab_menu.py,sha256=O9EGA_3FrIb-wkyeRbCQm4nzEoEebLDsbYcEGsqtR3o,10182
102
+ je_editor/pyside_ui/main_ui/menu/tab_menu/build_tab_menu.py,sha256=gUua9jFTGbfW6tN9Jd_TQluSObaXJC75YfEcqY-I0q8,9312
103
103
  je_editor/pyside_ui/main_ui/menu/text_menu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
104
  je_editor/pyside_ui/main_ui/menu/text_menu/build_text_menu.py,sha256=k3pOpK4i8ccs3D8VIrNx8g3kUDp1fZdqv9H5uEvNl5c,3558
105
105
  je_editor/pyside_ui/main_ui/save_settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -133,8 +133,8 @@ je_editor/utils/redirect_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
133
133
  je_editor/utils/redirect_manager/redirect_manager_class.py,sha256=1gICetKpohBvmxmVhnqeCRq7AQS2YWK4AURmrqnVYVw,2277
134
134
  je_editor/utils/venv_check/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
135
135
  je_editor/utils/venv_check/check_venv.py,sha256=oCrMdue4NYUUGrVifh_iHFwIgxVx9azYN4jz3Xiulgg,999
136
- je_editor-0.0.222.dist-info/licenses/LICENSE,sha256=KMhUHh6pnIUvmXDW-49L_Sz63bqkOlPDqsecaqKiitU,1091
137
- je_editor-0.0.222.dist-info/METADATA,sha256=6FccWz2MH7kpbQpNXxZk8Jj4tWL-QlMU9VqAOqpZDtU,3487
138
- je_editor-0.0.222.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
139
- je_editor-0.0.222.dist-info/top_level.txt,sha256=_9YA7BgxpkmdLs-5V_UQILxClcMRgPyG1a3qaE-Bkcs,10
140
- je_editor-0.0.222.dist-info/RECORD,,
136
+ je_editor-0.0.223.dist-info/licenses/LICENSE,sha256=KMhUHh6pnIUvmXDW-49L_Sz63bqkOlPDqsecaqKiitU,1091
137
+ je_editor-0.0.223.dist-info/METADATA,sha256=tEHTB17ubfoQ13yiiKadUPfiuztKPQGQkWVcdOuSHhg,3487
138
+ je_editor-0.0.223.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
139
+ je_editor-0.0.223.dist-info/top_level.txt,sha256=_9YA7BgxpkmdLs-5V_UQILxClcMRgPyG1a3qaE-Bkcs,10
140
+ je_editor-0.0.223.dist-info/RECORD,,
@@ -1,81 +0,0 @@
1
- import datetime
2
- import traceback
3
-
4
- from git import Repo, GitCommandError, InvalidGitRepositoryError, NoSuchPathError
5
-
6
-
7
- class GitCloneHandler:
8
- """
9
- Handles cloning of remote Git repositories with audit logging.
10
- 負責複製遠端 Git 儲存庫,並記錄稽核日誌。
11
- 可在 UI 或 CLI 環境中重複使用。
12
- """
13
-
14
- def __init__(self, audit_log_path: str = "git_clone_audit.log"):
15
- """
16
- Initialize the handler with an audit log file path.
17
- 初始化處理器,指定稽核日誌檔案路徑。
18
-
19
- :param audit_log_path: Path to the audit log file
20
- 稽核日誌檔案的路徑
21
- """
22
- self.audit_log_path = audit_log_path
23
-
24
- def clone_repo(self, remote_url: str, local_path: str) -> str:
25
- """
26
- Clone a remote repository to a local path.
27
- 將遠端 Git 儲存庫複製到本地路徑。
28
-
29
- :param remote_url: The Git repository URL (e.g., https://github.com/user/repo.git)
30
- Git 儲存庫的 URL
31
- :param local_path: The local directory to clone into
32
- 要複製到的本地目錄
33
- :return: The path to the cloned repository
34
- 複製完成後的本地路徑
35
- :raises: RuntimeError if cloning fails
36
- 若複製失敗則拋出 RuntimeError
37
- """
38
- try:
39
- # 記錄開始複製 / Log start of cloning
40
- self._log_audit(f"Cloning started: {remote_url} -> {local_path}")
41
-
42
- # 執行 Git 複製 / Perform Git clone
43
- Repo.clone_from(remote_url, local_path)
44
-
45
- # 記錄完成複製 / Log completion
46
- self._log_audit(f"Cloning completed: {remote_url} -> {local_path}")
47
- return local_path
48
-
49
- except (GitCommandError, InvalidGitRepositoryError, NoSuchPathError) as e:
50
- # 捕捉 Git 相關錯誤 / Catch Git-related errors
51
- self._log_audit(
52
- f"ERROR: Git operation failed: {remote_url} -> {local_path}\n"
53
- f"{str(e)}\nTraceback:\n{traceback.format_exc()}"
54
- )
55
- raise RuntimeError(f"Git operation failed: {str(e)}") from e
56
-
57
- except Exception as e:
58
- # 捕捉其他未預期錯誤 / Catch unexpected errors
59
- self._log_audit(
60
- f"ERROR: Unexpected error during clone: {remote_url} -> {local_path}\n"
61
- f"{str(e)}\nTraceback:\n{traceback.format_exc()}"
62
- )
63
- raise RuntimeError(f"Unexpected error during clone: {str(e)}") from e
64
-
65
- def _log_audit(self, message: str):
66
- """
67
- Append an audit log entry with timestamp.
68
- 在稽核日誌中加入帶有時間戳的紀錄。
69
-
70
- :param message: The message to log
71
- 要記錄的訊息
72
- """
73
- timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
74
- log_entry = f"[{timestamp}] {message}\n"
75
- try:
76
- with open(self.audit_log_path, "a", encoding="utf-8") as f:
77
- f.write(log_entry)
78
- except Exception:
79
- # 確保日誌寫入失敗不會影響主要流程
80
- # Never let audit logging failure break the flow
81
- pass