diff --git a/src/tui/app.rs b/src/tui/app.rs index ec70957..90e37c3 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -129,6 +129,16 @@ impl<'a> Widget for &mut App<'a> { self.explorer.render(horizontal[0], buf); self.draw_tabs(editor_layout[0], buf); + + if let Some(editor) = self.editor.file_path.clone() { + let editor_abs = std::path::absolute(editor).unwrap(); + if let Some(selected) = self.explorer.selected() { + let selected_abs = std::path::absolute(selected).unwrap(); + if selected_abs.is_file() && selected_abs != editor_abs { + self.editor.set_contents(&selected_abs).ok(); + } + } + } self.editor.render(editor_layout[1], buf); } } @@ -150,13 +160,14 @@ impl<'a> Component for App<'a> { match self.handle_key_events(key_event) { Action::Quit => return Action::Quit, Action::Handled => { - dbg!(format!("Handled event: {:?}", self.id())); + // dbg!(format!("Handled event: {:?}", self.id())); return Action::Handled; } _ => {} } } - self.editor.handle_event(event); + self.explorer.handle_event(event.clone()); + self.editor.handle_event(event.clone()); // Handle events for all components. // for component in &mut self.components { diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 580043a..2ff4f8c 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -17,7 +17,7 @@ use ratatui::widgets::{Block, Borders, Padding, Widget}; pub struct Editor { pub state: EditorState, pub event_handler: EditorEventHandler, - file_path: Option, + pub file_path: Option, } impl Editor { @@ -30,15 +30,16 @@ impl Editor { } pub fn set_contents(&mut self, path: &std::path::PathBuf) -> Result<()> { - let contents = std::fs::read_to_string(path) - .expect(&format!("Failed to read file contents: {}", path.display())); - let lines: Vec<_> = contents - .lines() - .map(|line| line.chars().collect::>()) - .collect(); - self.file_path = Some(path.clone()); - self.state.lines = Lines::new(lines); - Ok(()) + if let Ok(contents) = std::fs::read_to_string(path) { + let lines: Vec<_> = contents + .lines() + .map(|line| line.chars().collect::>()) + .collect(); + self.file_path = Some(path.clone()); + self.state.lines = Lines::new(lines); + return Ok(()); + } + Err(anyhow::Error::msg("Failed to set editor file contents")) } pub fn save(&self) -> Result<()> { diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index a8aec73..0c843b1 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -1,18 +1,19 @@ -use crate::tui::component::Component; +use crate::tui::component::{Action, Component}; use anyhow::Result; use ratatui::buffer::Buffer; -use ratatui::layout::{Alignment, Rect}; +use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind}; +use ratatui::layout::{Alignment, Position, Rect}; use ratatui::prelude::Style; -use ratatui::style::Color; -use ratatui::widgets::{Block, Borders, Widget}; +use ratatui::style::{Color, Modifier}; +use ratatui::widgets::{Block, Borders, StatefulWidget}; use std::fs; -use tui_tree_widget::{Tree, TreeItem}; -use uuid::Uuid; +use tui_tree_widget::{Tree, TreeItem, TreeState}; -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Explorer<'a> { root_path: std::path::PathBuf, tree_items: TreeItem<'a, String>, + tree_state: TreeState, } impl<'a> Explorer<'a> { @@ -20,6 +21,7 @@ impl<'a> Explorer<'a> { let explorer = Explorer { root_path: path.to_owned(), tree_items: Self::build_tree_from_path(path.to_owned()), + tree_state: TreeState::default(), }; explorer } @@ -36,19 +38,32 @@ impl<'a> Explorer<'a> { 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() - .expect("Failed to get file name from path.") - .to_string_lossy() - .to_string(), - )); + if let Ok(path) = std::path::absolute(&path) { + let path_str = path.to_string_lossy().to_string(); + children.push(TreeItem::new_leaf( + path_str, + path.file_name() + .expect("Failed to get file name from path.") + .to_string_lossy() + .to_string(), + )); + } } } } + let abs = std::path::absolute(&path) + .expect( + format!( + "Failed to find absolute path for TreeItem: {}", + path.to_string_lossy().to_string() + ) + .as_str(), + ) + .to_string_lossy() + .to_string(); TreeItem::new( - Uuid::new_v4().to_string(), + abs, path.file_name() .expect("Failed to get file name from path.") .to_string_lossy() @@ -57,26 +72,38 @@ impl<'a> Explorer<'a> { ) .expect("Failed to build tree from path.") } -} -impl<'a> Widget for &Explorer<'a> { - fn render(self, area: Rect, buf: &mut Buffer) { - Tree::new(&self.tree_items.children()) - .expect("Failed to build tree.") - .style(Style::default()) - .block( - Block::default() - .borders(Borders::ALL) - .title( - self.root_path - .file_name() - .expect("Failed to get file name from path.") - .to_string_lossy(), - ) - .title_style(Style::default().fg(Color::Green)) - .title_alignment(Alignment::Center), - ) - .render(area, buf); + pub fn render(&mut self, area: Rect, buf: &mut Buffer) { + StatefulWidget::render( + Tree::new(&self.tree_items.children()) + .expect("Failed to build tree.") + .style(Style::default()) + .block( + Block::default() + .borders(Borders::ALL) + .title( + self.root_path + .file_name() + .expect("Failed to get file name from path.") + .to_string_lossy(), + ) + .title_style(Style::default().fg(Color::Green)) + .title_alignment(Alignment::Center), + ) + .highlight_style( + Style::new() + .fg(Color::Black) + .bg(Color::Rgb(57, 59, 64)) + .add_modifier(Modifier::BOLD), + ), + area, + buf, + &mut self.tree_state, + ) + } + + pub fn selected(&self) -> Option<&String> { + self.tree_state.selected().last() } } @@ -84,4 +111,54 @@ impl<'a> Component for Explorer<'a> { fn id(&self) -> &str { "explorer" } + fn handle_event(&mut self, event: Event) -> Action { + if let Some(key_event) = event.as_key_event() { + // Handle events here that should not be passed on to the vim emulation handler. + match self.handle_key_events(key_event) { + Action::Handled => return Action::Handled, + _ => {} + } + } + if let Some(mouse_event) = event.as_mouse_event() { + match self.handle_mouse_events(mouse_event) { + Action::Handled => return Action::Handled, + _ => {} + } + } + Action::Pass + } + + fn handle_key_events(&mut self, key: KeyEvent) -> Action { + let changed = match key.code { + KeyCode::Up => self.tree_state.key_up(), + KeyCode::Char('k') => self.tree_state.key_up(), + KeyCode::Down => self.tree_state.key_down(), + KeyCode::Char('j') => self.tree_state.key_down(), + KeyCode::Left => self.tree_state.key_left(), + KeyCode::Char('h') => self.tree_state.key_left(), + KeyCode::Right => self.tree_state.key_right(), + KeyCode::Char('l') => self.tree_state.key_right(), + KeyCode::Enter => self.tree_state.toggle_selected(), + _ => false, + }; + if changed { + return Action::Handled; + } + Action::Noop + } + + fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Action { + let changed = match mouse.kind { + MouseEventKind::ScrollDown => self.tree_state.scroll_down(1), + MouseEventKind::ScrollUp => self.tree_state.scroll_up(1), + MouseEventKind::Down(_button) => self + .tree_state + .click_at(Position::new(mouse.column, mouse.row)), + _ => false, + }; + if changed { + return Action::Handled; + } + Action::Noop + } }