1
0
mirror of https://github.com/chylex/Bark-Browser.git synced 2024-11-26 01:42:51 +01:00

Compare commits

...

5 Commits

29 changed files with 435 additions and 280 deletions

View File

@ -5,6 +5,7 @@ use crossterm::event::{Event, KeyEventKind};
use crate::input::keymap::KeyBinding; use crate::input::keymap::KeyBinding;
use crate::state::{Environment, State}; use crate::state::{Environment, State};
use crate::state::action::ActionResult; use crate::state::action::ActionResult;
use crate::state::event::EventResult;
use crate::state::view::View; use crate::state::view::View;
pub fn run(start_path: &Path) -> std::io::Result<()> { pub fn run(start_path: &Path) -> std::io::Result<()> {
@ -14,41 +15,55 @@ pub fn run(start_path: &Path) -> std::io::Result<()> {
let environment = Environment::try_from(&view)?; let environment = Environment::try_from(&view)?;
let mut state = State::with_root_path(start_path, environment); let mut state = State::with_root_path(start_path, environment);
'render: loop { loop {
match state.handle_events() {
EventResult::Nothing => {}
EventResult::Draw => {
view.set_dirty(false);
}
EventResult::Redraw => {
view.set_dirty(true);
}
}
view.render(|frame| state.render(frame))?; view.render(|frame| state.render(frame))?;
'event: loop { match handle_terminal_event(&mut state, crossterm::event::read()?) {
match handle_event(&mut state, crossterm::event::read()?) { ActionResult::Nothing => {
ActionResult::Nothing => { continue;
continue 'event; }
}
ActionResult::Draw => { ActionResult::Draw => {
continue 'render; view.set_dirty(false);
} continue;
}
ActionResult::Redraw => { ActionResult::Redraw => {
view.clear()?; view.set_dirty(true);
continue 'render; continue;
} }
ActionResult::PushLayer(layer) => { ActionResult::PushLayer(layer) => {
state.push_layer(layer); state.push_layer(layer);
continue 'render; view.set_dirty(false);
} continue;
}
ActionResult::ReplaceLayer(layer) => { ActionResult::ReplaceLayer(layer) => {
state.pop_layer(); state.pop_layer();
state.push_layer(layer); state.push_layer(layer);
continue 'render; view.set_dirty(false);
} continue;
}
ActionResult::PopLayer => { ActionResult::PopLayer => {
if state.pop_layer() { if state.pop_layer() {
break 'render; break;
} else { } else {
continue 'render; view.set_dirty(false);
} continue;
} }
} }
} }
@ -58,7 +73,7 @@ pub fn run(start_path: &Path) -> std::io::Result<()> {
} }
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
fn handle_event(state: &mut State, event: Event) -> ActionResult { fn handle_terminal_event(state: &mut State, event: Event) -> ActionResult {
if let Event::Key(key) = event { if let Event::Key(key) = event {
if key.kind == KeyEventKind::Release { if key.kind == KeyEventKind::Release {
ActionResult::Nothing ActionResult::Nothing

View File

@ -69,7 +69,7 @@ impl<'a> InputFieldDialogBuilder5<'a> {
self self
} }
pub fn build(self, confirm_action: Box<dyn Fn(String) -> ActionResult>) -> InputFieldDialogLayer<'a> { pub fn on_confirm<F>(self, confirm_action: F) -> InputFieldDialogLayer<'a> where F: Fn(String) -> ActionResult + 'static {
let step4 = self.step4; let step4 = self.step4;
let step3 = step4.step3; let step3 = step4.step3;
let step2 = step3.step2; let step2 = step3.step2;

View File

@ -12,6 +12,7 @@ use crate::component::input::InputField;
use crate::input::keymap::KeyBinding; use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult; use crate::state::action::ActionResult;
use crate::state::Environment; use crate::state::Environment;
use crate::state::event::EventResult;
use crate::state::layer::Layer; use crate::state::layer::Layer;
use crate::state::view::Frame; use crate::state::view::Frame;
@ -29,8 +30,9 @@ pub struct InputFieldDialogLayer<'a> {
} }
impl<'a> InputFieldDialogLayer<'a> { impl<'a> InputFieldDialogLayer<'a> {
fn new(y: u16, min_width: u16, default_color: Color, darker_color: Color, title: Line<'a>, message: Text<'a>, initial_value: Option<String>, confirm_action: Box<dyn Fn(String) -> ActionResult>) -> Self { fn new<F>(y: u16, min_width: u16, default_color: Color, darker_color: Color, title: Line<'a>, message: Text<'a>, initial_value: Option<String>, confirm_action: F) -> Self where F: Fn(String) -> ActionResult + 'static {
let field = initial_value.map_or_else(InputField::new, InputField::with_text); let field = initial_value.map_or_else(InputField::new, InputField::with_text);
let confirm_action = Box::new(confirm_action);
Self { y, min_width, default_color, darker_color, title, message, field, confirm_action } Self { y, min_width, default_color, darker_color, title, message, field, confirm_action }
} }
@ -58,6 +60,10 @@ impl<'a> Layer for InputFieldDialogLayer<'a> {
} }
} }
fn handle_events(&mut self, _environment: &Environment) -> EventResult {
EventResult::Nothing
}
fn render(&mut self, frame: &mut Frame) { fn render(&mut self, frame: &mut Frame) {
let message_width = u16::try_from(self.message.width()).unwrap_or(u16::MAX); let message_width = u16::try_from(self.message.width()).unwrap_or(u16::MAX);
let message_height = u16::try_from(self.message.height()).unwrap_or(u16::MAX); let message_height = u16::try_from(self.message.height()).unwrap_or(u16::MAX);

View File

@ -37,9 +37,9 @@ impl<'a> MessageDialogActionMap<'a> {
]) ])
} }
pub fn yes_no(yes_action: Box<dyn Fn() -> ActionResult>) -> Self { pub fn yes_no<F>(yes_action: F) -> Self where F: Fn() -> ActionResult + 'static {
let mut map = ActionHashMap::new(); let mut map = ActionHashMap::new();
map.insert(KeyBinding::char('y'), yes_action); map.insert(KeyBinding::char('y'), Box::new(yes_action));
map.insert(KeyBinding::char('n'), Box::new(|| ActionResult::PopLayer)); map.insert(KeyBinding::char('n'), Box::new(|| ActionResult::PopLayer));
Self::new(map, vec![ Self::new(map, vec![

View File

@ -63,7 +63,7 @@ impl<'a> MessageDialogBuilder4<'a> {
self.actions(MessageDialogActionMap::ok()) self.actions(MessageDialogActionMap::ok())
} }
pub fn yes_no(self, yes_action: Box<dyn Fn() -> ActionResult>) -> MessageDialogLayer<'a> { pub fn yes_no<F>(self, yes_action: F) -> MessageDialogLayer<'a> where F: Fn() -> ActionResult + 'static {
self.actions(MessageDialogActionMap::yes_no(yes_action)) self.actions(MessageDialogActionMap::yes_no(yes_action))
} }
} }

View File

