fix(ui): tab Thư mục giữ đúng thư mục người dùng tự chọn
Hai lỗi riêng biệt cùng gây ra "chọn folder khác, sang tab khác rồi quay lại thì về folder cũ": 1. WorkspaceFileTree.root_changed KHÔNG có ai lắng nghe trong toàn bộ repo. Người dùng chọn thư mục trong cây thì chỉ cái cây đổi gốc; _root, khung xem và terminal ở lại thư mục cũ — ba widget con lệch nhau ngay từ lúc bấm chọn. 2. _load_current gọi set_root(project.workspace_dir()) vô điều kiện, và _goto gọi workspace.refresh() mỗi lần vào lại màn Workspace, nên mỗi cú chuyển tab kéo thư mục về workspace của project. set_project_root() bỏ qua khi project KHÔNG đổi. Đổi sang project khác thì vẫn re-root — thư mục của màn này thuộc về project; chỉ lần refresh trong CÙNG một project là không được đụng. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,9 @@ class FolderTab(QWidget):
|
||||
super().__init__()
|
||||
self.ctx = ctx
|
||||
self._root = str(ctx.config.cowork_output_dir())
|
||||
# Goc ma project dang chon ap xuong, de phan biet "doi project" voi
|
||||
# "chi la refresh" — xem set_project_root().
|
||||
self._project_root = ""
|
||||
|
||||
root_layout = QVBoxLayout(self)
|
||||
split = QSplitter(Qt.Horizontal)
|
||||
@@ -82,6 +85,10 @@ class FolderTab(QWidget):
|
||||
self.terminal.expanded.connect(lambda: self.terminal.set_cwd(self._root))
|
||||
root_layout.addWidget(self.terminal)
|
||||
|
||||
# ``root_changed`` truoc day KHONG co ai lang nghe: nguoi dung tu chon
|
||||
# thu muc trong cay thi chi cai cay doi goc, con khung xem va terminal o
|
||||
# lai thu muc cu.
|
||||
self.tree.root_changed.connect(self._on_user_picked_root)
|
||||
self.tree.file_selected.connect(self.preview.open_file)
|
||||
self.preview.status_message.connect(self.status_message.emit)
|
||||
self.ai_panel.status_message.connect(self.status_message.emit)
|
||||
@@ -105,6 +112,29 @@ class FolderTab(QWidget):
|
||||
self.preview.set_root(path)
|
||||
self.terminal.set_cwd(path)
|
||||
|
||||
def _on_user_picked_root(self, path: str) -> None:
|
||||
"""Người dùng tự chọn thư mục trong cây: lan sang khung xem và terminal."""
|
||||
self._root = path
|
||||
self.preview.set_root(path)
|
||||
self.terminal.set_cwd(path)
|
||||
|
||||
def set_project_root(self, path: str) -> None:
|
||||
"""Áp thư mục gốc theo project đang chọn.
|
||||
|
||||
Bỏ qua nếu project KHÔNG đổi. ``WorkspaceTab.refresh()`` — và qua đó
|
||||
``_load_current`` — chạy lại mỗi lần người dùng vào lại màn Workspace
|
||||
(``_goto`` gọi nó), nên gọi ``set_root`` vô điều kiện sẽ kéo thư mục về
|
||||
workspace của project và xoá mất lựa chọn tay: chọn folder khác, chuyển
|
||||
tab rồi quay lại là mất.
|
||||
|
||||
Đổi sang project khác thì vẫn re-root — thư mục của màn này thuộc về
|
||||
project, chỉ có lần refresh trong CÙNG một project là không được đụng.
|
||||
"""
|
||||
if path == self._project_root:
|
||||
return
|
||||
self._project_root = path
|
||||
self.set_root(path)
|
||||
|
||||
def _toggle_ai_panel(self) -> None:
|
||||
"""Gập/mở panel AI-Edit; mở ra thì báo cho panel biết để nó nạp model lần đầu."""
|
||||
show = self.ai_btn.isChecked()
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
"""Thư mục người dùng tự chọn ở tab Thư mục không được biến mất khi chuyển tab.
|
||||
|
||||
Hai lỗi riêng biệt cùng gây ra triệu chứng "chọn folder khác, sang tab khác rồi
|
||||
quay lại thì về folder cũ":
|
||||
|
||||
1. ``WorkspaceFileTree.root_changed`` **không có ai lắng nghe**. Người dùng chọn
|
||||
thư mục trong cây thì chỉ cái cây đổi gốc; ``_root``, khung xem và terminal ở
|
||||
lại thư mục cũ. Tín hiệu chết.
|
||||
2. ``WorkspaceTab._load_current`` gọi ``set_root(project.workspace_dir())`` vô
|
||||
điều kiện, và ``_goto`` gọi ``workspace.refresh()`` mỗi lần vào lại màn
|
||||
Workspace — nên mỗi cú chuyển tab kéo thư mục về workspace của project.
|
||||
|
||||
Đổi sang project KHÁC thì vẫn phải re-root: thư mục của màn này thuộc về project.
|
||||
Chỉ có lần refresh trong cùng một project là không được đụng.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("PySide6", reason="cần PySide6 để dựng widget thật")
|
||||
|
||||
|
||||
class _Cfg:
|
||||
"""Đủ cho FolderTab, không hơn."""
|
||||
|
||||
def __init__(self, out_dir):
|
||||
self._out = str(out_dir)
|
||||
self.data = {}
|
||||
|
||||
def cowork_output_dir(self):
|
||||
from pathlib import Path
|
||||
return Path(self._out)
|
||||
|
||||
|
||||
class _Ctx:
|
||||
def __init__(self, out_dir):
|
||||
self.config = _Cfg(out_dir)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def folder_tab(qapp, tmp_path):
|
||||
from cowork_local.presentation.folder.folder_tab import FolderTab
|
||||
|
||||
goc = tmp_path / "goc-ban-dau"
|
||||
goc.mkdir()
|
||||
tab = FolderTab(_Ctx(goc))
|
||||
yield tab, tmp_path
|
||||
tab.deleteLater()
|
||||
|
||||
|
||||
# ---- lỗi 1: tín hiệu chết ------------------------------------------------
|
||||
|
||||
def test_nguoi_dung_chon_thu_muc_thi_khung_xem_va_terminal_theo_kip(folder_tab, tmp_path):
|
||||
"""``root_changed`` phải được nối, không thì ba widget con lệch nhau."""
|
||||
tab, base = folder_tab
|
||||
moi = base / "nguoi-dung-chon"
|
||||
moi.mkdir()
|
||||
|
||||
tab.tree.set_root(str(moi)) # phát root_changed như khi bấm chọn thư mục
|
||||
|
||||
assert tab._root == str(moi)
|
||||
assert tab.preview._root == str(moi)
|
||||
|
||||
|
||||
# ---- lỗi 2: refresh không được ghi đè lựa chọn tay ----------------------
|
||||
|
||||
def test_refresh_cung_project_khong_keo_ve_thu_muc_cu(folder_tab, tmp_path):
|
||||
"""Đây chính là triệu chứng: chuyển tab rồi quay lại là mất lựa chọn."""
|
||||
tab, base = folder_tab
|
||||
ws = base / "workspace-cua-project"
|
||||
ws.mkdir()
|
||||
tab.set_project_root(str(ws)) # vào màn lần đầu
|
||||
|
||||
nguoi_dung_chon = base / "folder-khac"
|
||||
nguoi_dung_chon.mkdir()
|
||||
tab.tree.set_root(str(nguoi_dung_chon))
|
||||
|
||||
tab.set_project_root(str(ws)) # chuyển tab rồi quay lại -> refresh
|
||||
|
||||
assert tab._root == str(nguoi_dung_chon), "lựa chọn tay bị ghi đè"
|
||||
|
||||
|
||||
def test_doi_sang_project_khac_thi_van_re_root(folder_tab, tmp_path):
|
||||
"""Thư mục của màn này thuộc về project — đổi project là phải đổi theo."""
|
||||
tab, base = folder_tab
|
||||
ws_a = base / "ws-a"
|
||||
ws_b = base / "ws-b"
|
||||
ws_a.mkdir()
|
||||
ws_b.mkdir()
|
||||
|
||||
tab.set_project_root(str(ws_a))
|
||||
khac = base / "tu-chon"
|
||||
khac.mkdir()
|
||||
tab.tree.set_root(str(khac))
|
||||
|
||||
tab.set_project_root(str(ws_b)) # người dùng đổi project
|
||||
|
||||
assert tab._root == str(ws_b)
|
||||
|
||||
|
||||
def test_quay_lai_project_cu_thi_ve_workspace_cua_no(folder_tab, tmp_path):
|
||||
tab, base = folder_tab
|
||||
ws_a = base / "ws-a"
|
||||
ws_b = base / "ws-b"
|
||||
ws_a.mkdir()
|
||||
ws_b.mkdir()
|
||||
|
||||
tab.set_project_root(str(ws_a))
|
||||
tab.set_project_root(str(ws_b))
|
||||
tab.set_project_root(str(ws_a))
|
||||
|
||||
assert tab._root == str(ws_a)
|
||||
|
||||
|
||||
def test_lan_dau_vao_man_van_ap_duoc_goc(folder_tab, tmp_path):
|
||||
"""``_project_root`` khởi tạo rỗng nên lần gọi đầu không bị bỏ qua."""
|
||||
tab, base = folder_tab
|
||||
ws = base / "ws"
|
||||
ws.mkdir()
|
||||
|
||||
tab.set_project_root(str(ws))
|
||||
|
||||
assert tab._root == str(ws)
|
||||
|
||||
|
||||
# ---- chỗ gọi phải dùng hàm mới -------------------------------------------
|
||||
|
||||
def test_workspace_tab_goi_set_project_root_chu_khong_set_root():
|
||||
from pathlib import Path
|
||||
|
||||
src = (Path(__file__).resolve().parents[2] / "ui" / "workspace_tab.py").read_text(encoding="utf-8")
|
||||
|
||||
assert "_folder.set_project_root(" in src
|
||||
assert "_folder.set_root(" not in src, (
|
||||
"gọi set_root() vô điều kiện là quay lại đúng lỗi cũ")
|
||||
Reference in New Issue
Block a user