[tui] File explorer controls editor contents.
This commit is contained in:
parent
f10d4cd41d
commit
e65eb20048
@ -129,6 +129,16 @@ impl<'a> Widget for &mut App<'a> {
|
|||||||
self.explorer.render(horizontal[0], buf);
|
self.explorer.render(horizontal[0], buf);
|
||||||
|
|
||||||
self.draw_tabs(editor_layout[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);
|
self.editor.render(editor_layout[1], buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,13 +160,14 @@ impl<'a> Component for App<'a> {
|
|||||||
match self.handle_key_events(key_event) {
|
match self.handle_key_events(key_event) {
|
||||||
Action::Quit => return Action::Quit,
|
Action::Quit => return Action::Quit,
|
||||||
Action::Handled => {
|
Action::Handled => {
|
||||||
dbg!(format!("Handled event: {:?}", self.id()));
|
// dbg!(format!("Handled event: {:?}", self.id()));
|
||||||
return Action::Handled;
|
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.
|
// Handle events for all components.
|
||||||
// for component in &mut self.components {
|
// for component in &mut self.components {
|
||||||
|
|||||||
@ -17,7 +17,7 @@ use ratatui::widgets::{Block, Borders, Padding, Widget};
|
|||||||
pub struct Editor {
|
pub struct Editor {
|
||||||
pub state: EditorState,
|
pub state: EditorState,
|
||||||
pub event_handler: EditorEventHandler,
|
pub event_handler: EditorEventHandler,
|
||||||
file_path: Option<std::path::PathBuf>,
|
pub file_path: Option<std::path::PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Editor {
|
impl Editor {
|
||||||
@ -30,15 +30,16 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_contents(&mut self, path: &std::path::PathBuf) -> Result<()> {
|
pub fn set_contents(&mut self, path: &std::path::PathBuf) -> Result<()> {
|
||||||
let contents = std::fs::read_to_string(path)
|
if let Ok(contents) = std::fs::read_to_string(path) {
|
||||||
.expect(&format!("Failed to read file contents: {}", path.display()));
|
let lines: Vec<_> = contents
|
||||||
let lines: Vec<_> = contents
|
.lines()
|
||||||
.lines()
|
.map(|line| line.chars().collect::<Vec<char>>())
|
||||||
.map(|line| line.chars().collect::<Vec<char>>())
|
.collect();
|
||||||
.collect();
|
self.file_path = Some(path.clone());
|
||||||
self.file_path = Some(path.clone());
|
self.state.lines = Lines::new(lines);
|
||||||
self.state.lines = Lines::new(lines);
|
return Ok(());
|
||||||
Ok(())
|
}
|
||||||
|
Err(anyhow::Error::msg("Failed to set editor file contents"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&self) -> Result<()> {
|
pub fn save(&self) -> Result<()> {
|
||||||
|
|||||||
@ -1,18 +1,19 @@
|
|||||||
use crate::tui::component::Component;
|
use crate::tui::component::{Action, Component};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ratatui::buffer::Buffer;
|
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::prelude::Style;
|
||||||
use ratatui::style::Color;
|
use ratatui::style::{Color, Modifier};
|
||||||
use ratatui::widgets::{Block, Borders, Widget};
|
use ratatui::widgets::{Block, Borders, StatefulWidget};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use tui_tree_widget::{Tree, TreeItem};
|
use tui_tree_widget::{Tree, TreeItem, TreeState};
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Explorer<'a> {
|
pub struct Explorer<'a> {
|
||||||
root_path: std::path::PathBuf,
|
root_path: std::path::PathBuf,
|
||||||
tree_items: TreeItem<'a, String>,
|
tree_items: TreeItem<'a, String>,
|
||||||
|
tree_state: TreeState<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Explorer<'a> {
|
impl<'a> Explorer<'a> {
|
||||||
@ -20,6 +21,7 @@ impl<'a> Explorer<'a> {
|
|||||||
let explorer = Explorer {
|
let explorer = Explorer {
|
||||||
root_path: path.to_owned(),
|
root_path: path.to_owned(),
|
||||||
tree_items: Self::build_tree_from_path(path.to_owned()),
|
tree_items: Self::build_tree_from_path(path.to_owned()),
|
||||||
|
tree_state: TreeState::default(),
|
||||||
};
|
};
|
||||||
explorer
|
explorer
|
||||||
}
|
}
|
||||||
@ -36,19 +38,32 @@ impl<'a> Explorer<'a> {
|
|||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
children.push(Self::build_tree_from_path(path));
|
children.push(Self::build_tree_from_path(path));
|
||||||
} else {
|
} else {
|
||||||
children.push(TreeItem::new_leaf(
|
if let Ok(path) = std::path::absolute(&path) {
|
||||||
Uuid::new_v4().to_string(),
|
let path_str = path.to_string_lossy().to_string();
|
||||||
path.file_name()
|
children.push(TreeItem::new_leaf(
|
||||||
.expect("Failed to get file name from path.")
|
path_str,
|
||||||
.to_string_lossy()
|
path.file_name()
|
||||||
.to_string(),
|
.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(
|
TreeItem::new(
|
||||||
Uuid::new_v4().to_string(),
|
abs,
|
||||||
path.file_name()
|
path.file_name()
|
||||||
.expect("Failed to get file name from path.")
|
.expect("Failed to get file name from path.")
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
@ -57,26 +72,38 @@ impl<'a> Explorer<'a> {
|
|||||||
)
|
)
|
||||||
.expect("Failed to build tree from path.")
|
.expect("Failed to build tree from path.")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Widget for &Explorer<'a> {
|
pub fn render(&mut self, area: Rect, buf: &mut Buffer) {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
StatefulWidget::render(
|
||||||
Tree::new(&self.tree_items.children())
|
Tree::new(&self.tree_items.children())
|
||||||
.expect("Failed to build tree.")
|
.expect("Failed to build tree.")
|
||||||
.style(Style::default())
|
.style(Style::default())
|
||||||
.block(
|
.block(
|
||||||
Block::default()
|
Block::default()
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.title(
|
.title(
|
||||||
self.root_path
|
self.root_path
|
||||||
.file_name()
|
.file_name()
|
||||||
.expect("Failed to get file name from path.")
|
.expect("Failed to get file name from path.")
|
||||||
.to_string_lossy(),
|
.to_string_lossy(),
|
||||||
)
|
)
|
||||||
.title_style(Style::default().fg(Color::Green))
|
.title_style(Style::default().fg(Color::Green))
|
||||||
.title_alignment(Alignment::Center),
|
.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 {
|
fn id(&self) -> &str {
|
||||||
"explorer"
|
"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