TUI #1
@ -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 {
|
||||
|
||||
@ -17,7 +17,7 @@ use ratatui::widgets::{Block, Borders, Padding, Widget};
|
||||
pub struct Editor {
|
||||
pub state: EditorState,
|
||||
pub event_handler: EditorEventHandler,
|
||||
file_path: Option<std::path::PathBuf>,
|
||||
pub file_path: Option<std::path::PathBuf>,
|
||||
}
|
||||
|
||||
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()));
|
||||
if let Ok(contents) = std::fs::read_to_string(path) {
|
||||
let lines: Vec<_> = contents
|
||||
.lines()
|
||||
.map(|line| line.chars().collect::<Vec<char>>())
|
||||
.collect();
|
||||
self.file_path = Some(path.clone());
|
||||
self.state.lines = Lines::new(lines);
|
||||
Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
Err(anyhow::Error::msg("Failed to set editor file contents"))
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<()> {
|
||||
|
||||
@ -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<String>,
|
||||
}
|
||||
|
||||
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,8 +38,10 @@ impl<'a> Explorer<'a> {
|
||||
if path.is_dir() {
|
||||
children.push(Self::build_tree_from_path(path));
|
||||
} else {
|
||||
if let Ok(path) = std::path::absolute(&path) {
|
||||
let path_str = path.to_string_lossy().to_string();
|
||||
children.push(TreeItem::new_leaf(
|
||||
Uuid::new_v4().to_string(),
|
||||
path_str,
|
||||
path.file_name()
|
||||
.expect("Failed to get file name from path.")
|
||||
.to_string_lossy()
|
||||
@ -46,9 +50,20 @@ impl<'a> Explorer<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,10 +72,9 @@ 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) {
|
||||
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())
|
||||
@ -76,7 +90,20 @@ impl<'a> Widget for &Explorer<'a> {
|
||||
.title_style(Style::default().fg(Color::Green))
|
||||
.title_alignment(Alignment::Center),
|
||||
)
|
||||
.render(area, buf);
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user