From b65565adfa0ecb4454b17d6346f4b73233e58877 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 17 Jan 2026 15:07:05 -0500 Subject: [PATCH] [tui] Add Explorer widget for left panel. --- Cargo.lock | 13 ++++++++++ Cargo.toml | 2 ++ src/tui.rs | 1 + src/tui/app.rs | 19 +++++++-------- src/tui/explorer.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 src/tui/explorer.rs diff --git a/Cargo.lock b/Cargo.lock index 1d9d0f3..86a6948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,6 +230,8 @@ dependencies = [ "ratatui", "structopt", "syntect", + "tui-tree-widget", + "uuid", ] [[package]] @@ -1908,6 +1910,17 @@ dependencies = [ "time-core", ] +[[package]] +name = "tui-tree-widget" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deca119555009eee2e0cfb9c020f39f632444dc4579918d5fc009d51d75dff92" +dependencies = [ + "ratatui-core", + "ratatui-widgets", + "unicode-width 0.2.2", +] + [[package]] name = "typenum" version = "1.19.0" diff --git a/Cargo.toml b/Cargo.toml index 4db63bb..0f88e17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ syntect = "5.2.0" structopt = "0.3.26" ratatui = "0.30.0" anyhow = "1.0.100" +tui-tree-widget = "0.24.0" +uuid = { version = "1.19.0", features = ["v4"] } [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. diff --git a/src/tui.rs b/src/tui.rs index 3ba419d..98229c0 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,4 +1,5 @@ pub mod app; +mod explorer; use anyhow::{Context, Result}; diff --git a/src/tui/app.rs b/src/tui/app.rs index 1d24d6a..7ee8b14 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -8,14 +8,18 @@ use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap}; use ratatui::{DefaultTerminal, symbols}; use std::time::Duration; +use crate::tui::explorer::Explorer; + #[derive(Debug, Clone, Copy)] pub struct App<'a> { - root_path: &'a std::path::Path, + explorer: Explorer<'a>, } impl<'a> App<'a> { pub(crate) fn new(root_path: &'a std::path::Path) -> Self { - Self { root_path } + Self { + explorer: Explorer::new(root_path), + } } pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<()> { @@ -84,7 +88,7 @@ impl<'a> App<'a> { fn draw_terminal(self, area: Rect, buf: &mut Buffer) { // TODO: Title should be detected shell name // TODO: Contents should be shell output - Paragraph::new("Terminal placeholder") + Paragraph::new("shaun@pc:~/Code/clide$ ") .style(Style::default()) .block( Block::default() @@ -95,13 +99,6 @@ impl<'a> App<'a> { .wrap(Wrap { trim: false }) .render(area, buf); } - - fn draw_file_explorer(self, area: Rect, buf: &mut Buffer) { - Paragraph::new("File explorer placeholder") - .style(Style::default()) - .block(Block::default().borders(Borders::ALL)) - .render(area, buf); - } } // TODO: Separate complex components into their own widgets. @@ -138,7 +135,7 @@ impl<'a> Widget for App<'a> { self.draw_status(vertical[0], buf); self.draw_terminal(vertical[2], buf); - self.draw_file_explorer(horizontal[0], buf); + self.explorer.draw(horizontal[0], buf); self.draw_tabs(editor_layout[0], buf); self.draw_editor(editor_layout[1], buf); diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs new file mode 100644 index 0000000..5be7736 --- /dev/null +++ b/src/tui/explorer.rs @@ -0,0 +1,58 @@ +use std::fs; +use ratatui::buffer::Buffer; +use ratatui::layout::Rect; +use ratatui::prelude::Style; +use ratatui::widgets::{Block, Borders, Widget}; +use tui_tree_widget::{Tree, TreeItem}; +use uuid::Uuid; + +#[derive(Clone, Copy, Debug)] +pub struct Explorer<'a> { + root_path: &'a std::path::Path, +} + +impl<'a> Explorer<'a> { + pub fn new(path: &'a std::path::Path) -> Self { + Explorer { root_path: path } + } + + pub fn draw(self, area: Rect, buf: &mut Buffer) { + let tree_item = Self::build_tree_from_path(self.root_path.to_path_buf()); + Tree::new(&tree_item.children()) + .expect("Failed to build tree.") + .style(Style::default()) + .block(Block::default().borders(Borders::ALL)) + .render(area, buf); + } + + fn build_tree_from_path(path: std::path::PathBuf) -> TreeItem<'static, String> { + let mut children = vec![]; + if let Ok(entries) = fs::read_dir(&path) { + let mut paths = entries + .map(|res| res.map(|e| e.path())) + .collect::, std::io::Error>>() + .expect(""); + paths.sort(); + for path in paths { + if path.is_dir() { + children.push(Self::build_tree_from_path(path)); + } else { + children.push(TreeItem::new_leaf( + Uuid::new_v4().to_string(), + path.file_name().unwrap().to_string_lossy().to_string(), + )); + } + } + } + + TreeItem::new( + Uuid::new_v4().to_string(), + path.file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(), + children, + ) + .expect("Failed to build tree from path.") + } +}