@ -10,6 +10,7 @@ use crate::component::dialog::render_dialog_border;
use crate::input::keymap::KeyBinding; use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult; use crate::state::action::ActionResult;
use crate::state::Environment; use crate::state::Environment;
use crate::state::event::EventResult;
use crate::state::layer::Layer; use crate::state::layer::Layer;
use crate::state::view::Frame; use crate::state::view::Frame;
@ -40,7 +41,7 @@ impl<'a> MessageDialogLayer<'a> {
MessageDialogBuilder MessageDialogBuilder
} }
pub fn generic_error(y: u16, message: impl Into<Text<'a>>) -> MessageDialogLayer<'a> { pub fn error(y: u16, message: impl Into<Text<'a>>) -> MessageDialogLayer<'a> {
Self::build() Self::build()
.y(y) .y(y)
.color(Color::LightRed) .color(Color::LightRed)
@ -55,6 +56,10 @@ impl Layer for MessageDialogLayer<'_> {
self.actions.handle_input(key_binding) self.actions.handle_input(key_binding)
} }
fn handle_events(&mut self, _environment: &Environment) -> EventResult {
EventResult::Nothing
}
fn render(&mut self, frame: &mut Frame) { fn render(&mut self, frame: &mut Frame) {
let content_width = u16::try_from(self.message.width()).unwrap_or(u16::MAX); let content_width = u16::try_from(self.message.width()).unwrap_or(u16::MAX);
let content_height = u16::try_from(self.message.height()).unwrap_or(u16::MAX); let content_height = u16::try_from(self.message.height()).unwrap_or(u16::MAX);

View File

@ -17,7 +17,7 @@ impl Action<FsLayer> for PushCountDigit {
if new_count > MAX_COUNT { if new_count > MAX_COUNT {
layer.registers.count = None; layer.registers.count = None;
ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(layer.dialog_y(), format!("Count is too large (> {MAX_COUNT}), it will be reset.")))) ActionResult::push_layer(MessageDialogLayer::error(layer.dialog_y(), format!("Count is too large (> {MAX_COUNT}), it will be reset.")))
} else { } else {
layer.registers.count = Some(new_count); layer.registers.count = Some(new_count);
ActionResult::Nothing ActionResult::Nothing

View File

@ -1,14 +1,12 @@
use std::{fs, io}; use std::{fs, io};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::rc::Rc;
use ratatui::style::Color; use ratatui::style::Color;
use slab_tree::NodeId; use slab_tree::NodeId;
use crate::component::dialog::input::InputFieldDialogLayer; use crate::component::dialog::input::InputFieldDialogLayer;
use crate::component::dialog::message::MessageDialogLayer; use crate::component::dialog::message::MessageDialogLayer;
use crate::component::filesystem::action::file::{FileNode, get_selected_file}; use crate::component::filesystem::action::file::{FileNode, get_selected_file, RefreshParentDirectoryAndSelectFile};
use crate::component::filesystem::event::FsLayerEvent;
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::file::FileKind; use crate::file::FileKind;
use crate::state::action::{Action, ActionResult}; use crate::state::action::{Action, ActionResult};
@ -71,7 +69,7 @@ impl Action<FsLayer> for CreateDirectoryInSelectedDirectory {
fn create_in_selected_directory<T: CreateEntry>(layer: &mut FsLayer) -> ActionResult { fn create_in_selected_directory<T: CreateEntry>(layer: &mut FsLayer) -> ActionResult {
if let Some(FileNode { node, path, .. }) = get_selected_file(layer).filter(|n| matches!(n.entry.kind(), FileKind::Directory)) { if let Some(FileNode { node, path, .. }) = get_selected_file(layer).filter(|n| matches!(n.entry.kind(), FileKind::Directory)) {
ActionResult::PushLayer(Box::new(create_new_name_prompt::<T>(layer, path.to_owned(), node.node_id()))) ActionResult::push_layer(create_new_name_prompt::<T>(layer, path.to_owned(), node.node_id()))
} else { } else {
ActionResult::Nothing ActionResult::Nothing
} }
@ -95,7 +93,7 @@ impl Action<FsLayer> for CreateDirectoryInParentOfSelectedEntry {
fn create_in_parent_of_selected_file<T: CreateEntry>(layer: &mut FsLayer) -> ActionResult { fn create_in_parent_of_selected_file<T: CreateEntry>(layer: &mut FsLayer) -> ActionResult {
if let Some((parent_node_id, parent_path)) = get_parent_of_selected_file(layer) { if let Some((parent_node_id, parent_path)) = get_parent_of_selected_file(layer) {
ActionResult::PushLayer(Box::new(create_new_name_prompt::<T>(layer, parent_path.to_owned(), parent_node_id))) ActionResult::push_layer(create_new_name_prompt::<T>(layer, parent_path.to_owned(), parent_node_id))
} else { } else {
ActionResult::Nothing ActionResult::Nothing
} }
@ -105,9 +103,9 @@ fn get_parent_of_selected_file(layer: &FsLayer) -> Option<(NodeId, &Path)> {
get_selected_file(layer).and_then(|n| { Some((n.node.parent_id()?, n.path.parent()?)) }) get_selected_file(layer).and_then(|n| { Some((n.node.parent_id()?, n.path.parent()?)) })
} }
fn create_new_name_prompt<'b, T: CreateEntry>(layer: &FsLayer, parent_folder: PathBuf, view_node_id_to_refresh: NodeId) -> InputFieldDialogLayer<'b> { fn create_new_name_prompt<'b, T: CreateEntry>(layer: &FsLayer, parent_folder: PathBuf, parent_view_node_id: NodeId) -> InputFieldDialogLayer<'b> {
let y = layer.dialog_y(); let y = layer.dialog_y();
let pending_events = Rc::clone(&layer.pending_events); let events = layer.events();
InputFieldDialogLayer::build() InputFieldDialogLayer::build()
.y(y) .y(y)
@ -115,25 +113,24 @@ fn create_new_name_prompt<'b, T: CreateEntry>(layer: &FsLayer, parent_folder: Pa
.color(Color::LightCyan, Color::Cyan) .color(Color::LightCyan, Color::Cyan)
.title(T::title()) .title(T::title())
.message(format!("Creating {} in {}", T::kind(), parent_folder.to_string_lossy())) .message(format!("Creating {} in {}", T::kind(), parent_folder.to_string_lossy()))
.build(Box::new(move |new_name| { .on_confirm(move |new_name| {
if new_name.is_empty() { if new_name.is_empty() {
return ActionResult::Nothing; return ActionResult::Nothing;
} }
let new_path = parent_folder.join(&new_name); let new_path = parent_folder.join(&new_name);
if new_path.exists() { if new_path.exists() {
return ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(y, "Something with this name already exists."))); return ActionResult::push_layer(MessageDialogLayer::error(y, "Something with this name already exists."));
} }
match T::create(new_path) { match T::create(new_path) {
Ok(_) => { Ok(_) => {
FsLayerEvent::RefreshViewNodeChildren(view_node_id_to_refresh).enqueue(&pending_events); events.enqueue(RefreshParentDirectoryAndSelectFile { parent_view_node_id, child_file_name: new_name });
FsLayerEvent::SelectViewNodeChildByFileName(view_node_id_to_refresh, new_name).enqueue(&pending_events);
ActionResult::PopLayer ActionResult::PopLayer
} }
Err(e) => { Err(e) => {
ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(y, format!("Could not create {}: {e}", T::kind())))) ActionResult::push_layer(MessageDialogLayer::error(y, format!("Could not create {}: {e}", T::kind())))
} }
} }
})) })
} }

View File

@ -1,6 +1,5 @@
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use ratatui::style::Color; use ratatui::style::Color;
@ -9,18 +8,18 @@ use slab_tree::NodeId;
use crate::component::dialog::message::MessageDialogLayer; use crate::component::dialog::message::MessageDialogLayer;
use crate::component::filesystem::action::file::{FileNode, get_entry_kind_name, get_selected_file}; use crate::component::filesystem::action::file::{FileNode, get_entry_kind_name, get_selected_file};
use crate::component::filesystem::event::FsLayerEvent;
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::file::{FileEntry, FileKind}; use crate::file::{FileEntry, FileKind};
use crate::state::action::{Action, ActionResult}; use crate::state::action::{Action, ActionResult};
use crate::state::Environment; use crate::state::Environment;
use crate::state::event::EventResult;
pub struct DeleteSelectedEntry; pub struct DeleteSelectedEntry;
impl Action<FsLayer> for DeleteSelectedEntry { impl Action<FsLayer> for DeleteSelectedEntry {
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult { fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
if let Some(FileNode { node, entry, path }) = get_selected_file(layer) { if let Some(FileNode { node, entry, path }) = get_selected_file(layer) {
ActionResult::PushLayer(Box::new(create_delete_confirmation_dialog(layer, node.node_id(), entry, path.to_owned()))) ActionResult::push_layer(create_delete_confirmation_dialog(layer, node.node_id(), entry, path.to_owned()))
} else { } else {
ActionResult::Nothing ActionResult::Nothing
} }
@ -29,7 +28,7 @@ impl Action<FsLayer> for DeleteSelectedEntry {
fn create_delete_confirmation_dialog<'a>(layer: &FsLayer, view_node_id: NodeId, entry: &FileEntry, path: PathBuf) -> MessageDialogLayer<'a> { fn create_delete_confirmation_dialog<'a>(layer: &FsLayer, view_node_id: NodeId, entry: &FileEntry, path: PathBuf) -> MessageDialogLayer<'a> {
let y = layer.dialog_y(); let y = layer.dialog_y();
let pending_events = Rc::clone(&layer.pending_events); let events = layer.events();
let total_files = if matches!(entry.kind(), FileKind::Directory) { let total_files = if matches!(entry.kind(), FileKind::Directory) {
count_files(path.clone()) count_files(path.clone())
@ -45,17 +44,17 @@ fn create_delete_confirmation_dialog<'a>(layer: &FsLayer, view_node_id: NodeId,
Line::from(format!("Permanently delete {}?", path.to_string_lossy())), Line::from(format!("Permanently delete {}?", path.to_string_lossy())),
Line::from(format!("This will affect {}.", total_files.describe())), Line::from(format!("This will affect {}.", total_files.describe())),
]) ])
.yes_no(Box::new(move || { .yes_no(move || {
match delete_path_recursively(&path) { match delete_path_recursively(&path) {
Ok(_) => { Ok(_) => {
FsLayerEvent::DeleteViewNode(view_node_id).enqueue(&pending_events); events.enqueue_fn(move |layer, _| EventResult::draw_if(layer.tree.delete_node(view_node_id)));
ActionResult::PopLayer ActionResult::PopLayer
} }
Err(e) => { Err(e) => {
ActionResult::ReplaceLayer(Box::new(MessageDialogLayer::generic_error(y.saturating_add(1), e.to_string()))) ActionResult::replace_layer(MessageDialogLayer::error(y.saturating_add(1), e.to_string()))
} }
} }
})) })
} }
const MAX_COUNT_TIME: Duration = Duration::from_secs(5); const MAX_COUNT_TIME: Duration = Duration::from_secs(5);

View File

@ -8,11 +8,11 @@ use slab_tree::NodeRef;
use crate::component::dialog::message::MessageDialogLayer; use crate::component::dialog::message::MessageDialogLayer;
use crate::component::filesystem::action::file::{FileNode, get_selected_file}; use crate::component::filesystem::action::file::{FileNode, get_selected_file};
use crate::component::filesystem::event::FsLayerEvent;
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::FsTreeViewNode; use crate::component::filesystem::tree::FsTreeViewNode;
use crate::state::action::{Action, ActionResult}; use crate::state::action::{Action, ActionResult};
use crate::state::Environment; use crate::state::Environment;
use crate::state::event::EventResult;
use crate::util::slab_tree::NodeRefExtensions; use crate::util::slab_tree::NodeRefExtensions;
pub struct EditSelectedEntry; pub struct EditSelectedEntry;
@ -34,12 +34,12 @@ fn open_default_editor(layer: &FsLayer, node: &NodeRef<FsTreeViewNode>, path: &P
.status(); .status();
if status.is_err_and(|e| e.kind() == ErrorKind::NotFound) { if status.is_err_and(|e| e.kind() == ErrorKind::NotFound) {
return ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(layer.dialog_y(), format!("Default editor '{}' not found.", editor.to_string_lossy())))); return ActionResult::push_layer(MessageDialogLayer::error(layer.dialog_y(), format!("Default editor '{}' not found.", editor.to_string_lossy())));
} }
// Refresh the parent directory, or the root node if this is the view root. // Refresh the parent directory, or the root node if this is the view root.
let node_id_to_refresh = node.parent_id().unwrap_or_else(|| node.node_id()); let node_id_to_refresh = node.parent_id().unwrap_or_else(|| node.node_id());
FsLayerEvent::RefreshViewNodeChildren(node_id_to_refresh).enqueue(&layer.pending_events); layer.events().enqueue_fn(move |layer, _| EventResult::draw_if(layer.tree.refresh_children(node_id_to_refresh)));
ActionResult::Redraw ActionResult::Redraw
} }

View File

@ -1,11 +1,13 @@
use std::io; use std::io;
use std::path::Path; use std::path::Path;
use slab_tree::NodeRef; use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::FsTreeViewNode; use crate::component::filesystem::tree::FsTreeViewNode;
use crate::file::{FileEntry, FileKind}; use crate::file::{FileEntry, FileKind};
use crate::state::Environment;
use crate::state::event::{Event, EventResult};
pub use self::create::*; pub use self::create::*;
pub use self::delete::*; pub use self::delete::*;
@ -18,7 +20,7 @@ mod edit;
mod rename; mod rename;
fn get_selected_file(layer: &FsLayer) -> Option<FileNode> { fn get_selected_file(layer: &FsLayer) -> Option<FileNode> {
if let Some(node) = layer.selected_node() { if let Some(node) = layer.tree.selected_node() {
if let Some(entry) = layer.tree.get_entry(&node) { if let Some(entry) = layer.tree.get_entry(&node) {
if let Some(path) = entry.path() { if let Some(path) = entry.path() {
return Some(FileNode { node, entry, path }); return Some(FileNode { node, entry, path });
@ -54,3 +56,19 @@ fn format_io_error(err: &io::Error) -> String {
str.push('.'); str.push('.');
str str
} }
struct RefreshParentDirectoryAndSelectFile {
parent_view_node_id: NodeId,
child_file_name: String,
}
impl Event<FsLayer> for RefreshParentDirectoryAndSelectFile {
fn dispatch(&self, layer: &mut FsLayer, _environment: &Environment) -> EventResult {
if layer.tree.refresh_children(self.parent_view_node_id) {
layer.tree.select_child_node_by_name(self.parent_view_node_id, &self.child_file_name);
EventResult::Draw
} else {
EventResult::Nothing
}
}
}

View File

@ -1,15 +1,13 @@
use io::ErrorKind; use io::ErrorKind;
use std::{fs, io}; use std::{fs, io};
use std::path::PathBuf; use std::path::PathBuf;
use std::rc::Rc;
use ratatui::style::Color; use ratatui::style::Color;
use slab_tree::NodeRef; use slab_tree::NodeRef;
use crate::component::dialog::input::InputFieldDialogLayer; use crate::component::dialog::input::InputFieldDialogLayer;
use crate::component::dialog::message::MessageDialogLayer; use crate::component::dialog::message::MessageDialogLayer;
use crate::component::filesystem::action::file::{FileNode, format_io_error, get_entry_kind_name, get_selected_file}; use crate::component::filesystem::action::file::{FileNode, format_io_error, get_entry_kind_name, get_selected_file, RefreshParentDirectoryAndSelectFile};
use crate::component::filesystem::event::FsLayerEvent;
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::FsTreeViewNode; use crate::component::filesystem::tree::FsTreeViewNode;
use crate::file::FileEntry; use crate::file::FileEntry;
@ -24,7 +22,7 @@ pub struct RenameSelectedEntry {
impl Action<FsLayer> for RenameSelectedEntry { impl Action<FsLayer> for RenameSelectedEntry {
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult { fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
if let Some(FileNode { node, entry, path }) = get_selected_file(layer) { if let Some(FileNode { node, entry, path }) = get_selected_file(layer) {
ActionResult::PushLayer(Box::new(self.create_rename_dialog(layer, &node, entry, path.to_owned()))) ActionResult::push_layer(self.create_rename_dialog(layer, &node, entry, path.to_owned()))
} else { } else {
ActionResult::Nothing ActionResult::Nothing
} }
@ -35,8 +33,8 @@ impl RenameSelectedEntry {
#[allow(clippy::wildcard_enum_match_arm)] #[allow(clippy::wildcard_enum_match_arm)]
fn create_rename_dialog<'a, 'b>(&'a self, layer: &'a FsLayer, node: &'a NodeRef<FsTreeViewNode>, entry: &'a FileEntry, path: PathBuf) -> InputFieldDialogLayer<'b> { fn create_rename_dialog<'a, 'b>(&'a self, layer: &'a FsLayer, node: &'a NodeRef<FsTreeViewNode>, entry: &'a FileEntry, path: PathBuf) -> InputFieldDialogLayer<'b> {
let y = layer.dialog_y(); let y = layer.dialog_y();
let parent_node_id = node.parent_id(); let events = layer.events();
let pending_events = Rc::clone(&layer.pending_events); let parent_view_node_id = node.parent_id();
InputFieldDialogLayer::build() InputFieldDialogLayer::build()
.y(y) .y(y)
@ -45,20 +43,19 @@ impl RenameSelectedEntry {
.title(format!("Rename {}", get_entry_kind_name(entry))) .title(format!("Rename {}", get_entry_kind_name(entry)))
.message(format!("Renaming {}", path.to_string_lossy())) .message(format!("Renaming {}", path.to_string_lossy()))
.initial_value(self.prefill.then(|| entry.name().str().to_owned())) .initial_value(self.prefill.then(|| entry.name().str().to_owned()))
.build(Box::new(move |new_name| { .on_confirm(move |new_name| {
match rename_file(&path, &new_name) { match rename_file(&path, &new_name) {
Ok(_) => { Ok(_) => {
if let Some(parent_node_id) = parent_node_id { if let Some(parent_view_node_id) = parent_view_node_id {
FsLayerEvent::RefreshViewNodeChildren(parent_node_id).enqueue(&pending_events); events.enqueue(RefreshParentDirectoryAndSelectFile { parent_view_node_id, child_file_name: new_name });
FsLayerEvent::SelectViewNodeChildByFileName(parent_node_id, new_name).enqueue(&pending_events);
} }
ActionResult::PopLayer ActionResult::PopLayer
} }
Err(e) => { Err(e) => {
ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(y.saturating_add(1), format_io_error(&e)))) ActionResult::push_layer(MessageDialogLayer::error(y.saturating_add(1), format_io_error(&e)))
} }
} }
})) })
} }
} }

View File

@ -1,14 +1,15 @@
use slab_tree::NodeId; use slab_tree::NodeId;
use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, perform_movement_with_count, SimpleMovementAction}; use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, perform_movement_with_count_from_register, SimpleMovementAction};
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::FsTree;
use crate::state::Environment; use crate::state::Environment;
pub struct ExpandSelectedOr<M: SimpleMovementAction>(pub M); pub struct ExpandSelectedOr<M: SimpleMovementAction>(pub M);
impl<M: SimpleMovementAction> MovementAction for ExpandSelectedOr<M> { impl<M: SimpleMovementAction> MovementAction for ExpandSelectedOr<M> {
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
Some(perform_action_or_movement::<M, _>(layer, FsLayer::expand)) Some(perform_action_or_movement::<M, _>(layer, FsTree::expand))
} }
} }
@ -16,16 +17,16 @@ pub struct CollapseSelectedOr<M: SimpleMovementAction>(pub M);
impl<M: SimpleMovementAction> MovementAction for CollapseSelectedOr<M> { impl<M: SimpleMovementAction> MovementAction for CollapseSelectedOr<M> {
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
Some(perform_action_or_movement::<M, _>(layer, FsLayer::collapse)) Some(perform_action_or_movement::<M, _>(layer, FsTree::collapse))
} }
} }
fn perform_action_or_movement<M: SimpleMovementAction, F>(layer: &mut FsLayer, action: F) -> NodeId where F: Fn(&mut FsLayer, NodeId) -> bool { fn perform_action_or_movement<M: SimpleMovementAction, F>(layer: &mut FsLayer, action: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> bool {
perform_movement_with_count(layer, layer.registers.count, |layer, node_id| { perform_movement_with_count_from_register(layer, |tree, node_id| {
if action(layer, node_id) { if action(tree, node_id) {
Some(node_id) Some(node_id)
} else { } else {
get_simple_movement_target::<M>(layer, node_id) get_simple_movement_target::<M>(tree, node_id)
} }
}) })
} }

View File

@ -1,8 +1,8 @@
use slab_tree::{NodeId, NodeRef}; use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::action::movement::{MovementAction, perform_movement_with_count, SimpleMovementAction}; use crate::component::filesystem::action::movement::{MovementAction, perform_movement_with_count_from_register, SimpleMovementAction};
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode}; use crate::component::filesystem::tree::{FsTree, FsTreeView, FsTreeViewNode};
use crate::state::Environment; use crate::state::Environment;
use crate::util::slab_tree::NodeRefExtensions; use crate::util::slab_tree::NodeRefExtensions;
@ -46,19 +46,17 @@ pub struct MoveOrTraverseUpParent;
impl MovementAction for MoveOrTraverseUpParent { impl MovementAction for MoveOrTraverseUpParent {
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
Some(perform_movement_with_count(layer, layer.registers.count, Self::get_target)) Some(perform_movement_with_count_from_register(layer, Self::get_target))
} }
} }
impl MoveOrTraverseUpParent { impl MoveOrTraverseUpParent {
fn get_target(layer: &mut FsLayer, node_id: NodeId) -> Option<NodeId> { fn get_target(tree: &mut FsTree, node_id: NodeId) -> Option<NodeId> {
let view = &layer.tree.view; if let Some(node) = tree.view.get(node_id) {
let target_node_id = <MoveToParent as SimpleMovementAction>::get_target(&tree.view, &node);
if let Some(node) = view.get(node_id) {
let target_node_id = <MoveToParent as SimpleMovementAction>::get_target(view, &node);
if target_node_id.is_some() { if target_node_id.is_some() {
return target_node_id; return target_node_id;
} else if let Some(target_node_id) = layer.traverse_up_root() { } else if let Some(target_node_id) = tree.traverse_up_root() {
return Some(target_node_id) return Some(target_node_id)
} }
} }

View File

@ -2,7 +2,7 @@ use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, MoveToParent, perform_movement_with_count_from, SimpleMovementAction}; use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, MoveToParent, perform_movement_with_count_from, SimpleMovementAction};
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode}; use crate::component::filesystem::tree::{FsTree, FsTreeView, FsTreeViewNode};
use crate::state::Environment; use crate::state::Environment;
/// Moves up `count` lines (1 line by default). /// Moves up `count` lines (1 line by default).
@ -28,16 +28,15 @@ pub struct MoveToFirst;
impl MovementAction for MoveToFirst { impl MovementAction for MoveToFirst {
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
Some(Self::get_target(layer)) Some(Self::get_target(&mut layer.tree))
} }
} }
impl MoveToFirst { impl MoveToFirst {
fn get_target(layer: &mut FsLayer) -> NodeId where Self: Sized { fn get_target(tree: &mut FsTree) -> NodeId where Self: Sized {
let view = &layer.tree.view; let mut target_node_id = tree.selected_view_node_id;
let mut target_node_id = layer.selected_view_node_id;
while let Some(node_id) = view.get(target_node_id).and_then(|node| <MoveToParent as SimpleMovementAction>::get_target(view, &node)) { while let Some(node_id) = tree.view.get(target_node_id).and_then(|node| <MoveToParent as SimpleMovementAction>::get_target(&tree.view, &node)) {
target_node_id = node_id; target_node_id = node_id;
} }
@ -50,7 +49,7 @@ pub struct MoveToLast;
impl MovementAction for MoveToLast { impl MovementAction for MoveToLast {
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
let first_node_id = MoveToFirst::get_target(layer); let first_node_id = MoveToFirst::get_target(&mut layer.tree);
let last_node_id = layer.tree.view.get_last_descendant_or_self(first_node_id); let last_node_id = layer.tree.view.get_last_descendant_or_self(first_node_id);
Some(last_node_id) Some(last_node_id)
} }
@ -63,9 +62,10 @@ pub struct MoveToLineOr<A: MovementAction>(pub A);
impl<A: MovementAction> MovementAction for MoveToLineOr<A> { impl<A: MovementAction> MovementAction for MoveToLineOr<A> {
fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized {
if let Some(line_number) = layer.registers.count { if let Some(line_number) = layer.registers.count {
let tree = &mut layer.tree;
let line_index = Some(line_number.saturating_sub(1)); let line_index = Some(line_number.saturating_sub(1));
let first_node_id = MoveToFirst::get_target(layer); let first_node_id = MoveToFirst::get_target(tree);
Some(perform_movement_with_count_from(layer, line_index, first_node_id, get_simple_movement_target::<MoveDown>)) Some(perform_movement_with_count_from(tree, line_index, first_node_id, get_simple_movement_target::<MoveDown>))
} else { } else {
self.0.get_target(layer, environment) self.0.get_target(layer, environment)
} }

View File

@ -1,7 +1,7 @@
use slab_tree::{NodeId, NodeRef}; use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode}; use crate::component::filesystem::tree::{FsTree, FsTreeView, FsTreeViewNode};
use crate::state::action::{Action, ActionResult}; use crate::state::action::{Action, ActionResult};
use crate::state::Environment; use crate::state::Environment;
@ -25,7 +25,7 @@ pub trait MovementAction {
impl<T: MovementAction> Action<FsLayer> for T { impl<T: MovementAction> Action<FsLayer> for T {
fn perform(&self, layer: &mut FsLayer, environment: &Environment) -> ActionResult { fn perform(&self, layer: &mut FsLayer, environment: &Environment) -> ActionResult {
if let Some(target_node_id) = self.get_target(layer, environment) { if let Some(target_node_id) = self.get_target(layer, environment) {
layer.selected_view_node_id = target_node_id; layer.tree.selected_view_node_id = target_node_id;
ActionResult::Draw ActionResult::Draw
} else { } else {
ActionResult::Nothing ActionResult::Nothing
@ -33,15 +33,19 @@ impl<T: MovementAction> Action<FsLayer> for T {
} }
} }
fn perform_movement_with_count<F>(layer: &mut FsLayer, count: Option<usize>, get_target: F) -> NodeId where F: Fn(&mut FsLayer, NodeId) -> Option<NodeId> { fn perform_movement_with_count_from_register<F>(layer: &mut FsLayer, get_target: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> Option<NodeId> {
perform_movement_with_count_from(layer, count, layer.selected_view_node_id, get_target) perform_movement_with_count(&mut layer.tree, layer.registers.count, get_target)
} }
fn perform_movement_with_count_from<F>(layer: &mut FsLayer, count: Option<usize>, start_node_id: NodeId, get_target: F) -> NodeId where F: Fn(&mut FsLayer, NodeId) -> Option<NodeId> { fn perform_movement_with_count<F>(tree: &mut FsTree, count: Option<usize>, get_target: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> Option<NodeId> {
perform_movement_with_count_from(tree, count, tree.selected_view_node_id, get_target)
}
fn perform_movement_with_count_from<F>(tree: &mut FsTree, count: Option<usize>, start_node_id: NodeId, get_target: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> Option<NodeId> {
let mut target_node_id = start_node_id; let mut target_node_id = start_node_id;
for _ in 0..count.unwrap_or(1) { for _ in 0..count.unwrap_or(1) {
if let Some(node_id) = get_target(layer, target_node_id) { if let Some(node_id) = get_target(tree, target_node_id) {
target_node_id = node_id; target_node_id = node_id;
} else { } else {
break; break;
@ -57,11 +61,10 @@ pub trait SimpleMovementAction {
impl<T: SimpleMovementAction> MovementAction for T { impl<T: SimpleMovementAction> MovementAction for T {
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
Some(perform_movement_with_count(layer, layer.registers.count, get_simple_movement_target::<T>)) Some(perform_movement_with_count_from_register(layer, get_simple_movement_target::<T>))
} }
} }
fn get_simple_movement_target<T: SimpleMovementAction>(layer: &mut FsLayer, node_id: NodeId) -> Option<NodeId> { fn get_simple_movement_target<T: SimpleMovementAction>(tree: &mut FsTree, node_id: NodeId) -> Option<NodeId> {
let view = &layer.tree.view; tree.view.get(node_id).and_then(|node| T::get_target(&tree.view, &node))
view.get(node_id).and_then(|node| T::get_target(view, &node))
} }

View File

@ -37,7 +37,7 @@ impl<A: SimpleMovementAction + 'static, C: MovementCount> MovementWithCountFacto
impl<A: SimpleMovementAction, C: MovementCount> MovementAction for MovementWithCount<A, C> { impl<A: SimpleMovementAction, C: MovementCount> MovementAction for MovementWithCount<A, C> {
fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized {
let count = self.0.get_count(layer.registers.count, environment); let count = self.0.get_count(layer.registers.count, environment);
Some(perform_movement_with_count(layer, Some(count), get_simple_movement_target::<A>)) Some(perform_movement_with_count(&mut layer.tree, Some(count), get_simple_movement_target::<A>))
} }
} }

View File

@ -5,6 +5,7 @@ use slab_tree::NodeId;
use crate::component::dialog::message::MessageDialogLayer; use crate::component::dialog::message::MessageDialogLayer;
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::FsTree;
use crate::state::action::{Action, ActionResult}; use crate::state::action::{Action, ActionResult};
use crate::state::Environment; use crate::state::Environment;
@ -19,19 +20,19 @@ impl Action<FsLayer> for ExpandCollapse {
return ActionResult::Nothing; return ActionResult::Nothing;
} }
if layer.expand_or_collapse(layer.selected_view_node_id) { if layer.tree.expand_or_collapse(layer.tree.selected_view_node_id) {
if depth > 1 { if depth > 1 {
if let Some(node) = layer.selected_node() { if let Some(node) = layer.tree.selected_node() {
if node.data().is_expanded() { if node.data().is_expanded() {
let child_node_ids = node.children().map(|node| node.node_id()).collect(); let child_node_ids = node.children().map(|node| node.node_id()).collect();
let remaining_depth = depth.saturating_sub(1); let remaining_depth = depth.saturating_sub(1);
if !expand_children_to_depth(layer, child_node_ids, remaining_depth) { if !expand_children_to_depth(&mut layer.tree, child_node_ids, remaining_depth) {
return ActionResult::PushLayer(Box::new(MessageDialogLayer::build() return ActionResult::push_layer(MessageDialogLayer::build()
.y(layer.dialog_y()) .y(layer.dialog_y())
.color(Color::LightYellow) .color(Color::LightYellow)
.title("Expansion Stopped") .title("Expansion Stopped")
.message(format!("Expansion was taking more than {} seconds, stopping now.", MAX_EXPANSION_TIME.as_secs())) .message(format!("Expansion was taking more than {} seconds, stopping now.", MAX_EXPANSION_TIME.as_secs()))
.ok())); .ok());
} }
} }
} }
@ -46,7 +47,7 @@ impl Action<FsLayer> for ExpandCollapse {
const MAX_EXPANSION_TIME: Duration = Duration::from_secs(10); const MAX_EXPANSION_TIME: Duration = Duration::from_secs(10);
fn expand_children_to_depth(layer: &mut FsLayer, mut child_node_ids: Vec<NodeId>, max_depth: usize) -> bool { fn expand_children_to_depth(tree: &mut FsTree, mut child_node_ids: Vec<NodeId>, max_depth: usize) -> bool {
let start_time = Instant::now(); let start_time = Instant::now();
let mut current_pass_node_ids = Vec::new(); let mut current_pass_node_ids = Vec::new();
@ -55,8 +56,8 @@ fn expand_children_to_depth(layer: &mut FsLayer, mut child_node_ids: Vec<NodeId>
for node_id in &current_pass_node_ids { for node_id in &current_pass_node_ids {
let node_id = *node_id; let node_id = *node_id;
layer.tree.expand(node_id); tree.expand(node_id);
get_child_node_ids(layer, node_id, &mut child_node_ids); get_child_node_ids(tree, node_id, &mut child_node_ids);
if start_time.elapsed() >= MAX_EXPANSION_TIME { if start_time.elapsed() >= MAX_EXPANSION_TIME {
return false; return false;
@ -67,8 +68,8 @@ fn expand_children_to_depth(layer: &mut FsLayer, mut child_node_ids: Vec<NodeId>
true true
} }
fn get_child_node_ids(layer: &FsLayer, node_id: NodeId, output_node_ids: &mut Vec<NodeId>) { fn get_child_node_ids(tree: &FsTree, node_id: NodeId, output_node_ids: &mut Vec<NodeId>) {
if let Some(node) = layer.tree.view.get(node_id) { if let Some(node) = tree.get_view_node(node_id) {
for child in node.children() { for child in node.children() {
output_node_ids.push(child.node_id()); output_node_ids.push(child.node_id());
} }

View File

@ -6,7 +6,7 @@ pub struct RefreshChildrenOfSelected;
impl Action<FsLayer> for RefreshChildrenOfSelected { impl Action<FsLayer> for RefreshChildrenOfSelected {
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult { fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
if layer.refresh_children(layer.selected_view_node_id) { if layer.tree.refresh_children(layer.tree.selected_view_node_id) {
ActionResult::Draw ActionResult::Draw
} else { } else {
ActionResult::Nothing ActionResult::Nothing

View File

@ -1,62 +0,0 @@
use std::cell::RefCell;
use std::rc::Rc;
use slab_tree::NodeId;
use crate::component::filesystem::FsLayer;
pub type FsLayerPendingEvents = Rc<RefCell<Vec<FsLayerEvent>>>;
pub enum FsLayerEvent {
RefreshViewNodeChildren(NodeId),
SelectViewNodeChildByFileName(NodeId, String),
DeleteViewNode(NodeId),
}
impl FsLayerEvent {
pub fn enqueue(self, pending_events: &FsLayerPendingEvents) -> bool {
if let Ok(mut pending_events) = pending_events.try_borrow_mut() {
pending_events.push(self);
true
} else {
false
}
}
pub fn handle(self, layer: &mut FsLayer) {
match self {
Self::RefreshViewNodeChildren(view_node_id) => handle_refresh_view_node_children(layer, view_node_id),
Self::SelectViewNodeChildByFileName(parent_view_node_id, child_file_name) => handle_select_view_node_child_by_name(layer, parent_view_node_id, child_file_name.as_str()),
Self::DeleteViewNode(view_node_id) => handle_delete_view_node(layer, view_node_id),
}
}
}
fn handle_refresh_view_node_children(layer: &mut FsLayer, view_node_id: NodeId) {
layer.refresh_children(view_node_id);
}
fn handle_select_view_node_child_by_name(layer: &mut FsLayer, parent_view_node_id: NodeId, child_file_name: &str) {
layer.tree.expand(parent_view_node_id);
if let Some(parent_node) = layer.tree.view.get(parent_view_node_id) {
for child_node in parent_node.children() {
if layer.tree.get_entry(&child_node).is_some_and(|entry| entry.name().str() == child_file_name) {
layer.selected_view_node_id = child_node.node_id();
return;
}
}
}
}
fn handle_delete_view_node(layer: &mut FsLayer, view_node_id: NodeId) {
let view = &mut layer.tree.view;
if layer.selected_view_node_id == view_node_id {
layer.selected_view_node_id = view.get_node_below_id(view_node_id).or_else(|| view.get_node_above_id(view_node_id)).unwrap_or_else(|| view.root_id());
}
if let Some(view_node) = view.remove(view_node_id) {
layer.tree.model.remove(view_node.model_node_id());
}
}

View File

@ -1,32 +1,27 @@
use std::cell::RefCell;
use std::path::Path; use std::path::Path;
use std::rc::Rc;
use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::event::FsLayerPendingEvents;
use crate::component::filesystem::registers::FsTreeRegisters; use crate::component::filesystem::registers::FsTreeRegisters;
use crate::component::filesystem::tree::{FsTree, FsTreeViewNode}; use crate::component::filesystem::tree::FsTree;
use crate::file::FileOwnerNameCache; use crate::file::FileOwnerNameCache;
use crate::input::keymap::{KeyBinding, KeyMapLookupResult}; use crate::input::keymap::{KeyBinding, KeyMapLookupResult};
use crate::state::action::ActionResult; use crate::state::action::ActionResult;
use crate::state::Environment; use crate::state::Environment;
use crate::state::event::{EventQueue, EventResult};
use crate::state::layer::Layer; use crate::state::layer::Layer;
use crate::state::view::Frame; use crate::state::view::Frame;
mod action; mod action;
mod event;
mod render; mod render;
mod tree; mod tree;
mod registers; mod registers;
pub struct FsLayer { pub struct FsLayer {
pub tree: FsTree, pub tree: FsTree,
pub selected_view_node_id: NodeId, tree_structure_version: u32,
pub registers: FsTreeRegisters, pub registers: FsTreeRegisters,
cursor_y: u16, cursor_y: u16,
pending_keys: Vec<KeyBinding>, pending_keys: Vec<KeyBinding>,
pending_events: FsLayerPendingEvents, event_queue: EventQueue<FsLayer>,
file_owner_name_cache: FileOwnerNameCache, file_owner_name_cache: FileOwnerNameCache,
column_width_cache: Option<ColumnWidths>, column_width_cache: Option<ColumnWidths>,
} }
@ -36,70 +31,25 @@ impl FsLayer {
// Initialize action map early in case it errors. // Initialize action map early in case it errors.
let _ = *action::ACTION_MAP; let _ = *action::ACTION_MAP;
let mut tree = FsTree::with_root_path(root_path);
let root_id = tree.view.root_id();
tree.expand(root_id);
Self { Self {
tree, tree: FsTree::with_root_path(root_path),
selected_view_node_id: root_id, tree_structure_version: 0,
cursor_y: 0, cursor_y: 0,
registers: FsTreeRegisters::new(), registers: FsTreeRegisters::new(),
pending_keys: Vec::new(), pending_keys: Vec::new(),
pending_events: Rc::new(RefCell::new(Vec::new())), event_queue: EventQueue::new(),
file_owner_name_cache: FileOwnerNameCache::new(), file_owner_name_cache: FileOwnerNameCache::new(),
column_width_cache: None, column_width_cache: None,
} }
} }
pub fn events(&self) -> EventQueue<Self> {
self.event_queue.rc_clone()
}
pub const fn dialog_y(&self) -> u16 { pub const fn dialog_y(&self) -> u16 {
self.cursor_y.saturating_add(1) self.cursor_y.saturating_add(1)
} }
pub fn selected_node(&self) -> Option<NodeRef<FsTreeViewNode>> {
return self.tree.view.get(self.selected_view_node_id);
}
pub fn expand(&mut self, view_node_id: NodeId) -> bool {
let result = self.tree.expand(view_node_id);
tree_structure_changed_if_true(self, result)
}
pub fn collapse(&mut self, view_node_id: NodeId) -> bool {
let result = self.tree.collapse(view_node_id);
tree_structure_changed_if_true(self, result)
}
pub fn expand_or_collapse(&mut self, view_node_id: NodeId) -> bool {
let result = self.tree.expand_or_collapse(view_node_id);
tree_structure_changed_if_true(self, result)
}
pub fn refresh_children(&mut self, view_node_id: NodeId) -> bool {
let result = self.tree.refresh_children(view_node_id);
if result && self.selected_node().is_none() {
self.selected_view_node_id = view_node_id;
}
tree_structure_changed_if_true(self, result)
}
pub fn traverse_up_root(&mut self) -> Option<NodeId> {
let new_root_id = self.tree.traverse_up_root();
tree_structure_changed_if_true(self, new_root_id.is_some());
new_root_id
}
}
fn tree_structure_changed(layer: &mut FsLayer) {
layer.column_width_cache.take();
}
fn tree_structure_changed_if_true(layer: &mut FsLayer, result: bool) -> bool {
if result {
tree_structure_changed(layer);
}
result
} }
impl Layer for FsLayer { impl Layer for FsLayer {
@ -133,9 +83,14 @@ impl Layer for FsLayer {
} }
} }
fn handle_events(&mut self, environment: &Environment) -> EventResult {
self.event_queue.take().into_iter().fold(EventResult::Nothing, |result, event| result.merge(event.dispatch(self, environment)))
}
fn render(&mut self, frame: &mut Frame) { fn render(&mut self, frame: &mut Frame) {
for event in self.pending_events.take() { if self.tree_structure_version != self.tree.structure_version() {
event.handle(self); self.tree_structure_version = self.tree.structure_version();
self.column_width_cache.take();
} }
render::render(self, frame); render::render(self, frame);

View File

@ -24,7 +24,7 @@ pub fn render(layer: &mut FsLayer, frame: &mut Frame) {
let column_widths = get_or_update_column_widths(layer, size.width); let column_widths = get_or_update_column_widths(layer, size.width);
let file_owner_name_cache = &mut layer.file_owner_name_cache; let file_owner_name_cache = &mut layer.file_owner_name_cache;
let (rows, cursor_y) = collect_displayed_rows(&layer.tree, layer.selected_view_node_id, size.height as usize); let (rows, cursor_y) = collect_displayed_rows(&layer.tree, layer.tree.selected_view_node_id, size.height as usize);
layer.cursor_y = cursor_y; layer.cursor_y = cursor_y;
frame.render_widget(Clear, size); frame.render_widget(Clear, size);
@ -63,7 +63,7 @@ fn collect_displayed_rows(tree: &FsTree, selected_node_id: NodeId, terminal_rows
let mut displayed_rows = Vec::with_capacity(terminal_rows); let mut displayed_rows = Vec::with_capacity(terminal_rows);
let mut cursor_y: u16 = 0; let mut cursor_y: u16 = 0;
if let Some(middle_node) = tree.view.get(selected_node_id).or_else(|| tree.view.root()) { if let Some(middle_node) = tree.selected_node().or_else(|| tree.view.root()) {
let middle_node_id = middle_node.node_id(); let middle_node_id = middle_node.node_id();
displayed_rows.push(NodeRow::from(&middle_node, tree, middle_node_id == selected_node_id)); displayed_rows.push(NodeRow::from(&middle_node, tree, middle_node_id == selected_node_id));

View File

@ -13,16 +13,39 @@ mod model;
mod view; mod view;
pub struct FsTree { pub struct FsTree {
pub model: FsTreeModel, model: FsTreeModel,
pub view: FsTreeView, pub view: FsTreeView,
pub selected_view_node_id: NodeId,
structure_version: u32,
} }
impl FsTree { impl FsTree {
pub fn with_root_path(path: &Path) -> Self { pub fn with_root_path(path: &Path) -> Self {
let model = FsTreeModel::with_root_path(path); let model = FsTreeModel::with_root_path(path);
let view = FsTreeView::from_model_root(&model); let view = FsTreeView::from_model_root(&model);
let root_id = view.root_id();
Self { model, view } let mut tree = Self {
model,
view,
selected_view_node_id: root_id,
structure_version: 0,
};
tree.expand(root_id);
tree
}
pub const fn structure_version(&self) -> u32 {
self.structure_version
}
pub fn selected_node(&self) -> Option<NodeRef<FsTreeViewNode>> {
return self.view.get(self.selected_view_node_id);
}
pub fn get_view_node(&self, view_node_id: NodeId) -> Option<NodeRef<FsTreeViewNode>> {
self.view.get(view_node_id)
} }
pub fn get_entry(&self, node: &NodeRef<FsTreeViewNode>) -> Option<&FileEntry> { pub fn get_entry(&self, node: &NodeRef<FsTreeViewNode>) -> Option<&FileEntry> {
@ -32,26 +55,79 @@ impl FsTree {
} }
pub fn expand(&mut self, view_node_id: NodeId) -> bool { pub fn expand(&mut self, view_node_id: NodeId) -> bool {
self.view.expand(view_node_id, &mut self.model) let result = self.view.expand(view_node_id, &mut self.model);
self.structure_changed_if_true(result)
} }
pub fn collapse(&mut self, view_node_id: NodeId) -> bool { pub fn collapse(&mut self, view_node_id: NodeId) -> bool {
self.view.collapse(view_node_id) let result = self.view.collapse(view_node_id);
self.structure_changed_if_true(result)
} }
pub fn expand_or_collapse(&mut self, view_node_id: NodeId) -> bool { pub fn expand_or_collapse(&mut self, view_node_id: NodeId) -> bool {
self.view.expand_or_collapse(view_node_id, &mut self.model) let result = self.view.expand_or_collapse(view_node_id, &mut self.model);
self.structure_changed_if_true(result)
} }
pub fn traverse_up_root(&mut self) -> Option<NodeId> { pub fn traverse_up_root(&mut self) -> Option<NodeId> {
self.view.traverse_up_root(&mut self.model) let new_root_id = self.view.traverse_up_root(&mut self.model);
self.structure_changed_if(new_root_id, Option::is_some)
} }
pub fn refresh_children(&mut self, view_node_id: NodeId) -> bool { pub fn refresh_children(&mut self, view_node_id: NodeId) -> bool {
if let Some(view_node) = self.view.get(view_node_id) { if let Some(view_node) = self.view.get(view_node_id) {
self.model.refresh_children(view_node.data().model_node_id()) && self.view.refresh_children(view_node_id, &self.model) let result = self.model.refresh_children(view_node.data().model_node_id()) && self.view.refresh_children(view_node_id, &self.model);
if result && self.selected_node().is_none() {
self.selected_view_node_id = view_node_id;
}
self.structure_changed_if_true(result)
} else { } else {
false false
} }
} }
pub fn select_child_node_by_name(&mut self, parent_view_node_id: NodeId, child_file_name: &str) -> bool {
self.expand(parent_view_node_id);
if let Some(parent_node) = self.view.get(parent_view_node_id) {
for child_node in parent_node.children() {
if self.get_entry(&child_node).is_some_and(|entry| entry.name().str() == child_file_name) {
self.selected_view_node_id = child_node.node_id();
return true;
}
}
}
false
}
pub fn delete_node(&mut self, view_node_id: NodeId) -> bool {
let view = &mut self.view;
if self.selected_view_node_id == view_node_id {
self.selected_view_node_id = view.get_node_below_id(view_node_id).or_else(|| view.get_node_above_id(view_node_id)).unwrap_or_else(|| view.root_id());
}
if let Some(view_node) = view.remove(view_node_id) {
self.model.remove(view_node.model_node_id());
true
} else {
false
}
}
fn structure_changed(&mut self) {
self.structure_version = self.structure_version.wrapping_add(1);
}
fn structure_changed_if<T, F>(&mut self, result: T, predicate: F) -> T where F: FnOnce(&T) -> bool {
if predicate(&result) {
self.structure_changed();
}
result
}
fn structure_changed_if_true(&mut self, result: bool) -> bool {
self.structure_changed_if(result, |result| *result)
}
} }

View File

@ -1,12 +1,16 @@
use std::cmp::min;
use crossterm::event::{KeyCode, KeyModifiers}; use crossterm::event::{KeyCode, KeyModifiers};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use ratatui::style::{Color, Style}; use ratatui::style::{Color, Style};
use ratatui::text::Span;
use ratatui::widgets::Paragraph; use ratatui::widgets::Paragraph;
use crate::component::input::InputField; use crate::component::input::InputField;
use crate::input::keymap::KeyBinding; use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult; use crate::state::action::ActionResult;
use crate::state::Environment; use crate::state::Environment;
use crate::state::event::EventResult;
use crate::state::layer::Layer; use crate::state::layer::Layer;
use crate::state::view::Frame; use crate::state::view::Frame;
@ -17,12 +21,10 @@ pub struct InputFieldOverlayLayer<'a> {
} }
impl<'a> InputFieldOverlayLayer<'a> { impl<'a> InputFieldOverlayLayer<'a> {
pub fn new(read_only_prefix: &'a str, confirm_action: Box<dyn Fn(String) -> ActionResult>) -> Self { pub fn new<F>(read_only_prefix: &'a str, confirm_action: F) -> Self where F: Fn(String) -> ActionResult + 'static {
Self { let field = InputField::new();
field: InputField::new(), let confirm_action = Box::new(confirm_action);
read_only_prefix, Self { field, read_only_prefix, confirm_action }
confirm_action,
}
} }
} }
@ -53,6 +55,10 @@ impl<'a> Layer for InputFieldOverlayLayer<'a> {
} }
} }
fn handle_events(&mut self, _environment: &Environment) -> EventResult {
EventResult::Nothing
}
fn render(&mut self, frame: &mut Frame) { fn render(&mut self, frame: &mut Frame) {
let size = frame.size(); let size = frame.size();
if size.width < 1 || size.height < 1 { if size.width < 1 || size.height < 1 {
@ -62,17 +68,22 @@ impl<'a> Layer for InputFieldOverlayLayer<'a> {
let x = size.x; let x = size.x;
let y = size.bottom().saturating_sub(1); let y = size.bottom().saturating_sub(1);
let prefix_style = Style::new() let prefix_text = Span::from(self.read_only_prefix);
.fg(Color::Black) let prefix_width = min(u16::try_from(prefix_text.width()).unwrap_or(u16::MAX), size.width.saturating_sub(2));
.bg(Color::LightYellow);
let prefix_paragraph = Paragraph::new(self.read_only_prefix) if prefix_width > 0 {
.style(prefix_style); let prefix_style = Style::new()
.fg(Color::Black)
.bg(Color::LightYellow);
frame.render_widget(prefix_paragraph, Rect { x, y, width: 1, height: 1 }); let prefix_paragraph = Paragraph::new(self.read_only_prefix)
.style(prefix_style);
if size.width > 1 { frame.render_widget(prefix_paragraph, Rect { x, y, width: prefix_width, height: 1 });
self.field.render(frame, x.saturating_add(1), y, size.width.saturating_sub(1), Color::LightYellow, Color::Yellow); }
if size.width > prefix_width {
self.field.render(frame, x.saturating_add(prefix_width), y, size.width.saturating_sub(prefix_width), Color::LightYellow, Color::Yellow);
} }
} }
} }

View File

@ -22,4 +22,12 @@ impl ActionResult {
Self::Nothing Self::Nothing
} }
} }
pub fn push_layer<T>(layer: T) -> Self where T: Layer + 'static {
Self::PushLayer(Box::new(layer))
}
pub fn replace_layer<T>(layer: T) -> Self where T: Layer + 'static {
Self::ReplaceLayer(Box::new(layer))
}
} }

72
src/state/event.rs Normal file
View File

@ -0,0 +1,72 @@
use std::cell::RefCell;
use std::rc::Rc;
use crate::state::Environment;
pub trait Event<L> {
fn dispatch(&self, layer: &mut L, environment: &Environment) -> EventResult;
}
impl<L, F> Event<L> for F where F: Fn(& mut L, &Environment) -> EventResult {
fn dispatch(&self, layer: &mut L, environment: &Environment) -> EventResult {
self(layer, environment)
}
}
pub struct EventQueue<L> {
events: Rc<RefCell<Vec<Box<dyn Event<L>>>>>
}
impl<L> EventQueue<L> {
pub fn new() -> Self {
Self { events: Rc::new(RefCell::new(Vec::new())) }
}
pub fn rc_clone(&self) -> Self {
Self { events: Rc::clone(&self.events) }
}
pub fn enqueue<E: Event<L> + 'static>(&self, event: E) -> bool {
if let Ok(mut events) = self.events.try_borrow_mut() {
events.push(Box::new(event));
true
} else {
false
}
}
pub fn enqueue_fn<F>(&self, event: F) -> bool where F: Fn(&mut L, &Environment) -> EventResult + 'static {
self.enqueue(event)
}
pub fn take(&self) -> Vec<Box<dyn Event<L>>> {
self.events.take()
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum EventResult {
Nothing,
Draw,
Redraw,
}
impl EventResult {
pub const fn draw_if(condition: bool) -> Self {
if condition {
Self::Draw
} else {
Self::Nothing
}
}
pub fn merge(self, other: Self) -> Self {
if self == Self::Redraw || other == Self::Redraw {
Self::Redraw
} else if self == Self::Draw || other == Self::Draw {
Self::Draw
} else {
Self::Nothing
}
}
}

View File

@ -1,9 +1,11 @@
use crate::input::keymap::KeyBinding; use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult; use crate::state::action::ActionResult;
use crate::state::Environment; use crate::state::Environment;
use crate::state::event::EventResult;
use crate::state::view::Frame; use crate::state::view::Frame;
pub trait Layer { pub trait Layer {
fn handle_input(&mut self, environment: &Environment, key_binding: KeyBinding) -> ActionResult; fn handle_input(&mut self, environment: &Environment, key_binding: KeyBinding) -> ActionResult;
fn handle_events(&mut self, environment: &Environment) -> EventResult;
fn render(&mut self, frame: &mut Frame); fn render(&mut self, frame: &mut Frame);
} }

View File

@ -3,6 +3,7 @@ use std::path::Path;
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::input::keymap::KeyBinding; use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult; use crate::state::action::ActionResult;
use crate::state::event::EventResult;
use crate::state::layer::Layer; use crate::state::layer::Layer;
use crate::state::view::Frame; use crate::state::view::Frame;
@ -10,6 +11,7 @@ pub use self::environment::Environment;
mod environment; mod environment;
pub mod action; pub mod action;
pub mod event;
pub mod layer; pub mod layer;
pub mod view; pub mod view;
@ -26,8 +28,12 @@ impl State {
} }
} }
pub fn handle_events(&mut self) -> EventResult {
self.layers.iter_mut().fold(EventResult::Nothing, |result, layer| result.merge(layer.handle_events(&self.environment)))
}
pub fn handle_input(&mut self, key_binding: KeyBinding) -> ActionResult { pub fn handle_input(&mut self, key_binding: KeyBinding) -> ActionResult {
self.layers.last_mut().map(|layer| layer.handle_input(&self.environment, key_binding)).unwrap_or(ActionResult::Nothing) self.layers.last_mut().map_or(ActionResult::Nothing, |layer| layer.handle_input(&self.environment, key_binding))
} }
pub fn handle_resize(&mut self, width: u16, height: u16) { pub fn handle_resize(&mut self, width: u16, height: u16) {

View File

@ -10,6 +10,7 @@ use ratatui::widgets::{StatefulWidget, Widget};
pub struct View { pub struct View {
term: Terminal<CrosstermBackend<Stdout>>, term: Terminal<CrosstermBackend<Stdout>>,
render_request: RenderRequest,
} }
impl View { impl View {
@ -22,7 +23,7 @@ impl View {
term.hide_cursor()?; term.hide_cursor()?;
term.clear()?; term.clear()?;
Ok(Self { term }) Ok(Self { term, render_request: RenderRequest::Draw })
} }
pub fn restore_terminal_on_panic() { pub fn restore_terminal_on_panic() {
@ -45,11 +46,34 @@ impl View {
self.term.size() self.term.size()
} }
pub fn clear(&mut self) -> io::Result<()> { pub fn set_dirty(&mut self, full_redraw: bool) {
self.term.clear() let new_request = if full_redraw {
RenderRequest::Redraw
} else {
RenderRequest::Draw
};
self.render_request = self.render_request.merge(new_request);
} }
pub fn render<R>(&mut self, renderer: R) -> io::Result<CompletedFrame> where R: FnOnce(&mut Frame) { pub fn render<R>(&mut self, renderer: R) -> io::Result<()> where R: FnOnce(&mut Frame) {
match self.render_request.consume() {
RenderRequest::Skip => {}
RenderRequest::Draw => {
self.draw(renderer)?;
}
RenderRequest::Redraw => {
self.term.clear()?;
self.draw(renderer)?;
}
}
Ok(())
}
fn draw<R>(&mut self, renderer: R) -> io::Result<CompletedFrame> where R: FnOnce(&mut Frame) {
self.term.draw(|frame| { self.term.draw(|frame| {
let mut frame = Frame::new(frame); let mut frame = Frame::new(frame);
renderer(&mut frame); renderer(&mut frame);
@ -58,6 +82,29 @@ impl View {
} }
} }
#[derive(Copy, Clone, Eq, PartialEq)]
enum RenderRequest {
Skip,
Draw,
Redraw,
}
impl RenderRequest {
fn merge(self, other: Self) -> Self {
if self == Self::Redraw || other == Self::Redraw {
Self::Redraw
} else if self == Self::Draw || other == Self::Draw {
Self::Draw
} else {
Self::Skip
}
}
fn consume(&mut self) -> Self {
std::mem::replace(self, Self::Skip)
}
}
pub struct Frame<'a, 'b> { pub struct Frame<'a, 'b> {
inner: &'a mut ratatui::Frame<'b, CrosstermBackend<Stdout>>, inner: &'a mut ratatui::Frame<'b, CrosstermBackend<Stdout>>,
cursor: Option<(u16, u16)>, cursor: Option<(u16, u16)>,