File size: 4,196 Bytes
77bf0e5
31d220e
77bf0e5
 
 
 
31d220e
77bf0e5
 
 
31d220e
77bf0e5
 
 
 
 
 
 
 
 
 
 
 
 
31d220e
77bf0e5
 
 
 
 
31d220e
 
 
 
77bf0e5
 
 
 
 
31d220e
77bf0e5
 
31d220e
 
 
 
77bf0e5
 
 
 
 
31d220e
77bf0e5
 
31d220e
77bf0e5
 
31d220e
2156541
31d220e
 
77bf0e5
31d220e
77bf0e5
 
31d220e
 
 
77bf0e5
31d220e
 
 
 
 
 
 
 
 
 
 
 
 
a3bdcf1
31d220e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2156541
31d220e
77bf0e5
 
 
31d220e
77bf0e5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""
File Explorer Component - VS Code style file tree.
"""
import streamlit as st
import os
from pathlib import Path
from typing import Dict, List


def build_file_tree(file_paths: List[str], base_path: str = "") -> Dict:
    """Build a nested dictionary representing the file tree."""
    tree = {}
    
    for file_path in file_paths:
        if base_path:
            try:
                rel_path = os.path.relpath(file_path, base_path)
            except ValueError:
                rel_path = file_path
        else:
            rel_path = file_path
        
        parts = Path(rel_path).parts
        current = tree
        
        for i, part in enumerate(parts):
            if i == len(parts) - 1:
                current[part] = {"_type": "file", "_path": file_path}
            else:
                if part not in current:
                    current[part] = {"_type": "dir", "_children": {}}
                current = current.get(part, {}).get("_children", current.get(part, {}))
                if "_children" not in current:
                    current = current
    
    return tree


def get_file_icon(filename: str) -> str:
    """Get icon for file based on extension."""
    ext = Path(filename).suffix.lower()
    icons = {
        ".py": "🐍", ".js": "πŸ“œ", ".ts": "πŸ“˜", ".jsx": "βš›οΈ", ".tsx": "βš›οΈ",
        ".html": "🌐", ".css": "🎨", ".json": "πŸ“‹", ".md": "πŸ“",
        ".yaml": "βš™οΈ", ".yml": "βš™οΈ", ".toml": "βš™οΈ", ".sql": "πŸ—ƒοΈ",
        ".env": "πŸ”", ".gitignore": "🚫", ".txt": "πŸ“„",
    }
    return icons.get(ext, "πŸ“„")


def render_file_tree(indexed_files: List[str], base_path: str = ""):
    """Render VS Code style file tree."""
    
    if not indexed_files:
        st.caption("No files indexed")
        return
    
    # Custom CSS for tree styling
    # Styles are now handled globally in components/style.py for modularity
    
    st.markdown(f"**πŸ“ Files** ({len(indexed_files)})")
    
    # Build and render tree
    tree = build_file_tree(indexed_files, base_path)
    
    # Initialize expanded state
    if "tree_expanded" not in st.session_state:
        st.session_state.tree_expanded = set()
    
    # Render tree items
    render_tree_items(tree, 0)


def render_tree_items(tree: Dict, depth: int):
    """Render tree items with proper indentation."""
    
    # Sort: directories first, then files
    items = [(k, v) for k, v in tree.items() if not k.startswith("_")]
    sorted_items = sorted(items, key=lambda x: (x[1].get("_type") == "file", x[0].lower()))
    
    for name, node in sorted_items:
        is_file = node.get("_type") == "file"
        indent = "β”‚ " * depth # Compact indent for sidebar
        
        if is_file:
            # File item
            file_path = node.get("_path", "")
            icon = get_file_icon(name)
            is_selected = st.session_state.get("selected_file") == file_path
            
            # Compact button
            btn_label = f"{indent}β”œβ”€ {icon} {name}"
            if st.button(btn_label, key=f"tree_{file_path}", use_container_width=True,
                        type="primary" if is_selected else "secondary"):
                st.session_state.selected_file = file_path
                st.rerun()
        else:
            # Directory item
            dir_key = f"dir_{depth}_{name}"
            is_expanded = dir_key in st.session_state.tree_expanded
            arrow = "β–Ό" if is_expanded else "β–Ά"
            
            btn_label = f"{indent}{arrow} πŸ“ {name}"
            if st.button(btn_label, key=dir_key, use_container_width=True, type="secondary"):
                if is_expanded:
                    st.session_state.tree_expanded.discard(dir_key)
                else:
                    st.session_state.tree_expanded.add(dir_key)
                st.rerun()
            
            # Render children if expanded
            if is_expanded:
                children = node.get("_children", {})
                render_tree_items(children, depth + 1)


def get_indexed_files_from_session() -> List[str]:
    """Get indexed files from session state."""
    return st.session_state.get("indexed_files", [])