AISEAICodeEditor/src/ui/navigation.py
2026-05-26 20:58:25 +02:00

188 lines
6.8 KiB
Python

import tkinter as tk
from tkinter import filedialog
from pathlib import Path
import streamlit as st
import streamlit_antd_components as sac
from backend.file_manager import FileManager
class FileNavigation:
"""
Builds a VS Code-like file tree using streamlit-antd-components.
All filesystem ops delegated to FileManager.
"""
def __init__(self):
self.file_manager = FileManager()
self.supported_extensions = FileManager.SUPPORTED_EXTENSIONS
# ---------------- FOLDER PICKER ----------------
def pick_folder(self) -> str | None:
root = tk.Tk()
root.withdraw()
root.wm_attributes("-topmost", 1)
folder = filedialog.askdirectory(title="Select project folder")
root.destroy()
return folder if folder else None
# ---------------- TREE BUILDER ----------------
def build_tree(self, path=None) -> list:
if path is None:
path = Path.cwd()
path = Path(path)
def build_node(current_path: Path) -> sac.TreeItem:
is_dir = current_path.is_dir()
if is_dir:
children = []
try:
for item in sorted(
current_path.iterdir(),
key=lambda x: (not x.is_dir(), x.name.lower())
):
if item.is_dir() or item.suffix in self.supported_extensions:
children.append(build_node(item))
except PermissionError:
pass
return sac.TreeItem(
label=current_path.name,
icon="folder",
children=children if children else None,
)
else:
return sac.TreeItem(
label=current_path.name,
icon="file-earmark-code",
)
return [build_node(path)]
# ---------------- DIALOG ----------------
@st.dialog("New file or folder")
def _new_item_dialog(self, root_folder: str):
root = Path(root_folder)
all_dirs = self.file_manager.list_all_dirs(root_folder)
def dir_label(p: Path) -> str:
rel = p.relative_to(root)
return "/ (root)" if str(rel) == "." else str(rel)
dir_labels = [dir_label(d) for d in all_dirs]
dir_paths = [str(d) for d in all_dirs]
mode = st.segmented_control(
"Type",
options=["📄 File", "📁 Folder"],
default="📄 File",
label_visibility="collapsed"
)
is_file = mode == "📄 File"
st.markdown("---")
selected_label = st.selectbox("Location", options=dir_labels, index=0)
target_dir = dir_paths[dir_labels.index(selected_label)]
name = st.text_input(
"File name" if is_file else "Folder name",
placeholder="e.g. main.py" if is_file else "e.g. utils",
)
if name and name.strip():
sep = "" if target_dir.endswith("/") else "/"
st.caption(f"`{target_dir}{sep}{name.strip()}`")
st.markdown("---")
col_cancel, col_create = st.columns(2)
with col_cancel:
if st.button("Cancel", use_container_width=True):
st.rerun()
with col_create:
if st.button(
"Create file" if is_file else "Create folder",
type="primary",
use_container_width=True,
disabled=not (name and name.strip())
):
if is_file:
ok, msg = self.file_manager.create_file(target_dir, name)
else:
ok, msg = self.file_manager.create_folder(target_dir, name)
if ok:
st.success(msg)
st.rerun()
else:
st.error(msg)
# ---------------- SIDEBAR RENDERER ----------------
def render_sidebar(self) -> str | None:
st.sidebar.title("File Explorer")
root_folder = st.session_state.get("root_folder")
# --- Auto-refresh: detect external filesystem changes ---
if root_folder:
current_snapshot = self.file_manager.snapshot(root_folder)
last_snapshot = st.session_state.get("_folder_snapshot")
if last_snapshot is None:
st.session_state._folder_snapshot = current_snapshot
elif current_snapshot != last_snapshot:
st.session_state._folder_snapshot = current_snapshot
st.rerun()
with st.sidebar:
btn_left, btn_right = st.columns(2)
with btn_left:
if st.button("📂 Folder", key="pick_folder", use_container_width=True):
picked = self.pick_folder()
if picked:
st.session_state.root_folder = picked
st.session_state._folder_snapshot = None
st.rerun()
with btn_right:
if st.button("💬 Ask AI", key="toggle_chat", use_container_width=True):
st.session_state.chat_open = not st.session_state.get("chat_open", False)
st.rerun()
st.markdown("---")
if root_folder:
if st.button("Add new File/Folder", key="new_item_btn", use_container_width=True):
self._new_item_dialog(root_folder)
try:
root_path = Path(root_folder)
if root_path.exists() and root_path.is_dir():
tree_data = self.build_tree(root_path)
selected = sac.tree(
items=tree_data,
label=" ",
index=None,
align="left",
size="sm",
icon="folder2",
open_all=False,
checkbox=False,
show_line=False,
key=f"tree_{root_folder}",
)
if selected:
return self._resolve_path(root_path, selected)
else:
st.warning("Folder not found.")
except Exception as e:
st.error(f"Could not load folder: {e}")
else:
st.text("No folder selected yet.")
return None
# ---------------- PATH RESOLVER ----------------
def _resolve_path(self, root_path: Path, selected_label: str) -> str | None:
for p in root_path.rglob("*"):
if p.name == selected_label and p.suffix in self.supported_extensions:
return str(p)
return None