# File explorer and IDE

> An IDE layout in Python, like a modern code editor: project tree, editor tabs, breadcrumbs, a code editor with minimap and git markers, terminal tabs, an outline and a status bar.

Source: https://albakore.github.io/pyzed/blocks/file-explorer/

A code editor window built with `ui.workspace`: a title bar with project and branch, a filterable project tree with git status colors, editor tabs and breadcrumbs, the code editor with syntax highlighting, git markers and a minimap, a terminal with tabs, an outline panel and a status bar.

The screenshot follows the site's theme: switch between light and dark at the top of the page to see the block in One Light and One Dark.

## Run it

From the repository root:

```bash
python examples/blocks/file_explorer.py
```

## What it shows

- `ui.workspace` places the docks; they can be resized by dragging.
- `ui.TreeNode.from_paths` builds the tree from a list of paths, with git statuses.
- `ui.Tab.for_file` picks the icon and color of each file type.
- `ui.terminal` understands ANSI colors.

## Components used

| Component | Used for |
| --- | --- |
| [Workspace](https://albakore.github.io/pyzed/library/workspace/) | The window layout |
| [Tree](https://albakore.github.io/pyzed/library/tree/) | Project files |
| [Tab bar](https://albakore.github.io/pyzed/library/tab-bar/) | Open files |
| [Breadcrumbs](https://albakore.github.io/pyzed/library/breadcrumbs/) | Path |
| [Editor](https://albakore.github.io/pyzed/library/editor/) | Code |
| [Terminal](https://albakore.github.io/pyzed/library/terminal/) | Terminal |
| [Title bar](https://albakore.github.io/pyzed/library/title-bar/) | Title bar |
| [Status bar](https://albakore.github.io/pyzed/library/status-bar/) | Status bar |
| [Toolbar](https://albakore.github.io/pyzed/library/toolbar/) | Editor toolbar |
| [Panel header](https://albakore.github.io/pyzed/library/panel-header/) | Panel titles |
| [List item](https://albakore.github.io/pyzed/library/list-item/) | Outline |

## Code

The whole block, ready to copy. It only needs `pip install pyzed-ui`.

```python title="examples/blocks/file_explorer.py"
"""IDE layout block: a project tree, editor tabs with breadcrumbs, a terminal, an outline panel and a status bar."""

import pyzed as pz
from pyzed import ui

FILES = {
    "src/app/main.py": '''"""Acme Cloud desktop client."""

import pyzed as pz
from pyzed import ui

from .api import Client
from .views import Sidebar, Deployments

class AcmeApp(pz.App):
    title = "Acme Cloud"
    size = (1280, 800)

    def __init__(self):
        self.client = Client.from_env()
        self.deployments = []
        self.selected = None

    def on_start(self):
        return self.refresh()

    async def refresh(self):
        return ("loaded", await self.client.deployments())

    def update(self, message):
        kind, data = message
        if kind == "loaded":
            self.deployments = data

    def view(self):
        return ui.workspace(
            Deployments(items=self.deployments),
            left=Sidebar(selected=self.selected),
        )

if __name__ == "__main__":
    pz.run(AcmeApp)
''',
    "src/app/api.py": "import httpx\n\n\nclass Client:\n    ...\n",
    "src/app/views.py": "import pyzed as pz\n",
    "src/app/__init__.py": "",
    "tests/test_app.py": "def test_refresh():\n    ...\n",
    "pyproject.toml": "[project]\nname = \"acme-cloud\"\n",
    "README.md": "# Acme Cloud\n",
    ".gitignore": "__pycache__/\n",
}
STATUS = {"src/app/main.py": "modified", "src/app/views.py": "created", "README.md": "modified"}
OUTLINE = [("class", "AcmeApp", 0), ("variable", "title", 1), ("variable", "size", 1), ("method", "__init__", 1),
           ("method", "on_start", 1), ("method", "refresh", 1), ("method", "update", 1), ("method", "view", 1)]
OUTLINE_ICON = {"class": ("box", "warning"), "method": ("square-function", "accent"), "variable": ("variable", "info")}
TERMINAL = ("$ uv run pytest -q\n"
            "\x1b[32m........................................\x1b[0m [100%]\n"
            "\x1b[32m40 passed\x1b[0m in 1.84s\n"
            "$ pyzed dev src/app/main.py\n"
            "\x1b[36m●\x1b[0m watching src/ for changes · hot reload on\n")

class FileExplorer(pz.App):
    title = "acme-cloud — main.py"
    size = (1200, 760)

    def __init__(self):
        self.tree = ui.TreeNode.from_paths(list(FILES), status=STATUS)
        self.expanded = {"src", "src/app", "tests"}
        self.open = ["src/app/main.py", "src/app/api.py", "README.md"]
        self.active = "src/app/main.py"
        self.texts = dict(FILES)
        self.cursor = (24, 18)
        self.query = ""

    # -- actions ---------------------------------------------------------------------------
    def toggle(self, node_id):
        self.expanded ^= {node_id}

    def select(self, node_id):
        if node_id in FILES:
            if node_id not in self.open:
                self.open.append(node_id)
            self.active = node_id

    def close(self, path):
        self.open.remove(path)
        if self.active == path:
            self.active = self.open[-1] if self.open else None

    def edit(self, text):
        self.texts[self.active] = text

    # -- view ------------------------------------------------------------------------------
    def project(self):
        return pz.column([
            ui.panel_header("acme-cloud", [ui.icon_button("file-plus", None, size="xs", tooltip="New file"),
                                           ui.icon_button("folder-plus", None, size="xs", tooltip="New folder"),
                                           ui.icon_button("list-collapse", lambda: setattr(self, "expanded", set()),
                                                          size="xs", tooltip="Collapse all")]),
            pz.container(ui.search_input(self.query, lambda v: setattr(self, "query", v), placeholder="Filter files…",
                                         size="sm"), padding=(4, 8)),
            ui.tree(self.tree, expanded=self.expanded, selected=self.active, on_toggle=self.toggle,
                    on_select=self.select),
        ])

    def outline(self):
        rows = []
        for kind, name, depth in OUTLINE:
            icon, color = OUTLINE_ICON[kind]
            rows.append(ui.list_item(name, None, icon=icon, icon_color=self.resolved_theme().color(color),
                                     indent=depth * 14, height=26, selected=name == "refresh"))
        return pz.column([ui.panel_header("Outline", [ui.icon_button("arrow-down-a-z", None, size="xs")],
                                          icon="list-tree"),
                          pz.column(rows, padding=(4, 4))])

    def editor(self):
        if self.active is None:
            return ui.empty_state("No open files", "Pick a file in the project panel.", icon="file")
        tabs = [ui.Tab.for_file(p, dirty=p == "src/app/main.py", status=STATUS.get(p)) for p in self.open]
        parts = self.active.split("/")
        return pz.column([
            ui.tab_bar(tabs, self.active, on_select=self.select, on_close=self.close,
                       end=[ui.icon_button("columns-2", None, size="sm", tooltip="Split right")]),
            ui.toolbar([ui.breadcrumbs([("folder", parts[0]), *parts[1:], "AcmeApp", "refresh"])],
                       [ui.icon_button("play", None, size="sm", tooltip="Run"),
                        ui.icon_button("sparkles", None, size="sm", tooltip="Inline assist")]),
            ui.editor("ide-editor", self.texts[self.active], path=self.active, on_change=self.edit,
                      markers={19: "modified", 20: "modified", 24: "created"}, minimap=True),
        ])

    def view(self):
        line, col = self.cursor
        return ui.workspace(
            self.editor(),
            title_bar=ui.title_bar(
                [ui.title_button("acme-cloud", chevron=True), ui.title_button("feat/deployments", icon="git-branch",
                                                                              muted=True)],
                [ui.icon_button("bell", None, size="sm"), ui.avatar("KV")],
                center=ui.title_button("Search files…", icon="search", muted=True)),
            left=self.project(),
            right=self.outline(),
            bottom=pz.column([
                ui.tab_bar([ui.Tab("zsh", "zsh", icon="terminal"), ui.Tab("tests", "tests", icon="flask-conical")],
                           "zsh", end=[ui.icon_button("plus", None, size="sm"), ui.icon_button("x", None, size="sm")]),
                ui.terminal(TERMINAL),
            ]),
            status_bar=ui.status_bar(
                [ui.status_item("feat/deployments", icon="git-branch"), ui.status_item("0", icon="circle-x"),
                 ui.status_item("2", icon="triangle-alert", icon_color=self.resolved_theme().warning)],
                [ui.status_item(f"Ln {line}, Col {col}"), ui.status_item("UTF-8"), ui.status_item("Python 3.13"),
                 ui.status_item(None, icon="sparkles", tooltip="AI agent")]),
            left_width=250, right_width=220, bottom_height=172,
        )

if __name__ == "__main__":
    pz.run(FileExplorer)
```

## Make it yours

Change `theme` (or `self.theme = pz.themes.get("One Dark").with_accent("#e11d48")`), replace the sample data with your own and split pieces into [components](https://albakore.github.io/pyzed/guides/components/) when they grow. An AI agent with the [MCP server](https://albakore.github.io/pyzed/ai/mcp/) can read this block with `read_example("blocks/file_explorer")` and adapt it for you.
