mirror of
https://github.com/chylex/Bark-Browser.git
synced 2024-11-25 16:42:53 +01:00
Compare commits
5 Commits
95c12d4f61
...
995e0fc8f8
Author | SHA1 | Date | |
---|---|---|---|
995e0fc8f8 | |||
020439d813 | |||
d04fc62033 | |||
38edfff874 | |||
df04593aab |
83
src/app.rs
83
src/app.rs
@ -5,6 +5,7 @@ use crossterm::event::{Event, KeyEventKind};
|
||||
use crate::input::keymap::KeyBinding;
|
||||
use crate::state::{Environment, State};
|
||||
use crate::state::action::ActionResult;
|
||||
use crate::state::event::EventResult;
|
||||
use crate::state::view::View;
|
||||
|
||||
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 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))?;
|
||||
|
||||
'event: loop {
|
||||
match handle_event(&mut state, crossterm::event::read()?) {
|
||||
ActionResult::Nothing => {
|
||||
continue 'event;
|
||||
}
|
||||
|
||||
ActionResult::Draw => {
|
||||
continue 'render;
|
||||
}
|
||||
|
||||
ActionResult::Redraw => {
|
||||
view.clear()?;
|
||||
continue 'render;
|
||||
}
|
||||
|
||||
ActionResult::PushLayer(layer) => {
|
||||
state.push_layer(layer);
|
||||
continue 'render;
|
||||
}
|
||||
|
||||
ActionResult::ReplaceLayer(layer) => {
|
||||
state.pop_layer();
|
||||
state.push_layer(layer);
|
||||
continue 'render;
|
||||
}
|
||||
|
||||
ActionResult::PopLayer => {
|
||||
if state.pop_layer() {
|
||||
break 'render;
|
||||
} else {
|
||||
continue 'render;
|
||||
}
|
||||
match handle_terminal_event(&mut state, crossterm::event::read()?) {
|
||||
ActionResult::Nothing => {
|
||||
continue;
|
||||
}
|
||||
|
||||
ActionResult::Draw => {
|
||||
view.set_dirty(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
ActionResult::Redraw => {
|
||||
view.set_dirty(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
ActionResult::PushLayer(layer) => {
|
||||
state.push_layer(layer);
|
||||
view.set_dirty(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
ActionResult::ReplaceLayer(layer) => {
|
||||
state.pop_layer();
|
||||
state.push_layer(layer);
|
||||
view.set_dirty(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
ActionResult::PopLayer => {
|
||||
if state.pop_layer() {
|
||||
break;
|
||||
} else {
|
||||
view.set_dirty(false);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -58,7 +73,7 @@ pub fn run(start_path: &Path) -> std::io::Result<()> {
|
||||
}
|
||||
|
||||
#[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 key.kind == KeyEventKind::Release {
|
||||
ActionResult::Nothing
|
||||
|
@ -69,7 +69,7 @@ impl<'a> InputFieldDialogBuilder5<'a> {
|
||||
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 step3 = step4.step3;
|
||||
let step2 = step3.step2;
|
||||
|
@ -12,6 +12,7 @@ use crate::component::input::InputField;
|
||||
use crate::input::keymap::KeyBinding;
|
||||
use crate::state::action::ActionResult;
|
||||
use crate::state::Environment;
|
||||
use crate::state::event::EventResult;
|
||||
use crate::state::layer::Layer;
|
||||
use crate::state::view::Frame;
|
||||
|
||||
@ -29,8 +30,9 @@ pub struct 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 confirm_action = Box::new(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) {
|
||||
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);
|
||||
|
@ -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();
|
||||
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));
|
||||
|
||||
Self::new(map, vec![
|
||||
|
@ -63,7 +63,7 @@ impl<'a> MessageDialogBuilder4<'a> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use crate::component::dialog::render_dialog_border;
|
||||
use crate::input::keymap::KeyBinding;
|
||||
use crate::state::action::ActionResult;
|
||||
use crate::state::Environment;
|
||||
use crate::state::event::EventResult;
|
||||
use crate::state::layer::Layer;
|
||||
use crate::state::view::Frame;
|
||||
|
||||
@ -40,7 +41,7 @@ impl<'a> MessageDialogLayer<'a> {
|
||||
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()
|
||||
.y(y)
|
||||
.color(Color::LightRed)
|
||||
@ -55,6 +56,10 @@ impl Layer for MessageDialogLayer<'_> {
|
||||
self.actions.handle_input(key_binding)
|
||||
}
|
||||
|
||||
fn handle_events(&mut self, _environment: &Environment) -> EventResult {
|
||||
EventResult::Nothing
|
||||
}
|
||||
|
||||
fn render(&mut self, frame: &mut Frame) {
|
||||
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);
|
||||
|
@ -17,7 +17,7 @@ impl Action<FsLayer> for PushCountDigit {
|
||||
if new_count > MAX_COUNT {
|
||||
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 {
|
||||
layer.registers.count = Some(new_count);
|
||||
ActionResult::Nothing
|
||||
|
@ -1,14 +1,12 @@
|
||||
use std::{fs, io};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
|
||||
use ratatui::style::Color;
|
||||
use slab_tree::NodeId;
|
||||
|
||||
use crate::component::dialog::input::InputFieldDialogLayer;
|
||||
use crate::component::dialog::message::MessageDialogLayer;
|
||||
use crate::component::filesystem::action::file::{FileNode, get_selected_file};
|
||||
use crate::component::filesystem::event::FsLayerEvent;
|
||||
use crate::component::filesystem::action::file::{FileNode, get_selected_file, RefreshParentDirectoryAndSelectFile};
|
||||
use crate::component::filesystem::FsLayer;
|
||||
use crate::file::FileKind;
|
||||
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 {
|
||||
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 {
|
||||
ActionResult::Nothing
|
||||
}
|
||||
@ -95,7 +93,7 @@ impl Action<FsLayer> for CreateDirectoryInParentOfSelectedEntry {
|
||||
|
||||
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) {
|
||||
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 {
|
||||
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()?)) })
|
||||
}
|
||||
|
||||
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 pending_events = Rc::clone(&layer.pending_events);
|
||||
let events = layer.events();
|
||||
|
||||
InputFieldDialogLayer::build()
|
||||
.y(y)
|
||||
@ -115,25 +113,24 @@ fn create_new_name_prompt<'b, T: CreateEntry>(layer: &FsLayer, parent_folder: Pa
|
||||
.color(Color::LightCyan, Color::Cyan)
|
||||
.title(T::title())
|
||||
.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() {
|
||||
return ActionResult::Nothing;
|
||||
}
|
||||
|
||||
let new_path = parent_folder.join(&new_name);
|
||||
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) {
|
||||
Ok(_) => {
|
||||
FsLayerEvent::RefreshViewNodeChildren(view_node_id_to_refresh).enqueue(&pending_events);
|
||||
FsLayerEvent::SelectViewNodeChildByFileName(view_node_id_to_refresh, new_name).enqueue(&pending_events);
|
||||
events.enqueue(RefreshParentDirectoryAndSelectFile { parent_view_node_id, child_file_name: new_name });
|
||||
ActionResult::PopLayer
|
||||
}
|
||||
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())))
|
||||
}
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use ratatui::style::Color;
|
||||
@ -9,18 +8,18 @@ use slab_tree::NodeId;
|
||||
|
||||
use crate::component::dialog::message::MessageDialogLayer;
|
||||
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::file::{FileEntry, FileKind};
|
||||
use crate::state::action::{Action, ActionResult};
|
||||
use crate::state::Environment;
|
||||
use crate::state::event::EventResult;
|
||||
|
||||
pub struct DeleteSelectedEntry;
|
||||
|
||||
impl Action<FsLayer> for DeleteSelectedEntry {
|
||||
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
|
||||
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 {
|
||||
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> {
|
||||
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) {
|
||||
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!("This will affect {}.", total_files.describe())),
|
||||
])
|
||||
.yes_no(Box::new(move || {
|
||||
.yes_no(move || {
|
||||
match delete_path_recursively(&path) {
|
||||
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
|
||||
}
|
||||
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);
|
||||
|
@ -8,11 +8,11 @@ use slab_tree::NodeRef;
|
||||
|
||||
use crate::component::dialog::message::MessageDialogLayer;
|
||||
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::tree::FsTreeViewNode;
|
||||
use crate::state::action::{Action, ActionResult};
|
||||
use crate::state::Environment;
|
||||
use crate::state::event::EventResult;
|
||||
use crate::util::slab_tree::NodeRefExtensions;
|
||||
|
||||
pub struct EditSelectedEntry;
|
||||
@ -34,12 +34,12 @@ fn open_default_editor(layer: &FsLayer, node: &NodeRef<FsTreeViewNode>, path: &P
|
||||
.status();
|
||||
|
||||
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.
|
||||
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
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use slab_tree::NodeRef;
|
||||
use slab_tree::{NodeId, NodeRef};
|
||||
|
||||
use crate::component::filesystem::FsLayer;
|
||||
use crate::component::filesystem::tree::FsTreeViewNode;
|
||||
use crate::file::{FileEntry, FileKind};
|
||||
use crate::state::Environment;
|
||||
use crate::state::event::{Event, EventResult};
|
||||
|
||||
pub use self::create::*;
|
||||
pub use self::delete::*;
|
||||
@ -18,7 +20,7 @@ mod edit;
|
||||
mod rename;
|
||||
|
||||
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(path) = entry.path() {
|
||||
return Some(FileNode { node, entry, path });
|
||||
@ -54,3 +56,19 @@ fn format_io_error(err: &io::Error) -> String {
|
||||
str.push('.');
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,13 @@
|
||||
use io::ErrorKind;
|
||||
use std::{fs, io};
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use ratatui::style::Color;
|
||||
use slab_tree::NodeRef;
|
||||
|
||||
use crate::component::dialog::input::InputFieldDialogLayer;
|
||||
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::event::FsLayerEvent;
|
||||
use crate::component::filesystem::action::file::{FileNode, format_io_error, get_entry_kind_name, get_selected_file, RefreshParentDirectoryAndSelectFile};
|
||||
use crate::component::filesystem::FsLayer;
|
||||
use crate::component::filesystem::tree::FsTreeViewNode;
|
||||
use crate::file::FileEntry;
|
||||
@ -24,7 +22,7 @@ pub struct RenameSelectedEntry {
|
||||
impl Action<FsLayer> for RenameSelectedEntry {
|
||||
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
|
||||
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 {
|
||||
ActionResult::Nothing
|
||||
}
|
||||
@ -35,8 +33,8 @@ impl RenameSelectedEntry {
|
||||
#[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> {
|
||||
let y = layer.dialog_y();
|
||||
let parent_node_id = node.parent_id();
|
||||
let pending_events = Rc::clone(&layer.pending_events);
|
||||
let events = layer.events();
|
||||
let parent_view_node_id = node.parent_id();
|
||||
|
||||
InputFieldDialogLayer::build()
|
||||
.y(y)
|
||||
@ -45,20 +43,19 @@ impl RenameSelectedEntry {
|
||||
.title(format!("Rename {}", get_entry_kind_name(entry)))
|
||||
.message(format!("Renaming {}", path.to_string_lossy()))
|
||||
.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) {
|
||||
Ok(_) => {
|
||||
if let Some(parent_node_id) = parent_node_id {
|
||||
FsLayerEvent::RefreshViewNodeChildren(parent_node_id).enqueue(&pending_events);
|
||||
FsLayerEvent::SelectViewNodeChildByFileName(parent_node_id, new_name).enqueue(&pending_events);
|
||||
if let Some(parent_view_node_id) = parent_view_node_id {
|
||||
events.enqueue(RefreshParentDirectoryAndSelectFile { parent_view_node_id, child_file_name: new_name });
|
||||
}
|
||||
ActionResult::PopLayer
|
||||
}
|
||||
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)))
|
||||
}
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,15 @@
|
||||
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::tree::FsTree;
|
||||
use crate::state::Environment;
|
||||
|
||||
pub struct ExpandSelectedOr<M: SimpleMovementAction>(pub M);
|
||||
|
||||
impl<M: SimpleMovementAction> MovementAction for ExpandSelectedOr<M> {
|
||||
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> {
|
||||
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 {
|
||||
perform_movement_with_count(layer, layer.registers.count, |layer, node_id| {
|
||||
if action(layer, node_id) {
|
||||
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_from_register(layer, |tree, node_id| {
|
||||
if action(tree, node_id) {
|
||||
Some(node_id)
|
||||
} else {
|
||||
get_simple_movement_target::<M>(layer, node_id)
|
||||
get_simple_movement_target::<M>(tree, node_id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
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::tree::{FsTreeView, FsTreeViewNode};
|
||||
use crate::component::filesystem::tree::{FsTree, FsTreeView, FsTreeViewNode};
|
||||
use crate::state::Environment;
|
||||
use crate::util::slab_tree::NodeRefExtensions;
|
||||
|
||||
@ -46,19 +46,17 @@ pub struct MoveOrTraverseUpParent;
|
||||
|
||||
impl MovementAction for MoveOrTraverseUpParent {
|
||||
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 {
|
||||
fn get_target(layer: &mut FsLayer, node_id: NodeId) -> Option<NodeId> {
|
||||
let view = &layer.tree.view;
|
||||
|
||||
if let Some(node) = view.get(node_id) {
|
||||
let target_node_id = <MoveToParent as SimpleMovementAction>::get_target(view, &node);
|
||||
fn get_target(tree: &mut FsTree, node_id: NodeId) -> Option<NodeId> {
|
||||
if let Some(node) = tree.view.get(node_id) {
|
||||
let target_node_id = <MoveToParent as SimpleMovementAction>::get_target(&tree.view, &node);
|
||||
if target_node_id.is_some() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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::FsLayer;
|
||||
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode};
|
||||
use crate::component::filesystem::tree::{FsTree, FsTreeView, FsTreeViewNode};
|
||||
use crate::state::Environment;
|
||||
|
||||
/// Moves up `count` lines (1 line by default).
|
||||
@ -28,16 +28,15 @@ pub struct MoveToFirst;
|
||||
|
||||
impl MovementAction for MoveToFirst {
|
||||
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 {
|
||||
fn get_target(layer: &mut FsLayer) -> NodeId where Self: Sized {
|
||||
let view = &layer.tree.view;
|
||||
let mut target_node_id = layer.selected_view_node_id;
|
||||
fn get_target(tree: &mut FsTree) -> NodeId where Self: Sized {
|
||||
let mut target_node_id = tree.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;
|
||||
}
|
||||
|
||||
@ -50,7 +49,7 @@ pub struct MoveToLast;
|
||||
|
||||
impl MovementAction for MoveToLast {
|
||||
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);
|
||||
Some(last_node_id)
|
||||
}
|
||||
@ -63,9 +62,10 @@ pub struct MoveToLineOr<A: MovementAction>(pub A);
|
||||
impl<A: MovementAction> MovementAction for MoveToLineOr<A> {
|
||||
fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized {
|
||||
if let Some(line_number) = layer.registers.count {
|
||||
let tree = &mut layer.tree;
|
||||
let line_index = Some(line_number.saturating_sub(1));
|
||||
let first_node_id = MoveToFirst::get_target(layer);
|
||||
Some(perform_movement_with_count_from(layer, line_index, first_node_id, get_simple_movement_target::<MoveDown>))
|
||||
let first_node_id = MoveToFirst::get_target(tree);
|
||||
Some(perform_movement_with_count_from(tree, line_index, first_node_id, get_simple_movement_target::<MoveDown>))
|
||||
} else {
|
||||
self.0.get_target(layer, environment)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use slab_tree::{NodeId, NodeRef};
|
||||
|
||||
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::Environment;
|
||||
|
||||
@ -25,7 +25,7 @@ pub trait MovementAction {
|
||||
impl<T: MovementAction> Action<FsLayer> for T {
|
||||
fn perform(&self, layer: &mut FsLayer, environment: &Environment) -> ActionResult {
|
||||
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
|
||||
} else {
|
||||
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> {
|
||||
perform_movement_with_count_from(layer, count, layer.selected_view_node_id, get_target)
|
||||
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(&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;
|
||||
|
||||
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;
|
||||
} else {
|
||||
break;
|
||||
@ -57,11 +61,10 @@ pub trait SimpleMovementAction {
|
||||
|
||||
impl<T: SimpleMovementAction> MovementAction for T {
|
||||
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> {
|
||||
let view = &layer.tree.view;
|
||||
view.get(node_id).and_then(|node| T::get_target(view, &node))
|
||||
fn get_simple_movement_target<T: SimpleMovementAction>(tree: &mut FsTree, node_id: NodeId) -> Option<NodeId> {
|
||||
tree.view.get(node_id).and_then(|node| T::get_target(&tree.view, &node))
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ impl<A: SimpleMovementAction + 'static, C: MovementCount> MovementWithCountFacto
|
||||
impl<A: SimpleMovementAction, C: MovementCount> MovementAction for MovementWithCount<A, C> {
|
||||
fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized {
|
||||
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>))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ use slab_tree::NodeId;
|
||||
|
||||
use crate::component::dialog::message::MessageDialogLayer;
|
||||
use crate::component::filesystem::FsLayer;
|
||||
use crate::component::filesystem::tree::FsTree;
|
||||
use crate::state::action::{Action, ActionResult};
|
||||
use crate::state::Environment;
|
||||
|
||||
@ -19,19 +20,19 @@ impl Action<FsLayer> for ExpandCollapse {
|
||||
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 let Some(node) = layer.selected_node() {
|
||||
if let Some(node) = layer.tree.selected_node() {
|
||||
if node.data().is_expanded() {
|
||||
let child_node_ids = node.children().map(|node| node.node_id()).collect();
|
||||
let remaining_depth = depth.saturating_sub(1);
|
||||
if !expand_children_to_depth(layer, child_node_ids, remaining_depth) {
|
||||
return ActionResult::PushLayer(Box::new(MessageDialogLayer::build()
|
||||
if !expand_children_to_depth(&mut layer.tree, child_node_ids, remaining_depth) {
|
||||
return ActionResult::push_layer(MessageDialogLayer::build()
|
||||
.y(layer.dialog_y())
|
||||
.color(Color::LightYellow)
|
||||
.title("Expansion Stopped")
|
||||
.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);
|
||||
|
||||
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 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 ¤t_pass_node_ids {
|
||||
let node_id = *node_id;
|
||||
layer.tree.expand(node_id);
|
||||
get_child_node_ids(layer, node_id, &mut child_node_ids);
|
||||
tree.expand(node_id);
|
||||
get_child_node_ids(tree, node_id, &mut child_node_ids);
|
||||
|
||||
if start_time.elapsed() >= MAX_EXPANSION_TIME {
|
||||
return false;
|
||||
@ -67,8 +68,8 @@ fn expand_children_to_depth(layer: &mut FsLayer, mut child_node_ids: Vec<NodeId>
|
||||
true
|
||||
}
|
||||
|
||||
fn get_child_node_ids(layer: &FsLayer, node_id: NodeId, output_node_ids: &mut Vec<NodeId>) {
|
||||
if let Some(node) = layer.tree.view.get(node_id) {
|
||||
fn get_child_node_ids(tree: &FsTree, node_id: NodeId, output_node_ids: &mut Vec<NodeId>) {
|
||||
if let Some(node) = tree.get_view_node(node_id) {
|
||||
for child in node.children() {
|
||||
output_node_ids.push(child.node_id());
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ pub struct RefreshChildrenOfSelected;
|
||||
|
||||
impl Action<FsLayer> for RefreshChildrenOfSelected {
|
||||
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
|
||||
} else {
|
||||
ActionResult::Nothing
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -1,32 +1,27 @@
|
||||
use std::cell::RefCell;
|
||||
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::tree::{FsTree, FsTreeViewNode};
|
||||
use crate::component::filesystem::tree::FsTree;
|
||||
use crate::file::FileOwnerNameCache;
|
||||
use crate::input::keymap::{KeyBinding, KeyMapLookupResult};
|
||||
use crate::state::action::ActionResult;
|
||||
use crate::state::Environment;
|
||||
use crate::state::event::{EventQueue, EventResult};
|
||||
use crate::state::layer::Layer;
|
||||
use crate::state::view::Frame;
|
||||
|
||||
mod action;
|
||||
mod event;
|
||||
mod render;
|
||||
mod tree;
|
||||
mod registers;
|
||||
|
||||
pub struct FsLayer {
|
||||
pub tree: FsTree,
|
||||
pub selected_view_node_id: NodeId,
|
||||
tree_structure_version: u32,
|
||||
pub registers: FsTreeRegisters,
|
||||
cursor_y: u16,
|
||||
pending_keys: Vec<KeyBinding>,
|
||||
pending_events: FsLayerPendingEvents,
|
||||
event_queue: EventQueue<FsLayer>,
|
||||
file_owner_name_cache: FileOwnerNameCache,
|
||||
column_width_cache: Option<ColumnWidths>,
|
||||
}
|
||||
@ -36,70 +31,25 @@ impl FsLayer {
|
||||
// Initialize action map early in case it errors.
|
||||
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 {
|
||||
tree,
|
||||
selected_view_node_id: root_id,
|
||||
tree: FsTree::with_root_path(root_path),
|
||||
tree_structure_version: 0,
|
||||
cursor_y: 0,
|
||||
registers: FsTreeRegisters::new(),
|
||||
pending_keys: Vec::new(),
|
||||
pending_events: Rc::new(RefCell::new(Vec::new())),
|
||||
event_queue: EventQueue::new(),
|
||||
file_owner_name_cache: FileOwnerNameCache::new(),
|
||||
column_width_cache: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn events(&self) -> EventQueue<Self> {
|
||||
self.event_queue.rc_clone()
|
||||
}
|
||||
|
||||
pub const fn dialog_y(&self) -> u16 {
|
||||
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 {
|
||||
@ -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) {
|
||||
for event in self.pending_events.take() {
|
||||
event.handle(self);
|
||||
if self.tree_structure_version != self.tree.structure_version() {
|
||||
self.tree_structure_version = self.tree.structure_version();
|
||||
self.column_width_cache.take();
|
||||
}
|
||||
|
||||
render::render(self, frame);
|
||||
|
@ -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 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;
|
||||
|
||||
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 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();
|
||||
|
||||
displayed_rows.push(NodeRow::from(&middle_node, tree, middle_node_id == selected_node_id));
|
||||
|
@ -13,16 +13,39 @@ mod model;
|
||||
mod view;
|
||||
|
||||
pub struct FsTree {
|
||||
pub model: FsTreeModel,
|
||||
model: FsTreeModel,
|
||||
pub view: FsTreeView,
|
||||
pub selected_view_node_id: NodeId,
|
||||
structure_version: u32,
|
||||
}
|
||||
|
||||
impl FsTree {
|
||||
pub fn with_root_path(path: &Path) -> Self {
|
||||
let model = FsTreeModel::with_root_path(path);
|
||||
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> {
|
||||
@ -32,26 +55,79 @@ impl FsTree {
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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> {
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,16 @@
|
||||
use std::cmp::min;
|
||||
|
||||
use crossterm::event::{KeyCode, KeyModifiers};
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::text::Span;
|
||||
use ratatui::widgets::Paragraph;
|
||||
|
||||
use crate::component::input::InputField;
|
||||
use crate::input::keymap::KeyBinding;
|
||||
use crate::state::action::ActionResult;
|
||||
use crate::state::Environment;
|
||||
use crate::state::event::EventResult;
|
||||
use crate::state::layer::Layer;
|
||||
use crate::state::view::Frame;
|
||||
|
||||
@ -17,12 +21,10 @@ pub struct InputFieldOverlayLayer<'a> {
|
||||
}
|
||||
|
||||
impl<'a> InputFieldOverlayLayer<'a> {
|
||||
pub fn new(read_only_prefix: &'a str, confirm_action: Box<dyn Fn(String) -> ActionResult>) -> Self {
|
||||
Self {
|
||||
field: InputField::new(),
|
||||
read_only_prefix,
|
||||
confirm_action,
|
||||
}
|
||||
pub fn new<F>(read_only_prefix: &'a str, confirm_action: F) -> Self where F: Fn(String) -> ActionResult + 'static {
|
||||
let field = InputField::new();
|
||||
let confirm_action = Box::new(confirm_action);
|
||||
Self { field, read_only_prefix, 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) {
|
||||
let size = frame.size();
|
||||
if size.width < 1 || size.height < 1 {
|
||||
@ -62,17 +68,22 @@ impl<'a> Layer for InputFieldOverlayLayer<'a> {
|
||||
let x = size.x;
|
||||
let y = size.bottom().saturating_sub(1);
|
||||
|
||||
let prefix_style = Style::new()
|
||||
.fg(Color::Black)
|
||||
.bg(Color::LightYellow);
|
||||
let prefix_text = Span::from(self.read_only_prefix);
|
||||
let prefix_width = min(u16::try_from(prefix_text.width()).unwrap_or(u16::MAX), size.width.saturating_sub(2));
|
||||
|
||||
let prefix_paragraph = Paragraph::new(self.read_only_prefix)
|
||||
.style(prefix_style);
|
||||
if prefix_width > 0 {
|
||||
let prefix_style = Style::new()
|
||||
.fg(Color::Black)
|
||||
.bg(Color::LightYellow);
|
||||
|
||||
let prefix_paragraph = Paragraph::new(self.read_only_prefix)
|
||||
.style(prefix_style);
|
||||
|
||||
frame.render_widget(prefix_paragraph, Rect { x, y, width: prefix_width, height: 1 });
|
||||
}
|
||||
|
||||
frame.render_widget(prefix_paragraph, Rect { x, y, width: 1, height: 1 });
|
||||
|
||||
if size.width > 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,4 +22,12 @@ impl ActionResult {
|
||||
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
72
src/state/event.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
use crate::input::keymap::KeyBinding;
|
||||
use crate::state::action::ActionResult;
|
||||
use crate::state::Environment;
|
||||
use crate::state::event::EventResult;
|
||||
use crate::state::view::Frame;
|
||||
|
||||
pub trait Layer {
|
||||
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);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use std::path::Path;
|
||||
use crate::component::filesystem::FsLayer;
|
||||
use crate::input::keymap::KeyBinding;
|
||||
use crate::state::action::ActionResult;
|
||||
use crate::state::event::EventResult;
|
||||
use crate::state::layer::Layer;
|
||||
use crate::state::view::Frame;
|
||||
|
||||
@ -10,6 +11,7 @@ pub use self::environment::Environment;
|
||||
|
||||
mod environment;
|
||||
pub mod action;
|
||||
pub mod event;
|
||||
pub mod layer;
|
||||
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 {
|
||||
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) {
|
||||
|
@ -10,6 +10,7 @@ use ratatui::widgets::{StatefulWidget, Widget};
|
||||
|
||||
pub struct View {
|
||||
term: Terminal<CrosstermBackend<Stdout>>,
|
||||
render_request: RenderRequest,
|
||||
}
|
||||
|
||||
impl View {
|
||||
@ -22,7 +23,7 @@ impl View {
|
||||
term.hide_cursor()?;
|
||||
term.clear()?;
|
||||
|
||||
Ok(Self { term })
|
||||
Ok(Self { term, render_request: RenderRequest::Draw })
|
||||
}
|
||||
|
||||
pub fn restore_terminal_on_panic() {
|
||||
@ -45,11 +46,34 @@ impl View {
|
||||
self.term.size()
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) -> io::Result<()> {
|
||||
self.term.clear()
|
||||
pub fn set_dirty(&mut self, full_redraw: bool) {
|
||||
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| {
|
||||
let mut frame = Frame::new(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> {
|
||||
inner: &'a mut ratatui::Frame<'b, CrosstermBackend<Stdout>>,
|
||||
cursor: Option<(u16, u16)>,
|
||||
|
Loading…
Reference in New Issue
Block a user